Wiki source code of Writing an Event Listener
Version 3.1 by Vincent Massol on 2014/05/29
Show last authors
author | version | line-number | content |
---|---|---|---|
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 | {{code language="xml"}} | ||
150 | ... | ||
151 | <dependency> | ||
152 | <groupId>org.xwiki.platform</groupId> | ||
153 | <artifactId>xwiki-platform-oldcore</artifactId> | ||
154 | <version>5.4.5</version> | ||
155 | </dependency> | ||
156 | <dependency> | ||
157 | <groupId>org.xwiki.platform</groupId> | ||
158 | <artifactId>xwiki-platform-mailsender</artifactId> | ||
159 | <version>5.4.5</version> | ||
160 | </dependency> | ||
161 | ... | ||
162 | {{/code}} | ||
163 | ))) | ||
164 | * Create a ##CommentListener## class in package ##org.xwiki.contrib.internal## (pick the package name you wish!) with the following content:((( | ||
165 | {{code language="java"}} | ||
166 | package org.xwiki.contrib.internal; | ||
167 | |||
168 | import java.util.Arrays; | ||
169 | import java.util.List; | ||
170 | |||
171 | import javax.inject.Inject; | ||
172 | import javax.inject.Named; | ||
173 | import javax.inject.Singleton; | ||
174 | |||
175 | import org.slf4j.Logger; | ||
176 | import org.xwiki.component.annotation.Component; | ||
177 | import org.xwiki.model.EntityType; | ||
178 | import org.xwiki.model.reference.EntityReference; | ||
179 | import org.xwiki.observation.EventListener; | ||
180 | import org.xwiki.observation.event.Event; | ||
181 | |||
182 | import com.xpn.xwiki.XWikiContext; | ||
183 | import com.xpn.xwiki.doc.XWikiDocument; | ||
184 | import com.xpn.xwiki.internal.event.XObjectAddedEvent; | ||
185 | import com.xpn.xwiki.objects.BaseObject; | ||
186 | import com.xpn.xwiki.plugin.mailsender.MailSenderPluginApi; | ||
187 | |||
188 | @Component | ||
189 | @Named("CommentEventListener") | ||
190 | @Singleton | ||
191 | public class CommentEventListener implements EventListener | ||
192 | { | ||
193 | @Inject | ||
194 | private Logger logger; | ||
195 | |||
196 | private EntityReference commentClassReference = new EntityReference("XWikiComments", EntityType.DOCUMENT, | ||
197 | new EntityReference("XWiki", EntityType.SPACE)); | ||
198 | |||
199 | @Override public String getName() | ||
200 | { | ||
201 | return "CommentEventListener"; | ||
202 | } | ||
203 | |||
204 | @Override public List<Event> getEvents() | ||
205 | { | ||
206 | return Arrays.<Event>asList(new XObjectAddedEvent()); | ||
207 | } | ||
208 | |||
209 | @Override public void onEvent(Event event, Object source, Object data) | ||
210 | { | ||
211 | XWikiDocument document = (XWikiDocument) source; | ||
212 | BaseObject commentObject = document.getXObject(this.commentClassReference); | ||
213 | if (commentObject != null) { | ||
214 | try { | ||
215 | // Get comment | ||
216 | String comment = commentObject.getStringValue("comment"); | ||
217 | // Send email | ||
218 | XWikiContext xcontext = (XWikiContext) data; | ||
219 | MailSenderPluginApi mailSender = (MailSenderPluginApi) xcontext.getWiki().getPluginApi("mailsender", xcontext); | ||
220 | mailSender.sendTextMessage("XWiki <xwiki@no-reply>", "[email protected]", | ||
221 | "[XWiki] Comment added to " + document.toString(), comment); | ||
222 | } catch (Exception e) { | ||
223 | this.logger.error("Failure in comment listener", e); | ||
224 | } | ||
225 | |||
226 | } | ||
227 | } | ||
228 | } | ||
229 | {{/code}} | ||
230 | ))) | ||
231 | * Don't forget to register your component in the ##META-INF/components.txt## file in your project:((( | ||
232 | {{code}} | ||
233 | org.xwiki.contrib.internal.CommentEventListener | ||
234 | {{/code}} | ||
235 | ))) | ||
236 | * 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. | ||
237 | |||
238 | 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! | ||
239 | |||
240 | {{warning}} | ||
241 | 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 :) | ||
242 | {{/warning}} | ||
243 | |||
244 | = Old Notifications Tutorial = | ||
245 | |||
246 | If you're using an old version of XWiki (prior to 2.0) you should check this [[old Notifications Tutorial>>GroovyNotificationTutorial]]. |