Wiki source code of Writing XWiki components

Version 38.1 by AreyouKiddingme on 2011/07/25

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}{{toc/}}{{/box}}
2
3 This tutorial guides you through the creation of an XWiki component, which is a way to extend or customize the XWiki platform. Indeed the XWiki platform is composed of components and it's possible to replace the default implementations with your own implementations. It's also possible to add new component implementations to extend the platform such as by implementing new [[Rendering Macros>>DevGuide.RenderingMacroTutorial]].
4
5 {{info}}Components replace the older Plugin architecture which has been deprecated a while ago.{{/info}}
6
7 You should start by reading the [[Reference document on XWiki Components>>extensions:Extension.Component Module]].
8
9 = Let's get started! =
10
11 Enough talking, let's see some code!
12
13 In the followings we will guide you through writing a simple component, helping you to quickly get oriented in XWiki components world and explaining how it works.
14
15 == Creating a XWiki component using Maven ==
16
17 As you've read in the [[XWiki Component Reference>>extensions:Extension.Component Module]] writing a component is a three-streps process (component interface, component implementation, registration of component). To make it easier for you to get started, we have created a maven archetype to help create a simple component module with a single command.
18
19 After you've [[installed Maven and added XWiki repositories in your profile>>dev:Community.Building#HInstallingMaven]], open a shell prompt and type:
20
21 {{code language="none"}}
22 mvn archetype:generate -DarchetypeCatalog=http://svn.xwiki.org/svnroot/xwiki/platform/xwiki-tools/trunk/xwiki-archetypes/xwiki-archetype-component/archetype-catalog.xml
23 {{/code}}
24
25 {{info}}
26 As of July, 2011, the svn version of source tree seems to be outdated, github (https://github.com/xwiki/) is rumored to be the correct place to look for the latest xwiki code. So you might try something like:
27
28 {{code language="none"}}
29 mvn archetype:generate -DarchetypeCatalog=https://raw.github.com/xwiki/xwiki-platform/master/xwiki-platform-tools/xwiki-platform-tool-archetypes/xwiki-platform-tool-archetype-component/archetype-catalog.xml
30 {{/code}}
31
32 However, I had no luck with the direct URL and after several attempts came to something like:
33
34 {{code language="none"}}
35 wget https://raw.github.com/xwiki/xwiki-platform/master/xwiki-platform-tools/xwiki-platform-tool-archetypes/xwiki-platform-tool-archetype-component/archetype-catalog.xml
36 mv archetype-catalog.xml /tmp
37 mvn archetype:generate -DarchetypeCatalog=file:///tmp/archetype-catalog.xml
38 {{/code}}
39 {{/info}}
40
41 Then follow the instructions. For example:
42
43 {{code language="none"}}
44 vmassol@target $ mvn archetype:generate -DarchetypeCatalog=http://svn.xwiki.org/svnroot/xwiki/platform/xwiki-tools/trunk/xwiki-archetypes/xwiki-archetype-component/archetype-catalog.xml
45 [INFO] Scanning for projects...
46 [INFO]
47 [INFO] ------------------------------------------------------------------------
48 [INFO] Building Maven Stub Project (No POM) 1
49 [INFO] ------------------------------------------------------------------------
50 [INFO]
51 [INFO] >>> maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom >>>
52 [INFO]
53 [INFO] <<< maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom <<<
54 [INFO]
55 [INFO] --- maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom ---
56 [INFO] Generating project in Interactive mode
57 [INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
58 Choose archetype:
59 1: http://svn.xwiki.org/svnroot/xwiki/platform/xwiki-tools/trunk/xwiki-archetypes/xwiki-archetype-component/archetype-catalog.xml -> xwiki-archetype-component (Make it easy to create a maven project for creating a XWiki Component.)
60 Choose a number: : 1
61 Downloading: http://maven.xwiki.org/snapshots/org/xwiki/platform/tools/xwiki-archetype-component/1.0-SNAPSHOT/maven-metadata.xml
62 Downloaded: http://maven.xwiki.org/snapshots/org/xwiki/platform/tools/xwiki-archetype-component/1.0-SNAPSHOT/maven-metadata.xml (383 B at 3.4 KB/sec)
63 Downloading: http://maven.xwiki.org/snapshots/org/xwiki/platform/tools/xwiki-archetype-component/1.0-SNAPSHOT/maven-metadata.xml
64 Downloaded: http://maven.xwiki.org/snapshots/org/xwiki/platform/tools/xwiki-archetype-component/1.0-SNAPSHOT/maven-metadata.xml (383 B at 4.9 KB/sec)
65 Define value for property 'groupId': : com.acme
66 Define value for property 'artifactId': : example
67 Define value for property 'version': 1.0-SNAPSHOT: :
68 Define value for property 'package': com.acme: :
69 Confirm properties configuration:
70 groupId: com.acme
71 artifactId: example
72 version: 1.0-SNAPSHOT
73 package: com.acme
74 Y: : Y
75 [INFO] ----------------------------------------------------------------------------
76 [INFO] Using following parameters for creating project from Old (1.x) Archetype: xwiki-archetype-component:1.0-SNAPSHOT
77 [INFO] ----------------------------------------------------------------------------
78 [INFO] Parameter: groupId, Value: com.acme
79 [INFO] Parameter: packageName, Value: com.acme
80 [INFO] Parameter: package, Value: com.acme
81 [INFO] Parameter: artifactId, Value: example
82 [INFO] Parameter: basedir, Value: /Users/vmassol/dev/xwiki/trunks-clean4/platform/tools/xwiki-archetypes/target
83 [INFO] Parameter: version, Value: 1.0-SNAPSHOT
84 [INFO] ********************* End of debug info from resources from generated POM ***********************
85 [INFO] project created from Old (1.x) Archetype in dir: /Users/vmassol/dev/xwiki/trunks-clean4/platform/tools/xwiki-archetypes/target/example
86 [INFO] ------------------------------------------------------------------------
87 [INFO] BUILD SUCCESS
88 [INFO] ------------------------------------------------------------------------
89 [INFO] Total time: 12.868s
90 [INFO] Finished at: Sun Nov 14 18:27:52 CET 2010
91 [INFO] Final Memory: 9M/81M
92 [INFO] ------------------------------------------------------------------------
93 vmassol@target $
94 {{/code}}
95
96 Then go in the created directory (##example## in our example above) and run ##mvn install## to build your component.
97
98 == The Component explained ==
99
100 Assume, for the following explanations, that the package you used is ##com.acme##
101
102 Navigating in the component project folder, you will see the following standard Maven project structure:
103
104 {{code language="none"}}
105 pom.xml
106 src/main/java/com/acme/HelloWorld.java
107 src/main/java/com/acme/internal/DefaultHelloWorld.java
108 src/main/java/com/acme/internal/HelloWorldScriptService.java
109 src/main/resources/META-INF/components.txt
110 src/test/java/com/acme/HelloWorldTest.java
111 {{/code}}
112
113 which correspond to the default files created: the ##HelloWorld## interface (a.k.a component role), its implementation ##DefaultHelloWorld## (component implementation), a test class for this component ##HelloWorldTest##, the component declaration file ##components.txt## and the Maven project ##pom.xml## file. The ##HelloWorldScriptService## file is described below when we explain how to make the component's API available to wiki pages.
114
115 If you have a look in the ##pom.xml##, you'll notice the following dependencies:
116
117 {{code language="xml"}}
118 <dependencies>
119 <dependency>
120 <groupId>org.xwiki.platform</groupId>
121 <artifactId>xwiki-core-component-default</artifactId>
122 <version>${platform.core.version}</version>
123 </dependency>
124 <!-- Testing dependencies -->
125 <dependency>
126 <groupId>org.xwiki.platform</groupId>
127 <artifactId>xwiki-core-shared-tests</artifactId>
128 <version>${platform.core.version}</version>
129 <scope>test</scope>
130 </dependency>
131 </dependencies>
132 {{/code}}
133
134 The code above defines the dependency on the ##xwiki-core-component-default## in the core which is where XWiki Component notions are defined. There's also a dependency on ##xwiki-core-shared-tests## which provides helper classes to easily test components.
135
136 The interface file (##HelloWorld.java##) contains the definition of a regular Java interface, and looks like this:
137
138 {{code language="java"}}
139 @ComponentRole /* annotation used for declaring the service our component provides */
140 public interface HelloWorld
141 {
142 String sayHello();
143 }
144 {{/code}}
145
146 Keep in mind that this interface specifies the API that other components can use on your component. In our case, we'll build a polite component that can ##sayHello()##.
147
148 Then we have the implementation of the interface, the ##DefaltHelloWorld## class.
149
150 {{code language="java"}}
151 @Component /* annotation used for declaring a component implementation */
152 public class DefaultHelloWorld implements HelloWorld
153 {{/code}}
154
155 Note that optionally, the ##@Component## annotation can specify a //hint//. This is useful especially when we want to distinguish between several implementations for the same type of component. Image we had a special HelloWorld implementation taking the greeting message from a database; it could look lile:
156
157 {{code language="java"}}
158 @Component("database")
159 public class DatabaseHelloWorld implements HelloWorld
160 {{/code}}
161
162 Then the ##sayHello## in ##DefaultHelloWorld## is basic in this example:
163
164 {{code language="java"}}
165 /**
166 * Says hello by returning a greeting to the caller.
167 *
168 * @return A greeting.
169 */
170 public String sayHello()
171 {
172 return "Hello world!";
173 }
174 {{/code}}
175
176 And now, the ##components.txt## file, in which component implementations present in this jar are specified for the ##ComponentManager## to register them.
177
178 {{code language="none"}}com.acme.internal.DefaultHelloWorld{{/code}}
179
180 = How to find my component and use it? =
181
182 == From other components ==
183
184 To access your component from another component we use the components engine, and specify the dependencies, leaving instantiation and component injection to the be handled by the component manager.
185
186 In order to use the ##HelloWorld## component, you need a reference to it in the the component that uses it. For this, you should use a member variable in the implementation of the using component, for example, a ##Socializer## component will need to be able to say hello to the world:
187
188 {{code}}
189 @Component
190 public class DefaultSocializer implements Socializer
191 {
192 [...]
193
194 /** Will be injected by the component manager */
195 @Requirement
196 private HelloWorld helloWorld;
197
198 [...]
199 }
200 {{/code}}
201
202 Note the ##@Requirement## annotation, which instructs the component manager to inject the required component where needed.
203
204 And that's it, you can now use the ##helloWorld## member anywhere in the ##DefaultSocializer## class freely, without further concerns, it will be assigned by the component manager provided that the ##HelloWorld## component is on the classpath at runtime when the ##Socializer## is used. Such as:
205
206 {{code}}
207 public class DefaultSocializer implements Socializer
208 {
209 [...]
210
211 public void startConversation()
212 {
213 this.helloWorld.sayHello();
214
215 [...]
216 }
217
218 [...]
219 }
220 {{/code}}
221
222 More, note that all through the process of defining a communication path between two components, we never referred components implementations, all specifications being done through //roles// and //interfaces//: the implementation of a service is completely hidden from any code external to the component.
223
224 == From non-components java code (e.g. older plugins) ==
225
226 For this kind of usages, since we cannot use the component-based architecture advantages and the "magic" of the component manager, the XWiki team has created a helper method that acts like a bridge between component code and non-component code, the ##com.xpn.xwiki.web.Utils.getComponent(String role, String hint)## that gets the specified component instance from the component manager and returns it. As seen in the previous sections, the hint is an optional identifier, additional to ##role##, used to differentiate between implementations of the same interface: the //roles// identify services while the hints help differentiate between implementations. The ##getComponent## function also has a signature without the ##hint## parameter, that uses the default hint.
227
228 To use our greetings provider component, we would simply invoke:
229
230 {{code}}
231 HelloWorld greeter = Utils.getComponent(HelloWorld.class);
232 greeter.sayHello();
233 {{/code}}
234
235 Note that, even if, in fact, the object returned by this function is an instance of the DefaultHelloWorld, you should **never declare your object of the implementation type nor cast to implementation instead of interface**. A component is represented by its interface, the implementation for such a service can be provided by any code, any class so relying on the implementation type is neither good practice (since the interface contract should be enough for a component), nor safe. In the future, a maven enforcer plugin will be setup in the build lifecycle, so that any reference to component implementations (located in an "internal" subpackage) will cause build errors.
236
237 {{info}}The usage of ##Utils.getComponent()## functions is highly discouraged, reserved for this type of situations, when you need to access a component from non-componentized code. For the componentized code, you should use either dependency declaration at 'compile-time' (as shown before with annotations) or, if you need to resolve components dependencies at runtime, use the ##ComponentManager##, which you can access by implementing the Composable interface as described in the [[Component Module Reference>>extensions:Extension.Component Module]].{{/info}}
238
239 == From wiki pages ==
240
241 Components can be made accessible to wiki pages by writing a ##ScriptService## implementation. They can then be access using any provided scripting language (velocity, groovy, python, ruby, php, etc).
242
243 Let's make our ##sayHello## method accessible:
244
245 {{code language="java"}}
246 @Component("hello")
247 public class HelloWorldScriptService implements ScriptService
248 {
249 @Requirement
250 private HelloWorld helloWorld;
251
252 public String greet()
253 {
254 return this.helloWorld.sayHello();
255 }
256 }
257 {{/code}}
258
259 Notice the component hint used (the ##hello## part in the ##@Component##). This is the name under which the script service will be accessible from scripting languages.
260
261 For example to access it in velocity you'd write:
262 {{code language="none"}}
263 $services.hello.greet()
264 {{/code}}
265
266 From Groovy:
267 {{code language="none"}}
268 services.hello.greet()
269 {{/code}}
270
271 Now for our script service to work we need to register it as a component and thus add it to the ##META-INF/components.txt## file:
272 {{code language="none"}}
273 ...
274 com.acme.internal.HelloWorldScriptService
275 {{/code}}
276
277 We also need to make the Script Service infrastructure available in our classpath. This is done by adding the following in your ##pom.xml## file:
278 {{code language="xml"}}
279 <dependency>
280 <groupId>org.xwiki.platform</groupId>
281 <artifactId>xwiki-core-script</artifactId>
282 <version>${platform.core.version}</version>
283 </dependency>
284 {{/code}}
285
286 = Accessing Legacy code =
287
288 By legacy we mean old XWiki code that hasn't been moved to components yet.
289
290 == The XWiki data model ==
291
292 Since the XWiki data model (documents, objects, attachments, etc.) reside in the big, old ##xwiki-core## module, and since we don't want to add the whole core and all its dependencies as a dependency of a simple lightweight component (this would eventually lead to a circular dependency, which is not allowed by maven), the current strategy, until the data model is completely turned into a component, is to use a //bridge// between the new component architecture and the old ##xwiki-core##.
293
294 In short, the way this works is based on the fact that implementations for a component don't have to be in the same ##.jar## as the interface, and there is no dependency //from// the component interface //to// the actual implementation, only the other way around. So, we made a few simple components that offer basic access to XWiki documents, and declared the classes in ##xwiki-core## as the default implementation for those components.
295
296 If your component needs to access the XWiki data model, it will use the components from the ##xwiki-core-bridge## module for that. Note that these interfaces are rather small, so you can't do everything that you could with the old model. If you need to add some methods to the bridge, feel free to propose it on the [[mailing list>>dev:Community.MailingLists]].
297
298 For example:
299
300 {{code}}
301 @Component
302 public class DefaultHelloWorld implements HelloWorld
303 {
304 /** Provides access to documents. Injected by the Component Manager. */
305 @Requirement
306 private DocumentAccessBridge documentAccessBridge;
307
308 [...]
309
310 private String getConfiguredGreeting()
311 {
312 return documentAccessBridge.getProperty("XWiki.XWikiPreferences", "greeting_text");
313 }
314 {{/code}}
315
316 == The XWiki context ==
317
318 Note that the XWiki context is deprecated. It was an older way of keeping track of the current request, which had to be passed around from method to method, looking like a [[ball and chain>>http://en.wikipedia.org/wiki/Ball_and_chain]] present everywhere in the code.
319
320 In the component world, the current request information is held in an **[[execution context>>http://maven.xwiki.org/site/xwiki-core-parent/xwiki-core-context/apidocs/org/xwiki/context/ExecutionContext.html]]**. This is actually more powerful than the old XWiki context, as it is a generic execution context, and you can create one anytime you want and use it anyway you want. And you don't have to manually pass it around with all method calls, as execution contexts are managed by the **[[Execution component>>http://maven.xwiki.org/site/xwiki-core-parent/xwiki-core-context/apidocs/org/xwiki/context/Execution.html]]**, which you can use just like any other XWiki component.
321
322 In short, if you want to get access to the execution context (which holds context information inserted by the new components), you must declare a requirement on the ##Execution## component (located in the ##xwiki-core-context## module), and then you can write:
323
324 {{code}}
325 /** Provides access to the request context. Injected by the Component Manager. */
326 @Requirement
327 private Execution execution;
328
329 [...]
330
331 private void workWithTheContext()
332 {
333 ExecutionContext context = execution.getContext();
334 // Do something with the execution context
335 }
336 {{/code}}
337
338 If you still need to access the old XWiki context, then you can get a reference to it from the execution context, but you should not cast it to an ##XWikiContext##, which would pull the whole xwiki-core as a dependency, but to a ##Map##. You won't be able to access all the properties, like the current user name or the URL factory, but you can access anything placed in the internal map of the XWikiContext.
339
340 {{code}}
341 private void workWithTheContext()
342 {
343 ExecutionContext context = execution.getContext();
344 Map<Object, Object> xwikiContext = (Map<Object, Object>) context.getProperty("xwikicontext");
345 // Do something with the XWiki context
346 }
347 {{/code}}
348
349 If you want not just to use the execution context, but to make something available in every execution context, you can create an implementation of the [[ExecutionContextInitializer>>http://maven.xwiki.org/site/xwiki-core-parent/xwiki-core-context/apidocs/org/xwiki/context/ExecutionContextInitializer.html]] component, and populate newly created execution contexts, just like with [[velocity contexts>>#HAccessingacomponentfromvelocity]].
350
351 == Code outside components ==
352
353 You can use external libraries as in any other maven module, just declare the right dependencies in your module's ##pom.xml##.
354
355 As a general rule, you should **not** work with any non-componentized XWiki code, as the way the old code was designed leads to an eventual dependency on the whole ##xwiki-core## module, which we are trying to avoid. If the component you are writing is needed by other modules (which is the case with most components, since a component which isn't providing any usable/used services is kind of useless), then this will likely lead to an eventual cyclic dependency, which will break the whole build.
356
357 If you need some functionality from the old core, consider rewriting that part as a new component first, and then use that new component from your code. You should ask first on the [[devs mailing list>>dev:Community.MailingLists]], so that we can design and implement it collaboratively.
358
359 If the effort needed for this is too large, you can try creating a bridge component, by writing just the interfaces in a new module, and make the classes from the core the default implementation of those interfaces. Then, since in the end the xwiki-core, the bridge component and your component will reside in the same classpath, plexus will take care of coupling the right classes. Be careful when writing such bridges, as they are short lived (since in the end all the old code will be replaced by proper components), and if the future real component will have a different interface, then you will have to rewrite your code to adapt to the new method names, or worse, the new component logic.
360
361 = Deploying the Component =
362
363 Now that we have a functioning Component let's build it and deploy it to a XWiki Enterprise instance:
364 * To build the component, issue ##mvn install##. This generates a JAR in the ##target## directory of your project.
365 * To install it into a XWiki Enterprise instance, just copy that JAR file in ##XE_WAR_HOME/WEB-INF/lib## where ##XE_WAR_HOME## is where the XWiki Enterprise WAR is deployed.
366
367 Your component is now ready for service.
368
369 Enjoy!

Get Connected