Wiki source code of CreatingPlugins

Version 1.37 by jeanvivienmaurice on 2007/11/16

Hide last authors
Vincent Massol 1.1 1 1 Creating Plugins
2
3 Plugins are quite handy when you want to interact with third-party code from the Velocity context. Check the [Code Zone>Code.WebHome] for a list of existing plugins.
4
Vincent Massol 1.13 5 Here are steps to develop a Hello World plugin and more.
Vincent Massol 1.1 6
Vincent Massol 1.13 7 #toc("" "" "")
8
Vincent Massol 1.1 9 1.1 The plugin architecture
10
11 Basically, a plugin is composed of two parts:
12
Sergiu Dumitriu 1.24 13 * The *plugin* itself: it must implement the [XWikiPluginInterface>http://fisheye2.cenqua.com/browse/xwiki/xwiki-platform/core/trunk/xwiki-core/src/main/java/com/xpn/xwiki/plugin/XWikiPluginInterface.java] interface. For simplicity you can also extends the [XWikiDefaultPlugin>http://fisheye2.cenqua.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).
14 * Its *API*: it should extend the [Api>http://fisheye2.cenqua.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.
Vincent Massol 1.21 15
Sergiu Dumitriu 1.25 16 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.
Sergiu Dumitriu 1.24 17
jeanvivienmaurice 1.14 18 1.1.1 Plugin lifecycle
19
Sergiu Dumitriu 1.26 20 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 <code>init(XWikiContext)</code> 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 21 {code}
22 #set($helloWorldText = "$xwiki.helloworld.hello()")
23 {code}
24 or when you ask the XWiki instance for the plugin API object :
25 {code}
26 #set($pluginObject = $xwiki.getPlugin("helloworld")
27
28 #* the name given as argument of getPlugin() should be
29 the one returned by the getName() method of the Plugin class.
30 *#
31 {code}
Sergiu Dumitriu 1.27 32 XWiki calls the <code>getPluginApi()</code> method for the plugin's instance, which itself creates an instance of the class <code>com.xpn.xwiki.plugin.PluginApi</code>. This is why you should not store things in fields of the class extending <code>PluginApi</code> in your plugin, since the usual behavior for the <code>getPluginApi()</code> method is to create a new instance of the <code>PluginApi</code> 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:
33 {code}
34 #set($myPluginApi = $xwiki.helloworld)
35 {code}
Sergiu Dumitriu 1.28 36 The <code>myPluginApi</code> 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.
jeanvivienmaurice 1.14 37
Sergiu Dumitriu 1.31 38 1.1 Write the plugin
Sergiu Dumitriu 1.24 39
Vincent Massol 1.1 40 First of all let's *declare our plugin class*:
41
42 {code:java}
43 public class HelloWorldPlugin extends XWikiDefaultPlugin {...}
44 {code}
45
Sergiu Dumitriu 1.29 46 Then let's *implement the needed constructor*:
Vincent Massol 1.1 47
48 {code:java}
49 public HelloWorldPlugin(String name, String className, XWikiContext context) {
50 super(name,className,context);
51 }{code}
52
53 *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()
54
55 {code:java}
56 public String getName() {
57 return "helloworld";
58 }
59 {code}
60
61 *Write a method to get the plugin API*. Don't forget to cast the plugin.
62
63 {code:java}
64 public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
65 return new HelloWorldPluginApi((HelloWorldPlugin) plugin, context);
66 }
67 {code}
68
69 *Overload the cache flush method* (optional)
70
71 {code:java}
72 public void flushCache() {}
73 {code}
74
75 Optionally, we can *create a [log4j>http://logging.apache.org/log4j/docs/index.html] instance* for the plugin:
76 {code:java}
Sergiu Dumitriu 1.30 77 private static final Log LOG = LogFactory.getLog(HelloWorldPlugin.class);
Vincent Massol 1.1 78 {code}
79
80 This is very useful for debugging. The logger could be invoked from any method like:
81 {code:java}
82 public String getName() {
Sergiu Dumitriu 1.30 83 LOG.debug("Entered method getName");
Vincent Massol 1.1 84 return "helloworld";
85 }
86 {code}
87
88 Then, to enable logging at a specific level for your plugin, edit webapps/xwiki/WEB-INF/classes/log4j.properties and add, for example:
89 {code}
90 log4j.com.xpn.xwiki.plugin.helloworld.HelloWorldPlugin=debug
91 {code}
92
93 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.
94
95 And finally, *write a method to init the context*.
96 {code:java}
97 public void init(XWikiContext context) {
98 super.init(context);
99 }
100 {code}
101
102 Here is the code you should have now:
103
104 {code:java}
105 package com.xpn.xwiki.plugin.helloworld;
106
107 import org.apache.commons.logging.Log;
108 import org.apache.commons.logging.LogFactory;
109
110 import com.xpn.xwiki.XWikiContext;
111 import com.xpn.xwiki.api.Api;
112 import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
113 import com.xpn.xwiki.plugin.XWikiPluginInterface;
114
115 public class HelloWorldPlugin extends XWikiDefaultPlugin {
116
Sergiu Dumitriu 1.30 117 private static Log LOG = LogFactory.getLog(HelloWorldPlugin.class);
Vincent Massol 1.1 118
119 public HelloWorldPlugin(String name, String className, XWikiContext context) {
120 super(name,className,context);
121 init(context);
122 }
Sergiu Dumitriu 1.30 123
Vincent Massol 1.1 124 public String getName() {
125 return "helloworld";
126 }
127
128 public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
129 return new HelloWorldPluginApi((HelloWorldPlugin) plugin, context);
130 }
131
132 public void flushCache() {
133 }
134
135 public void init(XWikiContext context) {
136 super.init(context);
137 }
138 }
139 {code}
140
Sergiu Dumitriu 1.29 141
Sergiu Dumitriu 1.31 142 1.1 Write the API
Vincent Massol 1.1 143
144 Let's write the API class which will contain the methods that can be called from Velocity.
145
146 Firstly, *class declaration*:
147
148 {code:java}
149 public class HelloWorldPluginApi extends Api {...}
150 {code}
151
Sergiu Dumitriu 1.31 152 Then, *plugin field declaration*. It will let our API to call backend methods.
Vincent Massol 1.1 153
154 {code:java}
155 private HelloWorldPlugin plugin;
156 {code}
157
Sergiu Dumitriu 1.31 158 *Required constructor*
Vincent Massol 1.1 159
160 {code:java}
161 public HelloWorldPluginApi(HelloWorldPlugin plugin, XWikiContext context) {
Sergiu Dumitriu 1.31 162 super(context);
163 setPlugin(plugin);
Vincent Massol 1.1 164 }
165 {code}
166
Sergiu Dumitriu 1.31 167 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 168
169 {code:java}
170 public HelloWorldPlugin getPlugin(){
Sergiu Dumitriu 1.31 171 return (hasProgrammingRights() ? plugin : null);
172 // Uncomment for allowing unrestricted access to the plugin
173 // return plugin;
Vincent Massol 1.1 174 }
175
176 public void setPlugin(HelloWorldPlugin plugin) {
Sergiu Dumitriu 1.31 177 this.plugin = plugin;
Vincent Massol 1.1 178 }
179 {code}
180
181 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.
182 {code:java}
183 public String hello() {
184 return "Hello World!";
185 }
186 {code}
jeanvivienmaurice 1.9 187 You can also have <code>void</code> methods :
188 {code:java}
189 public void updatePage() {
190 //...
191 }
192 {code}
Vincent Massol 1.1 193
194 Here is the complete API code:
195
196 {code:java}
197 package com.xpn.xwiki.plugin.helloworld;
198
199 import com.xpn.xwiki.XWikiContext;
200 import com.xpn.xwiki.api.Api;
201
202 public class HelloWorldPluginApi extends Api {
203 private HelloWorldPlugin plugin;
204
Sergiu Dumitriu 1.31 205 public HelloWorldPluginApi(HelloWorldPlugin plugin, XWikiContext context) {
206 super(context);
207 setPlugin(plugin);
208 }
Vincent Massol 1.1 209
Sergiu Dumitriu 1.32 210 public HelloWorldPlugin getPlugin(){
211 return (hasProgrammingRights() ? plugin : null);
212 // Uncomment for allowing unrestricted access to the plugin
213 // return plugin;
214 }
jeanvivienmaurice 1.9 215
Sergiu Dumitriu 1.31 216 public void setPlugin(HelloWorldPlugin plugin) {
217 this.plugin = plugin;
218 }
219
220 public String hello() {
221 return "Hello World!";
222 }
223
224 public void updatePage() {
225 //...
226 }
Vincent Massol 1.1 227 }
228 {code}
229
jeanvivienmaurice 1.9 230
Sergiu Dumitriu 1.32 231
Vincent Massol 1.1 232 1.1 Integrate the plugin in your XWiki installation
233
234 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 do this. You should be able to reproduce these steps easily in your favourite operating system.
235
236 {code}
Sergiu Dumitriu 1.33 237 go to the tomcat installation folder (or whatever container you are using)
Vincent Massol 1.1 238 $ cd myTomcatInstallation
239 go to the xwiki WEB-INF directory
240 $ cd webapps/xwiki/WEB-INF
241 create the classes tree, compliant to the "package" directive that you set in the plugin source files
242 $ mkdir classes/com/xpn/xwiki/plugin/helloworld
243 And then copy the class files to this location
Sergiu Dumitriu 1.33 244 $ cp myPluginsFolder/HelloWorldPlugin.class classes/com/xpn/xwiki/plugin/helloworld
245 $ cp myPluginsFolder/HelloWorldPluginAPI.class classes/com/xpn/xwiki/plugin/helloworld
Vincent Massol 1.1 246 {code}
247
248 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.
249
250 Finally you need to *register your plugin* in the ~~xwiki.cfg~~ file located in ~~WEB-INF~~
251
252 {code}
Sergiu Dumitriu 1.33 253 xwiki.plugins=com.xpn.xwiki.plugin.calendar.CalendarPlugin,\
254 ...,\
255 com.xpn.xwiki.plugin.helloworld.HelloWorldPlugin
Vincent Massol 1.1 256 {code}
257
Sergiu Dumitriu 1.34 258 Don't forget to restart your servlet container after this. XWiki has to re-read the configuration file.
Vincent Massol 1.1 259
260 1.1 Use the plugin
261
262 Here is the simplest part. Edit a page and write:
263 {code}
264 My plugin says: "$xwiki.helloworld.hello()"
265 {code}
266 It should be rendered like this
267 {code}
268 My plugin says: "Hello World!"
269 {code}
jeanvivienmaurice 1.10 270 You can also call <CODE>void</code> methods specified in the API class :
jeanvivienmaurice 1.8 271 {code}
272 $xwiki.helloworld.updatePage()
Sergiu Dumitriu 1.35 273 The page has been updated.
jeanvivienmaurice 1.8 274 {code}
jeanvivienmaurice 1.6 275
Sergiu Dumitriu 1.35 276
Vincent Massol 1.19 277 1.1 Examples
jeanvivienmaurice 1.10 278
Vincent Massol 1.19 279 Here are some examples of what you can do with plugins.
jeanvivienmaurice 1.23 280 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.
jeanvivienmaurice 1.6 281
282 1.1.1 Accessing pages, objects and object properties from pages
283
jeanvivienmaurice 1.36 284 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.
jeanvivienmaurice 1.6 285
jeanvivienmaurice 1.36 286 The class representing a document in the XWiki Java model is <code>com.xpn.xwiki.doc.XWikiDocument</code>.
287 The class representing an object in the XWiki Java model is <code>com.xpn.xwiki.objects.BaseObject</code>.
288
289 If you need to access existing documents from your plugin, you use the XWiki class, <code>com.xpn.xwiki.XWiki</code>, which has a <code>getDocument()</code> method.
290 You can retrieve the current Xwiki instance by using the <code>com.xpn.xwiki.XWikiContext</code> class, which has a <code>getWiki()</code> method.
291
292 The rule, in plugin programming, is to pass the current context as a <code>com.xpn.xwiki.XWikiContext</code> function parameter, between the different methods of your plugin class. The plugin API class also has a <code>context</code> property pointing to the current context.
293
jeanvivienmaurice 1.6 294 {code}
Vincent Massol 1.19 295 // You need the current context, which you always have in a plugin anyway
296 com.xpn.xwiki.doc.XWikiDocument doc = context.getDoc(); // current document;
297 com.xpn.xwiki.doc.XWikiDocument doc = context.getWiki().getDocument("theSpace.theDoc", context); // any document
jeanvivienmaurice 1.6 298 com.xpn.xwiki.objects.BaseObject meta;
299 meta = doc.getObject("fooSpace.fooClass");
300 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 301 meta.set("type", "newValue", context);
jeanvivienmaurice 1.6 302 {code}
303
jeanvivienmaurice 1.37 304 If you need to access the parent of an XWiki document, you should use the <code>getDocument()</code> method of the <code>XWiki</code> class, as seen in the example above, with, as parameter value, the parent's full name returned by the <code>getParent()</code> method of the <code>XWikiDocument</code> class.
305 {code}
306 com.xpn.xwiki.doc.XWikiDocument parentDocument = context.getWiki().getDocument(childDocument.getParent());
307 {code}
308 You should not use <code>XWikiDocument.getParentDoc</code> since it only returns a blank <code>XWikiDocument</code> object set with the same ful name as the parent's full name.
jeanvivienmaurice 1.6 309
jeanvivienmaurice 1.7 310

Get Connected