Completed
Push — develop ( d68bff...dc031d )
by
unknown
15s
created

EventStatusLinkResponseListener   B

Complexity

Total Complexity 27

Size/Duplication

Total Lines 371
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 87.02%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 16
dl 0
loc 371
ccs 114
cts 131
cp 0.8702
rs 8.4614
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A isNotConcerningRequest() 0 4 1
A createQueueEventObject() 0 10 1
A getSecurityUsername() 0 8 2
A getWorkerQueueEvent() 0 6 1
A getWorkerRelativeUrl() 0 9 1
B __construct() 0 33 2
C onKernelResponse() 0 47 10
A generateRoutingKey() 0 20 4
B getStatusUrl() 0 46 4
B getSubscribedWorkerIds() 0 30 1
1
<?php
2
/**
3
 * Response listener that adds an eventStatus to Link header if necessary, creates an EventStatus resource
4
 * and publishes the change to the queue
5
 */
6
7
namespace Graviton\RabbitMqBundle\Listener;
8
9
use Doctrine\ODM\MongoDB\DocumentManager;
10
use Graviton\DocumentBundle\Service\ExtReferenceConverter;
11
use Graviton\RabbitMqBundle\Document\QueueEvent;
12
use Graviton\RestBundle\HttpFoundation\LinkHeader;
13
use Graviton\RestBundle\HttpFoundation\LinkHeaderItem;
14
use Symfony\Component\HttpFoundation\Response;
15
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
16
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\RequestStack;
19
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
20
use Symfony\Component\Routing\RouterInterface;
21
use Graviton\SecurityBundle\Service\SecurityUtils;
22
use GravitonDyn\EventStatusBundle\Document\EventStatus;
23
use Zend\Diactoros\Uri;
24
25
/**
26
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
27
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
28
 * @link     http://swisscom.ch
29
 */
