Component Module

Version 5.1 by Vincent Massol on 2009/08/20 23:05

Component Module

XWiki's Architecture is based on Component-oriented Development (see also this Plexus Component Tutorial to understand the benefits of using Components).

There are several Component Manager solutions out there for Java. To name a few:

XWiki has chosen to be independent of all existing Components Managers and instead to define some simple Component interfaces that can then be bound on any existing Component Manager. XWiki is currently implementing its own lightweight Component Manager with the idea to implement a Guice bridge as soon as Guice starts implementing JSR299/JSR330 annotations.

Features

The Component Module defines the following features:

  • Annotations to declare Component interfaces, Component implementations and Component dependencies (a.k.a as Component Requireements). Note that as soon as JSR299/JSR330 become official we'll drop our annotations and use these instead.
  • Ability to have Singleton Components and Per-lookup Components (a new instance is created when the component is retrieved)
  • Ability to define a Hint to separate different Components implementations implementing the same Component interface.
  • Automatic Field-based injection of Component Dependencies. Support for List and Map injections.
  • Ability for Components to perform some initialization when they are instantiated.
  • Ability for Components to log things.
  • Component Events to be notified when a new Component is registered/unregistered in the system
  • [Future] Ability to define Component Realms, i.e. the ability to isolate groups of components. 

Component Registration

There are two ways to register a Component:

  • By setting Java Annotations on the Component Interface and the Component implementation and declaring the Component implementation in a META-INF/components.txt file.
  • By programatically registering the Component against the Component Manager instance.

Using Annotations

The following Annotations are available:

  • ComponentRole: Used to declare an Interface as a Component Interface (a.k.a Role)
  • Component: Used to declare a class implementing a Component Interface as a Component implementation
  • InstantiationStrategy: Used to declare a Component implementation as being a singleton or not
  • Requirement: Used to declare a field as requiring a Component implementation to be injected at runtime

Here's a quick example:

@ComponentRole
public interface Macro
{
    List<Block> execute();
}
@Component("message")
public class MessageMacro implements Macro
{
   @Requirement
   private Execution execution;

   @Requirement("box")
   private Macro boxMacro;

   public List<Block> execute()
   {
      ...
   }
}

In this example:

  • The Macro interface is the Component Interface
  • The MessageMacro class is declared as a Macro with a message Hint (to differentiate it from other implementations). Note that you can leave the hint empty, in which case it'll be the default hint.
  • The MessageMacro needs 2 other components injected: Execution and Macro. The implementation injected will be found at runtime by the Component Manager. An Execution implementation with a default Hint will be injected and a Macro implementation with the box Hint will be injected.

In addition, for our MessageMacro component to be available at runtime, you need to list it with its fully-qualified name in a META-INF/components.txt file:

org.xwiki.rendering.internal.macro.message.MessageMacro

Registering a Component with several Hints

You can register a component several times, for different hints. For example, to register our MessageMacro for the 3 hints info, error, warning we could use:

@Component(hints = {"info", "warning", "error" })
public class MessageMacro implements Macro
...

List and Map injections

You may want to need to have all the Component implementations of a given Component Interface injected. To do this you simply need to have a field of type List of Map defined. 

For example to be injected all Macro implementations you'd use:

@Requirement
private List<Macro> macros;

or

@Requirement
private Map<String, Macro> macros;

In the second example the Map keys are the Hint values.

Getting access to the Component Manager

Automatic dependency injection is great and easy but there are time when you don't know at compile time what you want injected. For these situation you can have the ComponentManager injected. For example:

@Requirement
private ComponentManager componentManager;

See below for the API available on ComponentManager.

Overrides

Sometimes you'll have several JARs with Component implementations for the Component Interface and same Hint. In this case you need to tell the Component Manager which implementation to use. This is done by creating a META-INF/component-overrides.txt file and listing the implementation to use (using the same format as for the components.txt file).

Component Manager

The Component Manager is a key class when using Components. It allows to lookup components and register new components programatically. Here's the API it offers:

<T> boolean hasComponent(Class<T> role);
<T> boolean hasComponent(Class<T> role, String roleHint);
<T> T lookup(Class<T> role) throws ComponentLookupException;
<T> T lookup(Class<T> role, String roleHint) throws ComponentLookupException;
<T> void release(T component) throws ComponentLifecycleException;
<T> Map<String, T> lookupMap(Class<T> role) throws ComponentLookupException;
<T> List<T> lookupList(Class<T> role) throws ComponentLookupException;
<T> void registerComponent(ComponentDescriptor<T> componentDescriptor) throws ComponentRepositoryException;
<T> void registerComponent(ComponentDescriptor<T> componentDescriptor, T componentInstance) throws ComponentRepositoryException;
void unregisterComponent(Class< ? > role, String roleHint);
<T> ComponentDescriptor<T> getComponentDescriptor(Class<T> role, String roleHint);
<T> List<ComponentDescriptor<T>> getComponentDescriptorList(Class<T> role);

There's only one instance of the Component Manager in the system. 

Note that when you're writing a Component you normally don't even need to have access to it since all you need to do is declare dependencies using Annotations as explained above.

Component Initialization

If your Component implementation needs to perform some initialization, you'll need to make it implement the org.xwiki.component.phase.Initializable interface. For example:

@Component
public class DefaultObservationManager implements ObservationManager, Initializable
{
   ...

   public void initialize() throws InitializationException
   {
       // Perform some init here.
   }
}

You are then guaranteed that when your component is instantiated its initialize() method will be called.

#info("Using a constructor doesn't always work since you might need dependency injections to be done prior to the initialization happening.")

Component Logging

Tutorial

See the "Writing a XWiki Component" tutorial.

Get Connected