Wiki source code of CreatingPlugins

Version 3.2 by Vincent Massol on 2009/08/17

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

Get Connected