30
class EventStatusLinkResponseListener
31
{
32
33
    /**
34
     * @var ProducerInterface Producer for publishing messages.
35
     */
36
    private $rabbitMqProducer = null;
37
38
    /**
39
     * @var RouterInterface Router to generate resource URLs
40
     */
41
    private $router = null;
42
43
    /**
44
     * @var Request request
45
     */
46
    private $request;
47
48
    /**
49
     * @var QueueEvent queue event document
50
     */
51
    private $queueEventDocument;
52
53
    /**
54
     * @var array
55
     */
56
    private $eventMap;
57
58
    /**
59
     * @var ExtReferenceConverter ExtReferenceConverter
60
     */
61
    private $extRefConverter;
62
63
    /**
64
     * @var string classname of the EventWorker document
65
     */
66
    private $eventWorkerClassname;
67
68
    /**
69
     * @var string classname of the EventStatus document
70
     */
71
    private $eventStatusClassname;
72
73
    /**
74
     * @var string classname of the EventStatusStatus document
75
     */
76
    private $eventStatusStatusClassname;
77
78
    /**
79
     * @var string classname of the EventStatusEventResource document
80
     */
81
    private $eventStatusEventResourceClassname;
82
83
    /**
84
     * @var string route name of the /event/status route
85
     */
86
    private $eventStatusRouteName;
87
88
    /**
89
     * @var DocumentManager Document manager
90
     */
91
    private $documentManager;
92
93
    /**
94
     * @var SecurityUtils
95
     */
96
    protected $securityUtils;
97
98
    /**
99
     * @var Uri
100
     */
101
    protected $workerRelativeUrl;
102
103
    /**
104
     * @param ProducerInterface     $rabbitMqProducer                  RabbitMQ dependency
105
     * @param RouterInterface       $router                            Router dependency
106
     * @param RequestStack          $requestStack                      Request stack
107
     * @param DocumentManager       $documentManager                   Doctrine document manager
108
     * @param ExtReferenceConverter $extRefConverter                   instance of the ExtReferenceConverter service
109
     * @param QueueEvent            $queueEventDocument                queueevent document
110
     * @param array                 $eventMap                          eventmap
111
     * @param string                $eventWorkerClassname              classname of the EventWorker document
112
     * @param string                $eventStatusClassname              classname of the EventStatus document
113
     * @param string                $eventStatusStatusClassname        classname of the EventStatusStatus document
114
     * @param string                $eventStatusEventResourceClassname classname of the E*S*E*Resource document
115
     * @param string                $eventStatusRouteName              name of the route to EventStatus
116
     * @param SecurityUtils         $securityUtils                     Security utils service
117
     * @param string                $workerRelativeUrl                 backend url relative from the workers
118
     */
119 2
    public function __construct(
120
        ProducerInterface $rabbitMqProducer,
121
        RouterInterface $router,
122
        RequestStack $requestStack,
123
        DocumentManager $documentManager,
124
        ExtReferenceConverter $extRefConverter,
125
        QueueEvent $queueEventDocument,
126
        array $eventMap,
127
        $eventWorkerClassname,
128
        $eventStatusClassname,
129
        $eventStatusStatusClassname,
130
        $eventStatusEventResourceClassname,
131
        $eventStatusRouteName,
132
        SecurityUtils $securityUtils,
133
        $workerRelativeUrl
134
    ) {
135 2
        $this->rabbitMqProducer = $rabbitMqProducer;
136 2
        $this->router = $router;
137 2
        $this->request = $requestStack->getCurrentRequest();
138 2
        $this->documentManager = $documentManager;
139 2
        $this->extRefConverter = $extRefConverter;
140 2
        $this->queueEventDocument = $queueEventDocument;
141 2
        $this->eventMap = $eventMap;
142 2
        $this->eventWorkerClassname = $eventWorkerClassname;
143 2
        $this->eventStatusClassname = $eventStatusClassname;
144 2
        $this->eventStatusStatusClassname = $eventStatusStatusClassname;
145 2
        $this->eventStatusEventResourceClassname = $eventStatusEventResourceClassname;
146 2
        $this->eventStatusRouteName = $eventStatusRouteName;
147 2
        $this->securityUtils = $securityUtils;
148 2
        if (!is_null($workerRelativeUrl)) {
149
            $this->workerRelativeUrl = new Uri($workerRelativeUrl);
150
        }
151 2
    }
152
153
    /**
154
     * add a rel=eventStatus Link header to the response if necessary
155
     *
156
     * @param FilterResponseEvent $event response listener event
157
     *
158
     * @return void
159
     */
160 2
    public function onKernelResponse(FilterResponseEvent $event)
161
    {
162
        /**
163
         * @var Response $response
164
         */
165 2
        $response = $event->getResponse();
166
167
        // exit if not master request, uninteresting method or an error occurred
168 2
        if (!$event->isMasterRequest() || $this->isNotConcerningRequest() || !$response->isSuccessful()) {
169
            return;
170
        }
171
172
        // we can always safely call this, it doesn't need much resources.
173
        // only if we have subscribers, it will create more load as it persists an EventStatus
174 2
        $queueEvent = $this->createQueueEventObject();
175
176 2
        if (!empty($queueEvent->getStatusurl()) && !empty($queueEvent->getEvent())) {
177 2
            $linkHeader = LinkHeader::fromResponse($response);
178 2
            $linkHeader->add(
179 2
                new LinkHeaderItem(
180 2
                    $queueEvent->getStatusurl(),
181 2
                    array('rel' => 'eventStatus')
182 1
                )
183 1
            );
184
185 2
            $response->headers->set(
186 2
                'Link',
187 1
                (string) $linkHeader
188 1
            );
189 1
        }
190
191
        // let's send it to the queue(s) if appropriate
192 2
        if (!empty($queueEvent->getEvent())) {
193 2
            $queuesForEvent = $this->getSubscribedWorkerIds($queueEvent);
194
195
            // if needed and activated, change urls relative to workers
196 2
            if (!empty($queuesForEvent) && $this->workerRelativeUrl instanceof Uri) {
197
                $queueEvent = $this->getWorkerQueueEvent($queueEvent);
198
            }
199
200 2
            foreach ($queuesForEvent as $queueForEvent) {
201
                // declare the Queue for the Event if its not there already declared
202 2
                $this->rabbitMqProducer->getChannel()->queue_declare($queueForEvent, false, true, false, false);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OldSound\RabbitMqBundle\RabbitMq\ProducerInterface as the method getChannel() does only exist in the following implementations of said interface: Graviton\RabbitMqBundle\Producer\Dummy, Graviton\RabbitMqBundle\Producer\JobProducer, OldSound\RabbitMqBundle\RabbitMq\Producer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
203 2
                $this->rabbitMqProducer->publish(json_encode($queueEvent), $queueForEvent);
204 1
            }
205 1
        }
206 2
    }
207
208
    /**
209
     * we only want to do something if we have a mapped event..
210
     *
211
     * @return boolean true if it should not concern us, false otherwise
212
     */
213 2
    private function isNotConcerningRequest()
214
    {
215 2
        return is_null($this->generateRoutingKey());
216
    }
217
218
    /**
219
     * Creates the structured object that will be sent to the queue (eventually..)
220
     *
221
     * @return QueueEvent event
222
     */
223 2
    private function createQueueEventObject()
224
    {
225 2
        $obj = clone $this->queueEventDocument;
226 2
        $obj->setEvent($this->generateRoutingKey());
227 2
        $obj->setDocumenturl($this->request->get('selfLink'));
228 2
        $obj->setStatusurl($this->getStatusUrl($obj));
229 2
        $obj->setCoreUserId($this->getSecurityUsername());
230
231 2
        return $obj;
232
    }
233
234
    /**
235
     * compose our routingKey. this will have the form of 'document.[bundle].[document].[event]'
236
     * rules:
237
     *  * always 4 parts divided by points.
238
     *  * in this context (doctrine/odm stuff) we prefix with 'document.'
239
     *
240
     * @return string routing key
241
     */
242 2
    private function generateRoutingKey()
243
    {
244 2
        $routeParts = explode('.', $this->request->get('_route'));
245 2
        $action = array_pop($routeParts);
246 2
        $baseRoute = implode('.', $routeParts);
247
248
        // find our route in the map
249 2
        $routingKey = null;
250
251 2
        foreach ($this->eventMap as $mapElement) {
252 2
            if ($mapElement['baseRoute'] == $baseRoute &&
253 2
                isset($mapElement['events'][$action])
254 1
            ) {
255 2
                $routingKey = $mapElement['events'][$action];
256 2
                break;
257
            }
258 1
        }
259
260 2
        return $routingKey;
261
    }
262
263
    /**
264
     * Creates a EventStatus object that gets persisted..
265
     *
266
     * @param QueueEvent $queueEvent queueEvent object
267
     *
268
     * @return string
269
     */
270 2
    private function getStatusUrl($queueEvent)
271
    {
272
        // this has to be checked after cause we should not call getSubscribedWorkerIds() if above is true
273 2
        $workerIds = $this->getSubscribedWorkerIds($queueEvent);
274 2
        if (empty($workerIds)) {
275
            return '';
276
        }
277
278
        // we have subscribers; create the EventStatus entry
279
        /** @var EventStatus $eventStatus **/
280 2
        $eventStatus = new $this->eventStatusClassname();
281 2
        $eventStatus->setCreatedate(new \DateTime());
282 2
        $eventStatus->setEventname($queueEvent->getEvent());
283
284
        // if available, transport the ref document to the eventStatus instance
285 2
        if (!empty($queueEvent->getDocumenturl())) {
286 2
            $eventStatusResource = new $this->eventStatusEventResourceClassname();
287 2
            $eventStatusResource->setRef($this->extRefConverter->getExtReference($queueEvent->getDocumenturl()));
288 2
            $eventStatus->setEventresource($eventStatusResource);
289 1
        }
290
291 2
        foreach ($workerIds as $workerId) {
292
            /** @var \GravitonDyn\EventStatusBundle\Document\EventStatusStatus $eventStatusStatus **/
293 2
            $eventStatusStatus = new $this->eventStatusStatusClassname();
294 2
            $eventStatusStatus->setWorkerid($workerId);
295 2
            $eventStatusStatus->setStatus('opened');
296 2
            $eventStatus->addStatus($eventStatusStatus);
297 1
        }
298
299
        // Set username to Event
300 2
        $eventStatus->setUserid($this->getSecurityUsername());
301
302 2
        $this->documentManager->persist($eventStatus);
303 2
        $this->documentManager->flush();
304
305
        // get the url..
306 2
        $url = $this->router->generate(
307 2
            $this->eventStatusRouteName,
308
            [
309 2
                'id' => $eventStatus->getId()
310 1
            ],
311 1
            UrlGeneratorInterface::ABSOLUTE_URL
312 1
        );
313
314 2
        return $url;
315
    }
316
317
    /**
318
     * Checks EventWorker for worker that are subscribed to our event and returns
319
     * their workerIds as array
320
321
     * @param QueueEvent $queueEvent queueEvent object
322
     *
323
     * @return array array of worker ids
324
     */
325 2
    private function getSubscribedWorkerIds(QueueEvent $queueEvent)
326
    {
327
        // compose our regex to match stars ;-)
328
        // results in = /((\*|document)+)\.((\*|dude)+)\.((\*|config)+)\.((\*|update)+)/
329 2
        $routingArgs = explode('.', $queueEvent->getEvent());
330
        $regex =
331
            '/'.
332 2
            implode(
333 2
                '\.',
334 2
                array_map(
335 2
                    function ($arg) {
336 2
                        return '((\*|'.$arg.')+)';
337 2
                    },
338 1
                    $routingArgs
339 1
                )
340 1
            ).
341 2
            '/';
342
343
        // look up workers by class name
344 2
        $qb = $this->documentManager->createQueryBuilder($this->eventWorkerClassname);
345
        $data = $qb
346 2
            ->select('id')
347 2
            ->field('subscription.event')
348 2
            ->equals(new \MongoRegex($regex))
349 2
            ->getQuery()
350 2
            ->execute()
351 2
            ->toArray();
352
353 2
        return array_keys($data);
354
    }
355
356
    /**
357
     * Security needs to be enabled to get
358
     *
359
     * @return String
360
     */
361 2
    private function getSecurityUsername()
362
    {
363 2
        if ($this->securityUtils->isSecurityUser()) {
364
            return $this->securityUtils->getSecurityUsername();
365
        }
366
367 2
        return '';
368
    }
369
370
    /**
371
     * Changes the urls in the QueueEvent for the workers
372
     *
373
     * @param QueueEvent $queueEvent queue event
374
     *
375
     * @return QueueEvent altered queue event
376
     */
377
    private function getWorkerQueueEvent(QueueEvent $queueEvent)
378
    {
379
        $queueEvent->setDocumenturl($this->getWorkerRelativeUrl($queueEvent->getDocumenturl()));
380
        $queueEvent->setStatusurl($this->getWorkerRelativeUrl($queueEvent->getStatusurl()));
381
        return $queueEvent;
382
    }
383
384
    /**
385
     * changes an uri for the workers
386
     *
387
     * @param string $uri uri
388
     *
389
     * @return string changed uri
390
     */
391
    private function getWorkerRelativeUrl($uri)
392
    {
393
        $uri = new Uri($uri);
394
        $uri = $uri
395
            ->withHost($this->workerRelativeUrl->getHost())
396
            ->withScheme($this->workerRelativeUrl->getScheme())
397
            ->withPort($this->workerRelativeUrl->getPort());
398
        return (string) $uri;
399
    }
400
}
401