Wiki source code of Local Observation Module
Last modified by Clemens Robbenhaar on 2015/06/19 18:07
Show last authors
author | version | line-number | content |
---|---|---|---|
1 | {{box cssClass="floatinginfobox" title="**Contents**"}} | ||
2 | {{toc/}} | ||
3 | {{/box}} | ||
4 | |||
5 | Provides the ability to listen to internal XWiki events such as events when document change, when an action is executed, etc. | ||
6 | |||
7 | {{warning}} | ||
8 | TODO: Add architecture diagram here | ||
9 | {{/warning}} | ||
10 | |||
11 | = Features = | ||
12 | |||
13 | * Supports any kind of event, including custom events | ||
14 | * API to listen to a particular event type | ||
15 | * API to listen to any event type | ||
16 | * API to register event listeners | ||
17 | ** Automatically as component | ||
18 | ** Programmatically | ||
19 | * API to unregister event listeners programmatically | ||
20 | * API to send events to all listeners | ||
21 | |||
22 | = Examples = | ||
23 | |||
24 | See this [[Writing an Event Listener Tutorial>>platform:DevGuide.WritingEventListenerTutorial]]. | ||
25 | |||
26 | {{toc scope="local"/}} | ||
27 | |||
28 | == Consuming and producing events == | ||
29 | |||
30 | Observe **document save events** in the wiki, and fire a custom **user created** event when the saved document is a user document. | ||
31 | |||
32 | The first class is the event listener, the second defines the custom event itself. | ||
33 | |||
34 | Since 5.4 an ##org.xwiki.observation.AbstractEventListener## is provided. | ||
35 | |||
36 | {{code language="java"}} | ||
37 | package com.example; | ||
38 | |||
39 | import java.util.Arrays; | ||
40 | import java.util.HashMap; | ||
41 | import java.util.List; | ||
42 | import java.util.Map; | ||
43 | |||
44 | import javax.inject.Inject; | ||
45 | import javax.inject.Named; | ||
46 | |||
47 | import org.xwiki.bridge.event.DocumentCreatedEvent; | ||
48 | import org.xwiki.bridge.event.DocumentDeletedEvent; | ||
49 | import org.xwiki.bridge.event.DocumentUpdatedEvent; | ||
50 | import org.xwiki.component.annotation.Component; | ||
51 | import org.xwiki.component.manager.ComponentLookupException; | ||
52 | import org.xwiki.component.manager.ComponentManager; | ||
53 | import org.xwiki.model.reference.DocumentReference; | ||
54 | import org.xwiki.observation.EventListener; | ||
55 | import org.xwiki.observation.ObservationManager; | ||
56 | import org.xwiki.observation.event.Event; | ||
57 | |||
58 | import com.xpn.xwiki.doc.XWikiDocument; | ||
59 | |||
60 | import com.example.event.UserCreationEvent; | ||
61 | |||
62 | @Component | ||
63 | @Named("usercreation") | ||
64 | public class UserCreationEventListener extends AbstractEventListener | ||
65 | { | ||
66 | @Inject | ||
67 | private ComponentManager componentManager; | ||
68 | |||
69 | /** | ||
70 | * The observation manager that will be use to fire user creation events. Note: We can't have the OM as a | ||
71 | * requirement, since it would create an infinite initialization loop, causing a stack overflow error (this event | ||
72 | * listener would require an initialized OM and the OM requires a list of initialized event listeners) | ||
73 | */ | ||
74 | private ObservationManager observationManager; | ||
75 | |||
76 | public UserCreationEventListener() | ||
77 | { | ||
78 | super("usercreation", new DocumentCreatedEvent()); | ||
79 | } | ||
80 | |||
81 | /** | ||
82 | * {@inheritDoc} | ||
83 | */ | ||
84 | public void onEvent(Event event, Object source, Object data) | ||
85 | { | ||
86 | XWikiDocument document = (XWikiDocument) source; | ||
87 | String wikiName = document.getDocumentReference().getWikiReference().getName(); | ||
88 | DocumentReference userClass = new DocumentReference(wikiName, "XWiki", "XWikiUsers"); | ||
89 | |||
90 | if (document.getXObject(userClass) != null) { | ||
91 | // Create a map to hold our new event data | ||
92 | Map<String,String> userData = new HashMap<String,String>(); | ||
93 | userData.put("firstName", document.getXObject(userClass).getStringValue("firstName")); | ||
94 | userData.put("lastName", document.getXObject(userClass).getStringValue("lastName")); | ||
95 | userData.put("email", document.getXObject(userClass).getStringValue("email")); | ||
96 | // Fire the user created event | ||
97 | UserCreatedEvent newEvent = new UserCreationEvent(); | ||
98 | getObservationManager().notify(newEvent, source, userData); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | private ObservationManager getObservationManager() | ||
103 | { | ||
104 | if (this.observationManager == null) { | ||
105 | try { | ||
106 | this.observationManager = componentManager.getInstance(ObservationManager.class); | ||
107 | } catch (ComponentLookupException e) { | ||
108 | throw new RuntimeException("Cound not retrieve an Observation Manager against the component manager"); | ||
109 | } | ||
110 | } | ||
111 | return this.observationManager; | ||
112 | } | ||
113 | |||
114 | } | ||
115 | {{/code}} | ||
116 | |||
117 | Here is an example before 5.4: | ||
118 | |||
119 | {{code language="java"}} | ||
120 | package com.example; | ||
121 | |||
122 | import java.util.Arrays; | ||
123 | import java.util.HashMap; | ||
124 | import java.util.List; | ||
125 | import java.util.Map; | ||
126 | |||
127 | import javax.inject.Inject; | ||
128 | import javax.inject.Named; | ||
129 | |||
130 | import org.xwiki.bridge.event.DocumentCreatedEvent; | ||
131 | import org.xwiki.bridge.event.DocumentDeletedEvent; | ||
132 | import org.xwiki.bridge.event.DocumentUpdatedEvent; | ||
133 | import org.xwiki.component.annotation.Component; | ||
134 | import org.xwiki.component.manager.ComponentLookupException; | ||
135 | import org.xwiki.component.manager.ComponentManager; | ||
136 | import org.xwiki.model.reference.DocumentReference; | ||
137 | import org.xwiki.observation.EventListener; | ||
138 | import org.xwiki.observation.ObservationManager; | ||
139 | import org.xwiki.observation.event.Event; | ||
140 | |||
141 | import com.xpn.xwiki.doc.XWikiDocument; | ||
142 | |||
143 | import com.example.event.UserCreationEvent; | ||
144 | |||
145 | @Component | ||
146 | @Named("usercreation") | ||
147 | public class UserCreationEventListener implements EventListener | ||
148 | { | ||
149 | @Inject | ||
150 | private ComponentManager componentManager; | ||
151 | |||
152 | /** | ||
153 | * The observation manager that will be use to fire user creation events. Note: We can't have the OM as a | ||
154 | * requirement, since it would create an infinite initialization loop, causing a stack overflow error (this event | ||
155 | * listener would require an initialized OM and the OM requires a list of initialized event listeners) | ||
156 | */ | ||
157 | private ObservationManager observationManager; | ||
158 | |||
159 | /** | ||
160 | * {@inheritDoc} | ||
161 | */ | ||
162 | public List<Event> getEvents() | ||
163 | { | ||
164 | return Arrays.<Event>asList(new DocumentCreatedEvent()); | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * {@inheritDoc} | ||
169 | */ | ||
170 | public String getName() | ||
171 | { | ||
172 | return "usercreation"; | ||
173 | } | ||
174 | |||
175 | /** | ||
176 | * {@inheritDoc} | ||
177 | */ | ||
178 | public void onEvent(Event event, Object source, Object data) | ||
179 | { | ||
180 | XWikiDocument document = (XWikiDocument) source; | ||
181 | String wikiName = document.getDocumentReference().getWikiReference().getName(); | ||
182 | DocumentReference userClass = new DocumentReference(wikiName, "XWiki", "XWikiUsers"); | ||
183 | |||
184 | if (document.getXObject(userClass) != null) { | ||
185 | // Create a map to hold our new event data | ||
186 | Map<String,String> userData = new HashMap<String,String>(); | ||
187 | userData.put("firstName", document.getXObject(userClass).getStringValue("firstName")); | ||
188 | userData.put("lastName", document.getXObject(userClass).getStringValue("lastName")); | ||
189 | userData.put("email", document.getXObject(userClass).getStringValue("email")); | ||
190 | // Fire the user created event | ||
191 | UserCreatedEvent newEvent = new UserCreationEvent(); | ||
192 | getObservationManager().notify(newEvent, source, userData); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | private ObservationManager getObservationManager() | ||
197 | { | ||
198 | if (this.observationManager == null) { | ||
199 | try { | ||
200 | this.observationManager = componentManager.getInstance(ObservationManager.class); | ||
201 | |||
202 | } catch (ComponentLookupException e) { | ||
203 | throw new RuntimeException("Cound not retrieve an Observation Manager against the component manager"); | ||
204 | } | ||
205 | } | ||
206 | return this.observationManager; | ||
207 | } | ||
208 | |||
209 | } | ||
210 | {{/code}} | ||
211 | |||
212 | Definition of the custom event: | ||
213 | |||
214 | {{code language="java"}} | ||
215 | package com.example.event; | ||
216 | |||
217 | import org.xwiki.bridge.event.AbstractDocumentEvent; | ||
218 | import org.xwiki.observation.event.Event; | ||
219 | import org.xwiki.observation.event.filter.EventFilter; | ||
220 | |||
221 | /** | ||
222 | * {@link Event} generated when a new user is created. | ||
223 | */ | ||
224 | public class UserCreationEvent extends AbstractDocumentEvent | ||
225 | { | ||
226 | /** | ||
227 | * The version identifier for this Serializable class. Increment only if the <i>serialized</i> form of the class | ||
228 | * changes. | ||
229 | */ | ||
230 | private static final long serialVersionUID = 1L; | ||
231 | |||
232 | /** | ||
233 | * Constructor initializing the event filter with an | ||
234 | * {@link org.xwiki.observation.event.filter.AlwaysMatchingEventFilter}, meaning that this event will match any | ||
235 | * other document update event. | ||
236 | */ | ||
237 | public UserCreatedEvent() | ||
238 | { | ||
239 | super(); | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * Constructor initializing the event filter with a {@link org.xwiki.observation.event.filter.FixedNameEventFilter}, | ||
244 | * meaning that this event will match only update events affecting the document matching the passed document name. | ||
245 | * | ||
246 | * @param documentName the name of the updated document to match | ||
247 | */ | ||
248 | public UserCreatedEvent(String documentName) | ||
249 | { | ||
250 | super(documentName); | ||
251 | } | ||
252 | |||
253 | /** | ||
254 | * Constructor using a custom {@link EventFilter}. | ||
255 | * | ||
256 | * @param eventFilter the filter to use for matching events | ||
257 | */ | ||
258 | public UserCreatedEvent(EventFilter eventFilter) | ||
259 | { | ||
260 | super(eventFilter); | ||
261 | } | ||
262 | } | ||
263 | {{/code}} | ||
264 | |||
265 | == Writing an Event Listener in Groovy in a Wiki page == | ||
266 | |||
267 | See the [[tutorial>>platform:DevGuide.WritingEventListenerTutorial||anchor="HAddingcontenttopagesonsave"]]. | ||
268 | |||
269 | == Writing an Event Listener in Velocity in a Wiki page == | ||
270 | |||
271 | The [[documentation of the wiki components>>Extension.WikiComponent Module]] contains, as an example, the creation of an Event Listener. | ||
272 | |||
273 | == Fold events == | ||
274 | |||
275 | An event tagged as "Fold" can be sent by a task that generates some events during its execution. Then, these generated events can be seen as children of the main task. In addition, the [[Activity Stream>>Extension.Activity Stream Plugin]] will not record these child events. | ||
276 | |||
277 | This is an example of a custom fold event: | ||
278 | |||
279 | {{code language="java"}} | ||
280 | package org.xwiki.bridge.event; | ||
281 | |||
282 | import org.xwiki.observation.event.BeginFoldEvent; | ||
283 | |||
284 | public class MyTaskBeginEvent extends AbstractDocumentEvent implements BeginFoldEvent | ||
285 | { | ||
286 | /** | ||
287 | * The version identifier for this Serializable class. Increment only if the <i>serialized</i> form of the class | ||
288 | * changes. | ||
289 | */ | ||
290 | private static final long serialVersionUID = 1L; | ||
291 | |||
292 | /** | ||
293 | * Constructor initializing the event filter with an | ||
294 | * {@link org.xwiki.observation.event.filter.AlwaysMatchingEventFilter}, meaning that this event will match any | ||
295 | * other document update event. | ||
296 | */ | ||
297 | public MyTaskBeginEvent() | ||
298 | { | ||
299 | super(); | ||
300 | } | ||
301 | |||
302 | /** | ||
303 | * Constructor initializing the event filter with a {@link org.xwiki.observation.event.filter.FixedNameEventFilter}, | ||
304 | * meaning that this event will match only update events affecting the document matching the passed document name. | ||
305 | * | ||
306 | * @param documentName the name of the updated document to match | ||
307 | */ | ||
308 | public MyTaskBeginEvent(String documentName) | ||
309 | { | ||
310 | super(documentName); | ||
311 | } | ||
312 | |||
313 | /** | ||
314 | * Constructor using a custom {@link EventFilter}. | ||
315 | * | ||
316 | * @param eventFilter the filter to use for matching events | ||
317 | */ | ||
318 | public MyTaskBeginEvent(EventFilter eventFilter) | ||
319 | { | ||
320 | super(eventFilter); | ||
321 | } | ||
322 | } | ||
323 | {{/code}} | ||
324 | |||
325 | Then you need to define the corresponding {{code}}EndFoldEvent{{/code}} : | ||
326 | |||
327 | {{code language="java"}} | ||
328 | package org.xwiki.bridge.event; | ||
329 | |||
330 | import org.xwiki.observation.event.EndFoldEvent; | ||
331 | |||
332 | public class MyTaskEndEvent implements EndFoldEvent | ||
333 | { | ||
334 | /** | ||
335 | * The version identifier for this Serializable class. Increment only if the <i>serialized</i> form of the class | ||
336 | * changes. | ||
337 | */ | ||
338 | private static final long serialVersionUID = 1L; | ||
339 | |||
340 | /** | ||
341 | * Constructor initializing the event filter with an | ||
342 | * {@link org.xwiki.observation.event.filter.AlwaysMatchingEventFilter}, meaning that this event will match any | ||
343 | * other document update event. | ||
344 | */ | ||
345 | public MyTaskEndEvent() | ||
346 | { | ||
347 | super(); | ||
348 | } | ||
349 | |||
350 | /** | ||
351 | * Constructor initializing the event filter with a {@link org.xwiki.observation.event.filter.FixedNameEventFilter}, | ||
352 | * meaning that this event will match only update events affecting the document matching the passed document name. | ||
353 | * | ||
354 | * @param documentName the name of the updated document to match | ||
355 | */ | ||
356 | public MyTaskEndEvent(String documentName) | ||
357 | { | ||
358 | super(documentName); | ||
359 | } | ||
360 | |||
361 | /** | ||
362 | * Constructor using a custom {@link EventFilter}. | ||
363 | * | ||
364 | * @param eventFilter the filter to use for matching events | ||
365 | */ | ||
366 | public MyTaskEndEvent(EventFilter eventFilter) | ||
367 | { | ||
368 | super(eventFilter); | ||
369 | } | ||
370 | } | ||
371 | {{/code}} |