WritingComponents

Version 19.2 by dan on 2009/05/21
Warning: For security reasons, the document is displayed in restricted mode as it is not the current version. There may be differences and errors due to this.

Writing XWiki components

Invalid macro parameters used for the [toc] macro. Cause: [Failed to validate bean: [must be greater than or equal to 1]]. Click on this message for details.

This tutorial will guide you through the creation of an XWiki component that will soon replace the plugins architecture and which is now the recommended way of writing XWiki modules. They should be able to execute any java code and communicate with XWiki by using the existing XWiki (core) components, as well as being exposed to the xwiki documents scripting environment (velocity and groovy).

What are Components?

First, you should know a few things about Plexus engine and component based architecture. In very few words, component based architecture is a design principle based on low-coupling / high-cohesion pattern, with components (modules) focused on a single "job" in the system, that describe themselves through interfaces (services) and communicate with other components through these "contracts", without any concern about the implementation. The key is in the components engine (or manager) that handles instantiation and components dependency, based on each component's declaration of these (in its description file), automatically injecting the implementations at runtime.

So, what we should do in order to write a component is:

  • define the service the component "provides" (the interface). This interface is all that the component will expose to the other components. Any function you should need to ask to your component goes here.
  • create one or more implementations for this service. Of course, you must actually provide the services you described earlier
  • letting the component manager know about the newly created component. This can be done in two ways:
    • the Plexus way (obsolete): register the implementation and the interface in the components.xml file (META-INF/plexus/components.xml), as well as the dependencies on other components, requirements and other configurations for your component;
    • the XWiki way (the recommended way, which is going to be illustrated bellow): using specific annotations in both the service declaration (the interface) and the implementation(s).

XWiki Specifics

In XWiki, although we use the Plexus engine for the moment, we don't want to depend on the Plexus implementation (because we should be able to change components engine anytime, if we wanted to) so we have our own set of lifecycle interfaces to communicate with the manager:

All these are defined in the xwiki-component module in platform/core.

Let's get started!

Enough talking, let's see some code!

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.

Creating a XWiki component using maven

To simplify the three steps process of component creation in XWiki, and since the XWiki code lifecycle is based on maven, we have created a maven archetype to help create a simple component module with a single command, with respect to the XWiki architecture and components specific requirements.

  • download the archetype from here: xwiki-archetype-component-1.0-SNAPSHOT.jar (it will soon be uploaded on our maven repository).
  • use maven to install this file on your local repository by executing (make sure you replace path-to-jar-file with your own path):
mvn install:install-file -Dfile=<path-to-jar-file> -DartifactId=xwiki-archetype-component -DgroupId=com.xpn.xwiki.platform.tools -Dversion=1.0-SNAPSHOT -Dpackaging=jar
  • now you're ready to use maven to generate the xwiki component based on this archetype. Navigate to the directory where you want your component to be located and type:
mvn archetype:generate -DarchetypeGroupId=com.xpn.xwiki.platform.tools -DarchetypeArtifactId=xwiki-archetype-component -DarchetypeVersion=1.0-SNAPSHOT -DgroupId=<component-group-id> -DartifactId=<component-artifact-id>  -Dpackage=<component-package> -Dversion=<component-version> -Dpackaging=jar

where you replace component-group-id, component-artifact-id, component-package, component-version with the corresponding values for your component. To create a server XWiki Watch component, for example, we used -DgroupId=com.xpn.xwiki.products -DartifactId=xwiki-watch-component -Dpackage=org.xwiki.watch.component -Dversion=1.1-SNAPSHOT. Don't forget to follow the xwiki package names guidelines.

Now this will create a new maven module in a folder named component-artifact-id in your folder, with a default xwiki component inside. Note that if your parent (current, from where you are executing maven) folder is the folder of a maven module (contains a pom.xml file), then the command above will fail unless the module is packaged as pom. If the project is packaged as pom, then the newly created module will be added in its modules list, and the parent of the newly created component module will be set to this project's pom.

