Wiki source code of Creating Plugins

Last modified by Simon Urli on 2023/10/10

Hide last authors
Manuel Smeria 4.4 1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
Silvia Macovei 4.1 4
Manuel Smeria 4.4 5 {{warning}}
Simon Urli 8.2 6 Plugins are the old way of writing XWiki extensions. The new way is to [[write a Component>>xwiki:Documentation.DevGuide.Tutorials.WritingComponents]].
Manuel Smeria 4.4 7 {{/warning}}
Vincent Massol 3.1 8
Vincent Massol 4.3 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.
Vincent Massol 1.1 10
Manuel Smeria 4.4 11 Here are the steps to develop a "Hello World" plugin and more.
Vincent Massol 1.1 12
Silvia Macovei 4.2 13 = The plugin architecture =
Vincent Massol 1.1 14
15 Basically, a plugin is composed of two parts:
16
Manuel Smeria 4.4 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.
Silvia Macovei 4.2 19
Silvia Macovei 4.1 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.
Vincent Massol 1.21 21
Silvia Macovei 4.2 22 == Plugin lifecycle ==
Sergiu Dumitriu 1.24 23
Silvia Macovei 4.2 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:
jeanvivienmaurice 1.14 25
Sergei Kulagin 7.1 26 {{code}}
27 #set($helloWorldText = "$xwiki.helloworld.hello()")
28 {{/code}}
Silvia Macovei 4.2 29
jeanvivienmaurice 1.14 30 or when you ask the XWiki instance for the plugin API object :
Silvia Macovei 4.1 31
32 {{code}}
jeanvivienmaurice 1.14 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 *#
Silvia Macovei 4.1 38 {{/code}}
39
Manuel Smeria 4.4 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:
jeanvivienmaurice 1.14 41
Manuel Smeria 4.4 42 {{code}}
43 #set($myPluginApi = $xwiki.helloworld)
44 {{/code}}
45
Silvia Macovei 4.2 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.
Sergiu Dumitriu 1.24 47
Silvia Macovei 4.2 48 = Write the plugin =
49
Sergei Kulagin 7.1 50 {{warning}}
51 Before starting to create plugins, you need to setup your environment correctly and learn the basics of working with the API:
52
53 To learn how to find classes, in what modules classes reside and what modules to import, use [[API Reference>>https://www.xwiki.org/xwiki/bin/view/Documentation/DevGuide/API/]] page.
54 To be able to build the plugin, you need to setup your environment according to the [[Building XWiki from sources>>https://dev.xwiki.org/xwiki/bin/view/Community/Building/]] guide.
55
56 These two should be enough to let you follow and finish this tutorial, and let you start making your own plugins.
57 {{/warning}}
58
Silvia Macovei 4.1 59 First of all let's **declare our plugin class**:
Vincent Massol 1.1 60
Manuel Smeria 4.4 61 {{code language="java"}}
62 public class HelloWorldPlugin extends XWikiDefaultPlugin {...}
63 {{/code}}
Vincent Massol 1.1 64
Silvia Macovei 4.1 65 Then let's **implement the needed constructor**:
Vincent Massol 1.1 66
Silvia Macovei 4.1 67 {{code language="java"}}
Vincent Massol 1.1 68 public HelloWorldPlugin(String name, String className, XWikiContext context) {
69 super(name,className,context);
Silvia Macovei 4.1 70 }
71 {{/code}}
Vincent Massol 1.1 72
Manuel Smeria 4.4 73 **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()##;
Vincent Massol 1.1 74
Silvia Macovei 4.1 75 {{code language="java"}}
Vincent Massol 1.1 76 public String getName() {
77 return "helloworld";
78 }
Silvia Macovei 4.1 79 {{/code}}
Vincent Massol 1.1 80
Silvia Macovei 4.1 81 **Write a method to get the plugin API**. Don't forget to cast the plugin.
Vincent Massol 1.1 82
Silvia Macovei 4.1 83 {{code language="java"}}
Vincent Massol 1.1 84 public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
85 return new HelloWorldPluginApi((HelloWorldPlugin) plugin, context);
86 }
Silvia Macovei 4.1 87 {{/code}}
Vincent Massol 1.1 88
Manuel Smeria 4.4 89 **Overload the cache flush method** (optional):
Vincent Massol 1.1 90
Manuel Smeria 4.4 91 {{code language="java"}}
92 public void flushCache() {}
93 {{/code}}
Vincent Massol 1.1 94
Manuel Smeria 4.4 95 Optionally, we can **create a [[log4j>>http://logging.apache.org/log4j/1.2/apidocs/index.html]] instance** for the plugin:
Vincent Massol 1.1 96
Manuel Smeria 4.4 97 {{code language="java"}}
98 private static final Log LOG = LogFactory.getLog(HelloWorldPlugin.class);
99 {{/code}}
Silvia Macovei 4.1 100
Vincent Massol 1.1 101 This is very useful for debugging. The logger could be invoked from any method like:
Silvia Macovei 4.1 102
103 {{code language="java"}}
Vincent Massol 1.1 104 public String getName() {
Sergiu Dumitriu 1.30 105 LOG.debug("Entered method getName");
Vincent Massol 1.1 106 return "helloworld";
107 }
Silvia Macovei 4.1 108 {{/code}}
Vincent Massol 1.1 109
Manuel Smeria 4.4 110 Then, to enable logging at a specific level for your plugin, edit //webapps/xwiki/WEB-INF/classes/log4j.properties// and add, for example:
Vincent Massol 1.1 111
Manuel Smeria 4.4 112 {{code}}
113 log4j.com.xpn.xwiki.plugin.helloworld.HelloWorldPlugin=debug
114 {{/code}}
Silvia Macovei 4.1 115
Manuel Smeria 4.4 116 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.
Silvia Macovei 4.2 117
Manuel Smeria 4.4 118 And finally, **write a method to init the context**:
Vincent Massol 1.1 119
Silvia Macovei 4.1 120 {{code language="java"}}
Vincent Massol 1.1 121 public void init(XWikiContext context) {
122 super.init(context);
123 }
Silvia Macovei 4.1 124 {{/code}}
Vincent Massol 1.1 125
126 Here is the code you should have now:
127
Silvia Macovei 4.1 128 {{code language="java"}}
Vincent Massol 1.1 129 package com.xpn.xwiki.plugin.helloworld;
130
131 import org.apache.commons.logging.Log;
132 import org.apache.commons.logging.LogFactory;
133
134 import com.xpn.xwiki.XWikiContext;
135 import com.xpn.xwiki.api.Api;
136 import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
137 import com.xpn.xwiki.plugin.XWikiPluginInterface;
138
139 public class HelloWorldPlugin extends XWikiDefaultPlugin {
140
Sergiu Dumitriu 1.30 141 private static Log LOG = LogFactory.getLog(HelloWorldPlugin.class);
Vincent Massol 1.1 142
143 public HelloWorldPlugin(String name, String className, XWikiContext context) {
Sergei Kulagin 7.1 144 super(name,className,context);
145 init(context);
Vincent Massol 1.1 146 }
Sergiu Dumitriu 1.30 147
Vincent Massol 1.1 148 public String getName() {
149 return "helloworld";
150 }
151
152 public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
153 return new HelloWorldPluginApi((HelloWorldPlugin) plugin, context);
154 }
155
156 public void flushCache() {
157 }
158
159 public void init(XWikiContext context) {
160 super.init(context);
161 }
162 }
Silvia Macovei 4.1 163 {{/code}}
Vincent Massol 1.1 164
Silvia Macovei 4.2 165 = Write the API =
Sergiu Dumitriu 1.29 166
Vincent Massol 1.1 167 Let's write the API class which will contain the methods that can be called from Velocity.
Silvia Macovei 4.2 168
Silvia Macovei 4.1 169 Firstly, **class declaration**:
Vincent Massol 1.1 170
Manuel Smeria 4.4 171 {{code language="java"}}
172 public class HelloWorldPluginApi extends Api {...}
173 {{/code}}
Vincent Massol 1.1 174
Silvia Macovei 4.1 175 Then, **plugin field declaration**. It will let our API to call backend methods.
Vincent Massol 1.1 176
Manuel Smeria 4.4 177 {{code language="java"}}
178 private HelloWorldPlugin plugin;
179 {{/code}}
Vincent Massol 1.1 180
Silvia Macovei 4.1 181 **Required constructor**
Vincent Massol 1.1 182
Silvia Macovei 4.1 183 {{code language="java"}}
Vincent Massol 1.1 184 public HelloWorldPluginApi(HelloWorldPlugin plugin, XWikiContext context) {
Sergiu Dumitriu 1.31 185 super(context);
186 setPlugin(plugin);
Vincent Massol 1.1 187 }
Silvia Macovei 4.1 188 {{/code}}
Vincent Massol 1.1 189
Silvia Macovei 4.1 190 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.
Vincent Massol 1.1 191
Silvia Macovei 4.1 192 {{code language="java"}}
Vincent Massol 1.1 193 public HelloWorldPlugin getPlugin(){
Sergiu Dumitriu 1.31 194 return (hasProgrammingRights() ? plugin : null);
195 // Uncomment for allowing unrestricted access to the plugin
196 // return plugin;
Vincent Massol 1.1 197 }
198
199 public void setPlugin(HelloWorldPlugin plugin) {
Sergiu Dumitriu 1.31 200 this.plugin = plugin;
Vincent Massol 1.1 201 }
Silvia Macovei 4.1 202 {{/code}}
Vincent Massol 1.1 203
Silvia Macovei 4.1 204 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.
205
206 {{code language="java"}}
Vincent Massol 1.1 207 public String hello() {
208 return "Hello World!";
209 }
Silvia Macovei 4.1 210 {{/code}}
211
Manuel Smeria 4.4 212 You can also have void methods:
213
Silvia Macovei 4.1 214 {{code language="java"}}
jeanvivienmaurice 1.9 215 public void updatePage() {
216 //...
Silvia Macovei 4.1 217 }
218 {{/code}}
Vincent Massol 1.1 219
220 Here is the complete API code:
221
Silvia Macovei 4.1 222 {{code language="java"}}
Vincent Massol 1.1 223 package com.xpn.xwiki.plugin.helloworld;
224
225 import com.xpn.xwiki.XWikiContext;
226 import com.xpn.xwiki.api.Api;
227
228 public class HelloWorldPluginApi extends Api {
229 private HelloWorldPlugin plugin;
230
Sergiu Dumitriu 1.31 231 public HelloWorldPluginApi(HelloWorldPlugin plugin, XWikiContext context) {
232 super(context);
233 setPlugin(plugin);
234 }
Vincent Massol 1.1 235
Sergiu Dumitriu 1.32 236 public HelloWorldPlugin getPlugin(){
237 return (hasProgrammingRights() ? plugin : null);
238 // Uncomment for allowing unrestricted access to the plugin
239 // return plugin;
240 }
jeanvivienmaurice 1.9 241
Sergiu Dumitriu 1.31 242 public void setPlugin(HelloWorldPlugin plugin) {
243 this.plugin = plugin;
244 }
245
246 public String hello() {
247 return "Hello World!";
248 }
249
250 public void updatePage() {
251 //...
252 }
Vincent Massol 1.1 253 }
Silvia Macovei 4.1 254 {{/code}}
Vincent Massol 1.1 255
Silvia Macovei 4.2 256 = Integrate the plugin in your XWiki installation =
jeanvivienmaurice 1.9 257
Manuel Smeria 4.4 258 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:
Sergiu Dumitriu 1.32 259
Silvia Macovei 4.1 260 {{code}}
Sergiu Dumitriu 1.33 261 go to the tomcat installation folder (or whatever container you are using)
Vincent Massol 1.1 262 $ cd myTomcatInstallation
263 go to the xwiki WEB-INF directory
264 $ cd webapps/xwiki/WEB-INF
265 create the classes tree, compliant to the "package" directive that you set in the plugin source files
266 $ mkdir classes/com/xpn/xwiki/plugin/helloworld
267 And then copy the class files to this location
Sergiu Dumitriu 1.33 268 $ cp myPluginsFolder/HelloWorldPlugin.class classes/com/xpn/xwiki/plugin/helloworld
269 $ cp myPluginsFolder/HelloWorldPluginAPI.class classes/com/xpn/xwiki/plugin/helloworld
Silvia Macovei 4.1 270 {{/code}}
Vincent Massol 1.1 271
Manuel Smeria 4.4 272 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.
Silvia Macovei 4.2 273
Manuel Smeria 4.4 274 Finally you need to **register your plugin** in the ##xwiki.cfg## file located in //WEB-INF//:
Vincent Massol 1.1 275
Silvia Macovei 4.1 276 {{code}}
Sergiu Dumitriu 1.33 277 xwiki.plugins=com.xpn.xwiki.plugin.calendar.CalendarPlugin,\
278 ...,\
279 com.xpn.xwiki.plugin.helloworld.HelloWorldPlugin
Silvia Macovei 4.1 280 {{/code}}
Vincent Massol 1.1 281
Sergei Kulagin 8.1 282 {{info}}
283 Here you need to specify the plugin class. The class that extends the XWikiDefaultPlugin or implements the XWikiPluginInterface, not groupId and artifactId of the pom.
284 {{/info}}
285
Sergiu Dumitriu 1.34 286 Don't forget to restart your servlet container after this. XWiki has to re-read the configuration file.
Vincent Massol 1.1 287
Silvia Macovei 4.2 288 = Use the plugin =
Vincent Massol 1.1 289
Manuel Smeria 4.4 290 Here is the simplest part. Edit a page and write: {{code}}My plugin says: "$xwiki.helloworld.hello()"{{/code}}.
291 It should be rendered like this: {{code}}My plugin says: "Hello World!"{{/code}}.
Silvia Macovei 4.2 292 You can also call void methods specified in the API class :
Silvia Macovei 4.1 293
294 {{code}}
jeanvivienmaurice 1.8 295 $xwiki.helloworld.updatePage()
Sergiu Dumitriu 1.35 296 The page has been updated.
Silvia Macovei 4.1 297 {{/code}}
jeanvivienmaurice 1.6 298
Silvia Macovei 4.2 299 = Examples =
Sergiu Dumitriu 1.35 300
Simon Urli 8.2 301 Here are some examples of what you can do with plugins. You should actually check the [[API Guide>>xwiki:Documentation.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.
jeanvivienmaurice 1.6 302
Silvia Macovei 4.2 303 == Accessing pages, objects and object properties from pages ==
jeanvivienmaurice 1.6 304
jeanvivienmaurice 1.36 305 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.
306
Manuel Smeria 4.4 307 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##.
Silvia Macovei 4.2 308
Manuel Smeria 4.4 309 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.
Silvia Macovei 4.2 310
Manuel Smeria 4.4 311 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.
Silvia Macovei 4.2 312
Silvia Macovei 4.1 313 {{code}}
Vincent Massol 1.19 314 // You need the current context, which you always have in a plugin anyway
315 com.xpn.xwiki.doc.XWikiDocument doc = context.getDoc(); // current document;
316 com.xpn.xwiki.doc.XWikiDocument doc = context.getWiki().getDocument("theSpace.theDoc", context); // any document
jeanvivienmaurice 1.6 317 com.xpn.xwiki.objects.BaseObject meta;
318 meta = doc.getObject("fooSpace.fooClass");
319 String docType = (String)meta.getStringValue("type"); //if the class of the object has a property named "type", which can accept a text value...
Vincent Massol 1.19 320 meta.set("type", "newValue", context);
Silvia Macovei 4.1 321 {{/code}}
jeanvivienmaurice 1.6 322
Silvia Macovei 4.2 323 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.
Silvia Macovei 4.1 324
Manuel Smeria 4.4 325 {{code}}
326 com.xpn.xwiki.doc.XWikiDocument parentDocument = context.getWiki().getDocument(childDocument.getParent());
327 {{/code}}
Silvia Macovei 4.1 328
Manuel Smeria 4.4 329 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