Wiki source code of Writing an Event Listener

Version 1.2 by Vincent Massol on 2014/05/29

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}{{toc/}}{{/box}}
2
3 XWiki supports notifications (a.k.a Observation Events) and it's possible to do some action when a document is modified, when a document's objects are modified, when a wiki is created, etc. See the full documentation in the [[Observation reference documentation>>extensions:Extension.Observation Module]].
4
5 There are several ways to write an Event Listener to react to some events:
6 * In Java, as an [[XWiki Component>>extensions:Extension.Component Module]]
7 * In a wiki page, as a [[Wiki Component>>extensions:Extension.WikiComponent Module]]
8 * In a wiki page, using Groovy, by writing an XWiki Component and manually registering it against the Component Manager
9
10 This tutorial will demonstrate all techniques on several various examples.
11
12 = Adding content to pages on save =
13
14 The goal is to listen to ##DocumentCreatingEvent## and ##DocumentUpdatingEvent## events (note that these events are fired **before** the page is saved and thus our code doesn't need to perform the save itself since this is done automatically).
15
16 Let's implement this example using a [[Wiki Component>>extensions:Extension.WikiComponent Module]].
17
18 {{warning}}
19 This example uses the [[~~{~~{groovy}} macro>>extensions:Extension.Groovy Macro]] and thus requires having Programming Rights. If you're in a wiki in a farm this means having Programming Rights on the main wiki.
20 {{/warning}}
21
22 Follow these steps:
23 * Create a page, for example ##EventListeners.DocumentSaveListener##
24 * Add a ##XWiki.ComponentClass## XObject in it
25 ** Component Role Type: ##org.xwiki.observation.EventListener##
26 ** Component Role Hint: ##mytest## (you can use whatever name you want, it's the technical id of your listener that you won't be using in this tutorial)
27 ** Component Scope: ##Current Wiki## this means this listener will be active only in the current wiki). You'll need Admin rights for ##Current Wiki## and you'd need Programming Rights for ##Global## which would make the listener available in all the farm (i.e. all the wikis). For ##Current User## you don't need any special permission but the listener will only be active for your user.
28 * Add a ##XWiki.ComponentMethodClass## XObject for implementing the ##getEvents()## method:
29 ** Method Name: ##getEvents##
30 ** Method body code:(((
31 {{code}}
32 {{groovy}}
33 import org.xwiki.bridge.event.*
34
35 xcontext.method.output.value = [new DocumentCreatingEvent(), new DocumentUpdatingEvent()]
36 {{/groovy}}
37 {{/code}}
38 )))
39 * Add another ##XWiki.ComponentMethodClass## XObject for implementing the ##getName()## method:
40 ** Method Name: ##getName##
41 ** Method body code:(((
42 {{code}}
43 {{groovy}}
44 xcontext.method.output.value = "mytest"
45 {{/groovy}}
46 {{/code}}
47
48 Note that ##mytest## should be the same technical id that you've used above.
49 )))
50 * Add another ##XWiki.ComponentMethodClass## XObject for implementing the ##onEvent()## method:
51 ** Method Name: ##onEvent##
52 ** Method body code:(((
53 {{code}}
54 {{groovy}}
55 def docSource = xcontext.method.input.get(1)
56 if (docSource.space != "EventListeners") {
57 docSource.setContent(docSource.content + "\n\nSome extra content...")
58 }
59 {{/groovy}}{{/code}}
60 )))
61 * Save!
62
63 When you save the ##EventListeners.DocumentSaveListener## page, the component you've defined (your Event Listener) is automatically registered and active.
64
65 You can verify it works by creating a new page or editing an existing page and you should see the text ##Some extra content...## added at the bottom of your pages when you save them.
66
67 == Log when a document is modified ==
68
69 In this example we want to log all document changes by adding a line in a page named ##Main.Logs##.
70
71 We'll implement this use case using Groovy in a wiki page.
72
73 {{warning}}
74 This example uses the [[~~{~~{groovy}} macro>>extensions:Extension.Groovy Macro]] and thus requires having Programming Rights. If you're in a wiki in a farm this means having Programming Rights on the main wiki.
75 {{/warning}}
76
77 {{code language="java"}}
78 {{groovy}}
79 import org.xwiki.observation.*
80 import org.xwiki.observation.event.*
81 import org.xwiki.bridge.event.*
82 import com.xpn.xwiki.web.*
83 import com.xpn.xwiki.*
84
85 class LoggingEventListener implements EventListener
86 {
87 def xwiki
88 def context
89
90 LoggingEventListener(xwiki, context)
91 {
92 this.xwiki = xwiki
93 this.context = context
94 }
95
96 String getName()
97 {
98 // The unique name of this event listener
99 return "logging"
100 }
101
102 List<Event> getEvents()
103 {
104 // The list of events this listener listens to
105 return [new DocumentUpdatedEvent()]
106 }
107
108 // Called by the Observation Manager when an event matches the list of events returned
109 // by getEvents()
110 void onEvent(Event event, Object source, Object data)
111 {
112 // Prevent infinite recursion since in this example we log to wiki page which
113 // triggers a document change... :)
114 if (source.fullName != "Main.Logs") {
115 def document = xwiki.getDocument("Main.Logs")
116 document.setContent("${document.getContent()}\n* ${source.fullName} has been modified!")
117 document.save("Logging event", true)
118 }
119 }
120 }
121
122 // Register against the Observation Manager
123 def observation = Utils.getComponent(ObservationManager.class)
124 observation.removeListener("logging")
125 def listener = new LoggingEventListener(xwiki, xcontext)
126 observation.addListener(listener)
127
128 {{/groovy}}
129 {{/code}}
130
131 You can add other events to the ##getEvents## returned list if you wish to listener to other events, for example:
132
133 {{code}}
134 ...
135 return Arrays.asList(new DocumentUpdateEvent(), new DocumentSaveEvent(), new DocumentDeleteEvent())
136 ...
137 {{/code}}
138
139 And here's the [[full code with nice pretty-printing and a register button>>attach:groovynotifier.txt]].
140
141 == Send a mail whenever a comment is added ==
142
143 Let's implement this use case in Java this time!
144
145 Note: We're going to listen to ##CommentAddedEvent## but in general if you wish to be notified when an XObject has been updated you should listen to ##XObjectUpdatedEvent## (or ##XObjectPropertyUpdatedEvent## when an XProperty has been updated).
146
147 * Let's use the XWiki Maven Archetype for creating a skeleton project, by following [[this tutorial>>WritingComponents]].
148 * Make sure you have the following 2 dependencies in your ##pom.xml## file (note that I've used version 5.4.5 but you can use the version you wish):(((
149 ...
150 <dependency>
151 <groupId>org.xwiki.platform</groupId>
152 <artifactId>xwiki-platform-oldcore</artifactId>
153 <version>5.4.5</version>
154 </dependency>
155 <dependency>
156 <groupId>org.xwiki.platform</groupId>
157 <artifactId>xwiki-platform-mailsender</artifactId>
158 <version>5.4.5</version>
159 </dependency>
160 ...
161 )))
162 * Create a ##CommentListener## class in package ##org.xwiki.contrib.internal## (pick the package name you wish!) with the following content:(((
163 {{code language="java"}}
164 package org.xwiki.contrib.internal;
165
166 import java.util.Arrays;
167 import java.util.List;
168
169 import javax.inject.Inject;
170 import javax.inject.Named;
171 import javax.inject.Singleton;
172
173 import org.slf4j.Logger;
174 import org.xwiki.component.annotation.Component;
175 import org.xwiki.model.EntityType;
176 import org.xwiki.model.reference.EntityReference;
177 import org.xwiki.observation.EventListener;
178 import org.xwiki.observation.event.Event;
179
180 import com.xpn.xwiki.XWikiContext;
181 import com.xpn.xwiki.doc.XWikiDocument;
182 import com.xpn.xwiki.internal.event.XObjectAddedEvent;
183 import com.xpn.xwiki.objects.BaseObject;
184 import com.xpn.xwiki.plugin.mailsender.MailSenderPluginApi;
185
186 @Component
187 @Named("CommentEventListener")
188 @Singleton
189 public class CommentEventListener implements EventListener
190 {
191 @Inject
192 private Logger logger;
193
194 private EntityReference commentClassReference = new EntityReference("XWikiComments", EntityType.DOCUMENT,
195 new EntityReference("XWiki", EntityType.SPACE));
196
197 @Override public String getName()
198 {
199 return "CommentEventListener";
200 }
201
202 @Override public List<Event> getEvents()
203 {
204 return Arrays.<Event>asList(new XObjectAddedEvent());
205 }
206
207 @Override public void onEvent(Event event, Object source, Object data)
208 {
209 XWikiDocument document = (XWikiDocument) source;
210 BaseObject commentObject = document.getXObject(this.commentClassReference);
211 if (commentObject != null) {
212 try {
213 // Get comment
214 String comment = commentObject.getStringValue("comment");
215 // Send email
216 XWikiContext xcontext = (XWikiContext) data;
217 MailSenderPluginApi mailSender = (MailSenderPluginApi) xcontext.getWiki().getPluginApi("mailsender", xcontext);
218 mailSender.sendTextMessage("XWiki <xwiki@no-reply>", "[email protected]",
219 "[XWiki] Comment added to " + document.toString(), comment);
220 } catch (Exception e) {
221 this.logger.error("Failure in comment listener", e);
222 }
223
224 }
225 }
226 }
227 {{/code}}
228 )))
229 * Don't forget to register your component in the ##META-INF/components.txt## file in your project:(((
230 {{code}}
231 org.xwiki.contrib.internal.CommentEventListener
232 {{/code}}
233 )))
234 * Build the project with maven: ##mvn clean install## and copy the JAR generated in the ##target## directory in your XWiki's ##WEB-INF/lib## directory, and restart XWiki.
235
236 Before trying it, go to your wiki's administration page and make sure you've configured the Email properties. Try it by adding a new comment on a page. You should receive an email!
237
238 {{warning}}
239 This implementation is not very good since the mail is sent synchronously on page saves, and sending an email takes some time. It would be better to send it asynchronously by spawning a separate thread so that the save can return quickly. We leave this as an exercise for our dear readers :)
240 {{/warning}}

Get Connected