The component explained

Assume, for the following explanations, that the package you used is org.xwiki.component

Navigating in the component project folder, you will see standard maven project structure like this:

pom.xml
src/main/java/org/xwiki/component/HelloWorld.java
src/main/java/org/xwiki/component/internal/DefaultHelloWorld.java
src/main/resources/META-INF/components.txt
src/test/java/org/xwiki/component/HelloWorldTest.java

which corresponds to the default files created: the HelloWorld interface (service), its implementation DefaultHelloWorld, a test class for this component HelloWorldTest, the component declaration file components.txt and the maven project pom file.

If we have a look in the pom, we see something like this:

<groupId>your-group-id</groupId>
 <artifactId>your-artifact-id</artifactId>
 <version>your-version</version>

which are the group, artifact and version you used when you created your component

<properties>
   <!-- TODO: remove this if you inherit a project that has the core version set -->
   <platform.core.version>1.8-SNAPSHOT</platform.core.version>
 </properties>

Failed to execute the [velocity] macro. Cause: [The execution of the [velocity] script macro is not allowed in [xwiki:Documentation.DevGuide.Tutorials.WritingComponents.WebHome]. Check the rights of its last author or the parameters if it's rendered from another script.]. Click on this message for details.

Of course, we need to have our HelloWorld component reference when we execute this code so we add it as a dependency to this velocity context initializer component, as described in the section above:

org.xwiki.component.internal.vcinitializer.HelloWorldVelocityContextInitializer

This code goes in the components.txt file of the package where the velocity context initializer is located. In our case, in the same file as the description for the HelloWorld is.

Note that this time, we also use roles for component identification, because we need to differentiate this implementation of the VelocityContextInitializer from the other implementations, as it is not the only component with this role in XWiki. 

Of course, in order to for all this to compile, we need to have the VelocityContextInitializer interface available on the classpath so we have this new dependency in the component module's pom:

<dependency>
     <groupId>org.xwiki.platform</groupId>
     <artifactId>xwiki-core-velocity</artifactId>
     <version>${platform.core.version}</version>
   </dependency>

And that's it, you have made your HelloWorld component velocity-accessible! Just recompile your package, copy it in the WEB-INF/lib folder of your xwiki webbapp container, and restart the server. You'll be able to get a greeting in velocity through:

$greeter.sayHello()

For the automatic creation of a velocity accessible xwiki component through this method, we have also created a maven archetype for this purpose too, the xwiki-archetype-velocity-component-1.0-SNAPSHOT.jar. Download it and use it as described in the first part of this tutorial.

How do I find other code?

The XWiki data model

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.

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.

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.

For example:

@Component
public class DefaultHelloWorld implements HelloWorld
{
   /** Provides access to documents. Injected by the Component Manager. */
    @Requirement
   private DocumentAccessBridge documentAccessBridge;

    [...]

   private String getConfiguredGreeting()
    {
       return documentAccessBridge.getProperty("XWiki.XWikiPreferences", "greeting_text");
    }

The XWiki context

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 present everywhere in the code.

In the component world, the current request information is held in an execution context. 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, which you can use just like any other XWiki component.

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:

/** Provides access to the request context. Injected by the Component Manager. */
    @Requirement
   private Execution execution;

    [...]

   private void workWithTheContext()
    {
       ExecutionContext context = execution.getContext();
       // Do something with the execution context
    }

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.

private void workWithTheContext()
    {
       ExecutionContext context = execution.getContext();
       Map<Object, Object> xwikiContext = (Map<Object, Object>) context.getProperty("xwikicontext");
       // Do something with the XWiki context
    }

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 component, and populate newly created execution contexts, just like with velocity contexts.

Code outside components

You can use external libraries as in any other maven module, just declare the right dependencies in your module's pom.xml.

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.

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, so that we can design and implement it collaboratively.

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.

Tags:
   

Get Connected