Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.41% |
69 / 88 |
|
78.57% |
11 / 14 |
CRAP | |
0.00% |
0 / 1 |
WebhookEventsService | |
78.41% |
69 / 88 |
|
78.57% |
11 / 14 |
34.34 | |
0.00% |
0 / 1 |
registerEvent | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
unregisterEvent | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
isEventRegistered | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getRegisteredEvents | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
handleEvent | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
checkEventIsSupported | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 | |||
prepareTasksParams | |
72.41% |
21 / 29 |
|
0.00% |
0 / 1 |
6.76 | |||
createWebhookTasks | |
16.67% |
2 / 12 |
|
0.00% |
0 / 1 |
8.21 | |||
generateEventId | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getEventHandlerCallback | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getWebhookRegistry | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getWebhookTaskService | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getEventManager | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
extendPayload | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2019 (original work) Open Assessment Technologies SA; |
19 | */ |
20 | |
21 | namespace oat\tao\model\webhooks; |
22 | |
23 | use oat\oatbox\event\Event; |
24 | use oat\oatbox\event\EventManager; |
25 | use oat\oatbox\log\LoggerAwareTrait; |
26 | use oat\oatbox\service\ConfigurableService; |
27 | use oat\tao\model\exceptions\WebhookConfigMissingException; |
28 | use oat\tao\model\webhooks\configEntity\WebhookInterface; |
29 | use oat\tao\model\webhooks\task\WebhookTaskParams; |
30 | |
31 | class WebhookEventsService extends ConfigurableService implements WebhookEventsServiceInterface |
32 | { |
33 | use LoggerAwareTrait; |
34 | |
35 | /** |
36 | * Option value is array of ['eventName' => true, ... ] of supported events |
37 | * Such array structure is needed to perform quick search by key |
38 | */ |
39 | public const OPTION_SUPPORTED_EVENTS = 'supportedEvents'; |
40 | |
41 | /** |
42 | * @inheritDoc |
43 | */ |
44 | public function registerEvent($eventName) |
45 | { |
46 | $supportedEvents = $this->getRegisteredEvents(); |
47 | $supportedEvents[$eventName] = true; |
48 | $this->setOption(self::OPTION_SUPPORTED_EVENTS, $supportedEvents); |
49 | |
50 | $this->getEventManager()->attach($eventName, $this->getEventHandlerCallback()); |
51 | } |
52 | |
53 | /** |
54 | * @inheritDoc |
55 | */ |
56 | public function unregisterEvent($eventName) |
57 | { |
58 | $supportedEvents = $this->getRegisteredEvents(); |
59 | unset($supportedEvents[$eventName]); |
60 | $this->setOption(self::OPTION_SUPPORTED_EVENTS, $supportedEvents); |
61 | |
62 | $this->getEventManager()->detach($eventName, $this->getEventHandlerCallback()); |
63 | } |
64 | |
65 | /** |
66 | * @inheritDoc |
67 | */ |
68 | public function isEventRegistered($eventName) |
69 | { |
70 | $supportedEvents = $this->getRegisteredEvents(); |
71 | return isset($supportedEvents[$eventName]); |
72 | } |
73 | |
74 | /** |
75 | * @return string[] |
76 | */ |
77 | public function getRegisteredEvents() |
78 | { |
79 | $events = $this->getOption(self::OPTION_SUPPORTED_EVENTS); |
80 | return $events !== null |
81 | ? $events |
82 | : []; |
83 | } |
84 | |
85 | public function handleEvent(Event $event) |
86 | { |
87 | if (!$this->checkEventIsSupported($event)) { |
88 | return; |
89 | } |
90 | |
91 | /** @var WebhookSerializableEventInterface $event */ |
92 | |
93 | $webhookConfigIds = $this->getWebhookRegistry()->getWebhookConfigIds($event->getName()); |
94 | if (count($webhookConfigIds) === 0) { |
95 | return; |
96 | } |
97 | |
98 | $tasksParams = $this->prepareTasksParams($event, $webhookConfigIds); |
99 | $this->createWebhookTasks($tasksParams); |
100 | } |
101 | |
102 | /** |
103 | * @param Event $event |
104 | * @return bool |
105 | */ |
106 | private function checkEventIsSupported(Event $event) |
107 | { |
108 | $eventName = $event->getName(); |
109 | |
110 | if (!$this->isEventRegistered($eventName)) { |
111 | $this->logError("Event '$eventName' is not supported by " . self::class); |
112 | return false; |
113 | } |
114 | |
115 | if (!($event instanceof WebhookSerializableEventInterface)) { |
116 | $this->logError(sprintf( |
117 | 'Event "%s" passed to "%s" is not implementing "%s"', |
118 | $eventName, |
119 | self::class, |
120 | WebhookSerializableEventInterface::class |
121 | )); |
122 | return false; |
123 | } |
124 | |
125 | return true; |
126 | } |
127 | |
128 | /** |
129 | * @param WebhookSerializableEventInterface $event |
130 | * @param string[] $webhookConfigIds |
131 | * @return WebhookTaskParams[] |
132 | * @throws WebhookConfigMissingException |
133 | */ |
134 | private function prepareTasksParams(WebhookSerializableEventInterface $event, $webhookConfigIds) |
135 | { |
136 | try { |
137 | $eventData = $event->serializeForWebhook(); |
138 | } catch (\Exception $exception) { |
139 | $this->logError(sprintf( |
140 | 'Error during "%s" event serialization for webhook. %s', |
141 | $event->getName(), |
142 | $exception->getMessage() |
143 | )); |
144 | return []; |
145 | } |
146 | |
147 | $triggeredTimestamp = time(); |
148 | $eventId = $this->generateEventId($event->getName()); |
149 | |
150 | $result = []; |
151 | |
152 | foreach ($webhookConfigIds as $webhookConfigId) { |
153 | if (($webhookConfig = $this->getWebhookRegistry()->getWebhookConfig($webhookConfigId)) === null) { |
154 | throw new WebhookConfigMissingException( |
155 | sprintf('Webhook config for id %s not found', $webhookConfigId) |
156 | ); |
157 | } |
158 | |
159 | if (($event instanceof WebhookConditionalEventInterface) && !$event->isSatisfiedBy($webhookConfig)) { |
160 | continue; |
161 | } |
162 | |
163 | $result[] = new WebhookTaskParams([ |
164 | WebhookTaskParams::EVENT_NAME => $event->getWebhookEventName(), |
165 | WebhookTaskParams::EVENT_ID => $eventId, |
166 | WebhookTaskParams::TRIGGERED_TIMESTAMP => $triggeredTimestamp, |
167 | WebhookTaskParams::EVENT_DATA => $this->extendPayload($webhookConfig, $eventData), |
168 | WebhookTaskParams::WEBHOOK_CONFIG_ID => $webhookConfigId, |
169 | WebhookTaskParams::RETRY_COUNT => 1, |
170 | WebhookTaskParams::RETRY_MAX => $webhookConfig->getMaxRetries(), |
171 | WebhookTaskParams::RESPONSE_VALIDATION => $webhookConfig->getResponseValidationEnable(), |
172 | ]); |
173 | } |
174 | |
175 | return $result; |
176 | } |
177 | |
178 | /** |
179 | * @param WebhookTaskParams[] $tasksParams |
180 | */ |
181 | private function createWebhookTasks($tasksParams) |
182 | { |
183 | foreach ($tasksParams as $taskParams) { |
184 | try { |
185 | $this->getWebhookTaskService()->createTask($taskParams); |
186 | } catch (\Exception $exception) { |
187 | $this->logError( |
188 | sprintf( |
189 | "Can't create webhook task for %s. %s", |
190 | $taskParams[WebhookTaskParams::EVENT_ID], |
191 | $exception->getMessage() |
192 | ), |
193 | (array) $taskParams |
194 | ); |
195 | continue; |
196 | } |
197 | } |
198 | } |
199 | |
200 | /** |
201 | * @param string $eventName |
202 | * @return string |
203 | */ |
204 | private function generateEventId($eventName) |
205 | { |
206 | return md5( |
207 | microtime() . |
208 | mt_rand() . |
209 | $eventName . |
210 | gethostname() |
211 | ); |
212 | } |
213 | |
214 | /** |
215 | * @return array|callable |
216 | */ |
217 | private function getEventHandlerCallback() |
218 | { |
219 | return [self::SERVICE_ID, 'handleEvent']; |
220 | } |
221 | |
222 | private function getWebhookRegistry(): WebhookRegistryInterface |
223 | { |
224 | /** @noinspection PhpIncompatibleReturnTypeInspection */ |
225 | return $this->getServiceLocator()->getContainer()->get(WebhookRegistryInterface::class); |
226 | } |
227 | |
228 | /** |
229 | * @return WebhookTaskServiceInterface |
230 | */ |
231 | private function getWebhookTaskService() |
232 | { |
233 | /** @noinspection PhpIncompatibleReturnTypeInspection */ |
234 | return $this->getServiceLocator()->get(WebhookTaskServiceInterface::SERVICE_ID); |
235 | } |
236 | |
237 | /** |
238 | * @return EventManager |
239 | */ |
240 | private function getEventManager() |
241 | { |
242 | /** @noinspection PhpIncompatibleReturnTypeInspection */ |
243 | return $this->getServiceLocator()->get(EventManager::SERVICE_ID); |
244 | } |
245 | |
246 | private function extendPayload(WebhookInterface $webhookConfig, array $eventData): array |
247 | { |
248 | if (empty($webhookConfig->getExtraPayload())) { |
249 | return $eventData; |
250 | } |
251 | |
252 | return array_merge($webhookConfig->getExtraPayload(), $eventData); |
253 | } |
254 | } |