Wiki source code of Creating Plugins

Version 4.8 by Vincent Massol on 2016/02/10

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 {{warning}}
6 Plugins are the old way of writing XWiki extensions. The new way is to [[write a Component>>platform:DevGuide.WritingComponents]].
7 {{/warning}}
8
9 Plugins are quite handy when you want to interact with third-party code from the Velocity context. Check the [[Extensions wiki>>extensions:Main.WebHome]] for a list of existing plugins.
10
11 Here are the steps to develop a "Hello World" plugin and more.
12
13 = The plugin architecture =
14
15 Basically, a plugin is composed of two parts:
16
17 * The **plugin** itself: it must implement the [[XWikiPluginInterface>>https://fisheye2.atlassian.com/browse/xwiki/xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/plugin/XWikiPluginInterface.java]] interface. For simplicity you can also extend the [[XWikiDefaultPlugin>>https://fisheye2.atlassian.com/browse/xwiki/xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/plugin/XWikiDefaultPlugin.java]] class which is an adapter to the XWikiPluginInterface. The plugin contains the core functions of your plugin. They will not be accessible from scripting (without programming rights).
18 * Its **API**: it should extend the [[Api>>https://fisheye2.atlassian.com/browse/xwiki/xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/api/Api.java]] class. Will contain all the public methods, accessible from scripting.
19
20 Although you can write the functionality inside the API, this is not recommended; the plugin functionality is written in the //hidden// part ("hidden" as in "not publicly accessible"), and the API can filter the access to privileged users, beautify the method names or parameter list, etc., or simply forward the call to the hidden part.
21
22 == Plugin lifecycle ==
23
24 When the XWiki engine is initialized, the Plugin Manager calls the class constructor for all the enabled plugins (classes implementing the com.xpn.xwiki.plugin.XWikiPluginInterface). For each plugin the class constructor is called only once, and the plugin manager calls the init(XWikiContext) method of the plugin. Each time a plugin is referenced by a Velocity script, for example, when you call a method served by the plugin API:
25
26 {{code}}
27 #set($helloWorldText = "$xwiki.helloworld.hello()")
28 {{/code}}
29
30 or when you ask the XWiki instance for the plugin API object :
31
32 {{code}}
33 #set($pluginObject = $xwiki.getPlugin("helloworld")
34
35 #* the name given as argument of getPlugin() should be
36 the one returned by the getName() method of the Plugin class.
37 *#
38 {{/code}}
39
40 XWiki calls the getPluginApi() method for the plugin's instance, which itself creates an instance of the class com.xpn.xwiki.plugin.PluginApi. This is why you should not store things in fields of the class extending PluginApi in your plugin, since the usual behavior for the getPluginApi() method is to create a new instance of the PluginApi class every time Velocity needs to access the API for your plugin. This is not true if you store the returned plugin API in a variable, for example:
41
42 {{code}}
43 #set($myPluginApi = $xwiki.helloworld)
44 {{/code}}
45
46 The myPluginApi variable will point to the same object as long as the variable exists. You can declare fields in your plugin class instead, since there is only one instance of this class, whose lifecycle spans over the entire servlet's lifecycle.
47
48 = Write the plugin =
49
50 First of all let's **declare our plugin class**:
51
52 {{code language="java"}}
53 public class HelloWorldPlugin extends XWikiDefaultPlugin {...}
54 {{/code}}
55
56 Then let's **implement the needed constructor**:
57
58 {{code language="java"}}
59 public HelloWorldPlugin(String name, String className, XWikiContext context) {
60 super(name,className,context);
61 }
62 {{/code}}
63
64 **Set a method to get the name of the plugin**. That's how we will call it from Velocity. For example, we will be able to use our plugin with ##$xwiki.helloworld.myMethod()##;
65
66 {{code language="java"}}
67 public String getName() {
68 return "helloworld";
69 }
70 {{/code}}
71
72 **Write a method to get the plugin API**. Don't forget to cast the plugin.
73
74 {{code language="java"}}
75 public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
76 return new HelloWorldPluginApi((HelloWorldPlugin) plugin, context);
77 }
78 {{/code}}
79
80 **Overload the cache flush method** (optional):
81
82 {{code language="java"}}
83 public void flushCache() {}
84 {{/code}}
85
86 Optionally, we can **create a [[log4j>>http://logging.apache.org/log4j/1.2/apidocs/index.html]] instance** for the plugin:
87
88 {{code language="java"}}
89 private static final Log LOG = LogFactory.getLog(HelloWorldPlugin.class);
90 {{/code}}
91
92 This is very useful for debugging. The logger could be invoked from any method like:
93
94 {{code language="java"}}
95 public String getName() {
96 LOG.debug("Entered method getName");
97 return "helloworld";
98 }
99 {{/code}}
100
101 Then, to enable logging at a specific level for your plugin, edit //webapps/xwiki/WEB-INF/classes/log4j.properties// and add, for example:
102
103 {{code}}
104 log4j.com.xpn.xwiki.plugin.helloworld.HelloWorldPlugin=debug
105 {{/code}}
106
107 You'll then be able to follow your plugin's log messages by tailing your ##xwiki.log## file. Note that you'll need to restart the app server for changes to ##log4j.properties## to take effect.
108
109 And finally, **write a method to init the context**:
110
111 {{code language="java"}}
112 public void init(XWikiContext context) {
113 super.init(context);
114 }
115 {{/code}}
116
117 Here is the code you should have now:
118
119 {{code language="java"}}
120 package com.xpn.xwiki.plugin.helloworld;
121
122 import org.apache.commons.logging.Log;
123 import org.apache.commons.logging.LogFactory;
124
125 import com.xpn.xwiki.XWikiContext;
126 import com.xpn.xwiki.api.Api;
127 import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
128 import com.xpn.xwiki.plugin.XWikiPluginInterface;
129
130 public class HelloWorldPlugin extends XWikiDefaultPlugin {
131
132 private static Log LOG = LogFactory.getLog(HelloWorldPlugin.class);
133
134 public HelloWorldPlugin(String name, String className, XWikiContext context) {
135 super(name,className,context);
136 init(context);
137 }
138
139 public String getName() {
140 return "helloworld";
141 }
142
143 public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
144 return new HelloWorldPluginApi((HelloWorldPlugin) plugin, context);
145 }
146
147 public void flushCache() {
148 }
149
150 public void init(XWikiContext context) {
151 super.init(context);
152 }
153 }
154 {{/code}}
155
156 = Write the API =
157
158 Let's write the API class which will contain the methods that can be called from Velocity.
159
160 Firstly, **class declaration**:
161
162 {{code language="java"}}
163 public class HelloWorldPluginApi extends Api {...}
164 {{/code}}
165
166 Then, **plugin field declaration**. It will let our API to call backend methods.
167
168 {{code language="java"}}
169 private HelloWorldPlugin plugin;
170 {{/code}}
171
172 **Required constructor**
173
174 {{code language="java"}}
175 public HelloWorldPluginApi(HelloWorldPlugin plugin, XWikiContext context) {
176 super(context);
177 setPlugin(plugin);
178 }
179 {{/code}}
180
181 Classic **plugin getter and setter**. These methods are not required at all, on the contrary, they should not be defined, unless they are really needed.
182
183 {{code language="java"}}
184 public HelloWorldPlugin getPlugin(){
185 return (hasProgrammingRights() ? plugin : null);
186 // Uncomment for allowing unrestricted access to the plugin
187 // return plugin;
188 }
189
190 public void setPlugin(HelloWorldPlugin plugin) {
191 this.plugin = plugin;
192 }
193 {{/code}}
194
195 Here is the key **API method**. Here is the one that you will call from velocity. You can define any number of them and call your plugin backend from them.
196
197 {{code language="java"}}
198 public String hello() {
199 return "Hello World!";
200 }
201 {{/code}}
202
203 You can also have void methods:
204
205 {{code language="java"}}
206 public void updatePage() {
207 //...
208 }
209 {{/code}}
210
211 Here is the complete API code:
212
213 {{code language="java"}}
214 package com.xpn.xwiki.plugin.helloworld;
215
216 import com.xpn.xwiki.XWikiContext;
217 import com.xpn.xwiki.api.Api;
218
219 public class HelloWorldPluginApi extends Api {
220 private HelloWorldPlugin plugin;
221
222 public HelloWorldPluginApi(HelloWorldPlugin plugin, XWikiContext context) {
223 super(context);
224 setPlugin(plugin);
225 }
226
227 public HelloWorldPlugin getPlugin(){
228 return (hasProgrammingRights() ? plugin : null);
229 // Uncomment for allowing unrestricted access to the plugin
230 // return plugin;
231 }
232
233 public void setPlugin(HelloWorldPlugin plugin) {
234 this.plugin = plugin;
235 }
236
237 public String hello() {
238 return "Hello World!";
239 }
240
241 public void updatePage() {
242 //...
243 }
244 }
245 {{/code}}
246
247 = Integrate the plugin in your XWiki installation =
248
249 First of all you need to **copy your classes to the XWiki servlet installation**. Don't forget to be consistent with your package tree. With a Linux Tomcat installation, you'll need to follow these steps which you should be able to reproduce easily in your favourite operating system:
250
251 {{code}}
252 go to the tomcat installation folder (or whatever container you are using)
253 $ cd myTomcatInstallation
254 go to the xwiki WEB-INF directory
255 $ cd webapps/xwiki/WEB-INF
256 create the classes tree, compliant to the "package" directive that you set in the plugin source files
257 $ mkdir classes/com/xpn/xwiki/plugin/helloworld
258 And then copy the class files to this location
259 $ cp myPluginsFolder/HelloWorldPlugin.class classes/com/xpn/xwiki/plugin/helloworld
260 $ cp myPluginsFolder/HelloWorldPluginAPI.class classes/com/xpn/xwiki/plugin/helloworld
261 {{/code}}
262
263 Alternatively, you can jar up your classes (with the required directory structure) and place the jar in //webapps/xwiki/WEB-INF/lib//. This is a more agreeable way of distributing your plugin.
264
265 Finally you need to **register your plugin** in the ##xwiki.cfg## file located in //WEB-INF//:
266
267 {{code}}
268 xwiki.plugins=com.xpn.xwiki.plugin.calendar.CalendarPlugin,\
269 ...,\
270 com.xpn.xwiki.plugin.helloworld.HelloWorldPlugin
271 {{/code}}
272
273 Don't forget to restart your servlet container after this. XWiki has to re-read the configuration file.
274
275 = Use the plugin =
276
277 Here is the simplest part. Edit a page and write: {{code}}My plugin says: "$xwiki.helloworld.hello()"{{/code}}.
278 It should be rendered like this: {{code}}My plugin says: "Hello World!"{{/code}}.
279 You can also call void methods specified in the API class :
280
281 {{code}}
282 $xwiki.helloworld.updatePage()
283 The page has been updated.
284 {{/code}}
285
286 = Examples =
287
288 Here are some examples of what you can do with plugins. You should actually check the [[API Guide>>DevGuide.APIGuide]], since it contains examples on how to use the XWiki API. The examples in the API Guide are written in Velocity, and are thus easily applicable to Java.
289
290 == Accessing pages, objects and object properties from pages ==
291
292 This is something you can do from Velocity as well, but when you need to perform complex treatments on your XWiki pages, you need to do it from a java plugin.
293
294 The class representing a document in the XWiki Java model is ##com.xpn.xwiki.doc.XWikiDocument##. The class representing an object in the XWiki Java model is ##com.xpn.xwiki.objects.BaseObject##.
295
296 If you need to access existing documents from your plugin, you use the XWiki class, ##com.xpn.xwiki.XWiki##, which has a getDocument() method. You can retrieve the current Xwiki instance by using the ##com.xpn.xwiki.XWikiContext## class, which has a getWiki() method.
297
298 The rule, in plugin programming, is to pass the current context as a ##com.xpn.xwiki.XWikiContext## function parameter, between the different methods of your plugin class. The plugin API class also has a context property pointing to the current context.
299
300 {{code}}
301 // You need the current context, which you always have in a plugin anyway
302 com.xpn.xwiki.doc.XWikiDocument doc = context.getDoc(); // current document;
303 com.xpn.xwiki.doc.XWikiDocument doc = context.getWiki().getDocument("theSpace.theDoc", context); // any document
304 com.xpn.xwiki.objects.BaseObject meta;
305 meta = doc.getObject("fooSpace.fooClass");
306 String docType = (String)meta.getStringValue("type"); //if the class of the object has a property named "type", which can accept a text value...
307 meta.set("type", "newValue", context);
308 {{/code}}
309
310 If you need to access the parent of an XWiki document, you should use the getDocument() method of the XWiki class, as seen in the example above, with, as parameter value, the parent's full name returned by the getParent() method of the XWikiDocument class.
311
312 {{code}}
313 com.xpn.xwiki.doc.XWikiDocument parentDocument = context.getWiki().getDocument(childDocument.getParent());
314 {{/code}}
315
316 You should not use ##XWikiDocument.getParentDoc## since it only returns a blank XWikiDocument object set with the same full name as the parent's full name.

Get Connected