Component Module

Last modified by Thomas Mortagne on 2024/07/05 14:20

cogDefines a way to add Java extensions to the XWiki platform
TypeJAR
Category
Developed by

XWiki Development Team

Rating
0 Votes
LicenseGNU Lesser General Public License 2.1
Bundled With

XWiki Standard

Compatibility

First introduced in XWiki 1.2M1

Description

XWiki's Architecture is based on Component-oriented Development (see Why use Components? to understand the benefits of using Components - Even though we're not using Plexus the advantages listed are the same).

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

So far, 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. The future will depend on whether there'll be a standard solution in JavaSE (CDI is the best candidate so far). When a standard solution exists, we'll probably move to it.

Features

The Component Module defines the following features:

  • Annotations to declare Component interfaces, Component implementations and Component dependencies (a.k.a as Component Requirements). Starting with 3.1 we now support JSR330 annotations.
  • 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.
  • Ability to define Component Realms, i.e. the ability to isolate groups of components.
  • New in 3.3 Ability to define JSR330 Providers as Components and to have them injected automatically.

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 programmatically registering the Component against the Component Manager instance.

Using Annotations

The following Annotations are available:

  • Role: Used to declare an Interface as a Component 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. See also the list of instantiation strategies

    The @Singleton annotation should be used instead when specifying a Singleton component. However for a per lookup lifecycle you should still use @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP). If not specified the default is currently a Singleton but since JSR330 has a default of Per Lookup we might change this in the future.

  • Inject: Used to declare a field as requiring a Component implementation to be injected at runtime.
  • Named: Used to give a Hint to the Role to specify which implementation should be injected.
  • Priority: XWiki 15.4+ Indicates the position of the component in the collection returned by calls to the ComponentManager#getInstanceList and ComponentManager#getInstanceMap APIs. The default value is 1000 and the higher the priority (e.g. 100 is greater than 1000), the closer to the collection start the component will be located.

Here's a quick example:

@Role
public interface Macro
{
    List<Block> execute();
}
@Component
@Named("message")
@Singleton
public class MessageMacro implements Macro
{
   @Inject
   private Execution execution;

   @Inject
   @Named("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.

A Component Interface + a Hint must be unique across the system. If you have 2 Components registered with the same Component Interface and the same Hint then the Component Manager will only register one of them and a warning will be printed in the logs. That's unless you really want this and have defined an override.

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 and Using a Provider

Providers are useful for the following use cases:

  • You wish to break a cyclic dependency
  • You wish to lazily get a component instance (instead of it getting injected when your Component is looked up the first time)
  • You wish to control how you return the instance you're providing for. For example you may wish to read the Component Hint from a configuration file and return the instance of the Component matching that Hint dynamically

Providers are registered as standard components with the @Component , @Named and @Singleton annotations. However unlike other Components you don't need to tag any interface with @Role. All you need to do is implement javax.inject.Provider as shown in this example:

@Component
@Singleton
public class ConfiguredQueryExecutorProvider implements Provider<QueryExecutor>
{
...
   @Override
   public QueryExecutor get()
   {
       ... return instance here ...
   }
}

In this example the ConfiguredQueryExecutorProvider Provider is in charge of returning instances of QueryExecutor.

To use it you would get it injected as follows:

...
@Inject
private Provider<QueryExecutor> queryExecutorProvider;
...

Note that you don't need to register a custom Provider to be able to get it injected. If no registered Provider is found the Component Manager injects a default Provider which simply does a lookup of the Component Role it provides using the Component Manager. This is very useful for "dynamic injection", i.e. the ability to lookup Components when you need them and not have them injected when your Component is first initialized. This allows your code to easily react to the registration of new Components at runtime.

Registering a Component with several Hints

You can register a component several times, for different hints. In this case several instances of the component will be registered. For example, to register our MessageMacro for the 3 hints info, error, warning you would use:

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

While this feature is still valid in 3.1+, in the future we'll either drop this feature (since there are workarounds) or we'll add the ability to specify several @Named annotations. We recommend to not start depending on this feature as much as possible to prevent future incompatibilities.

Registering a Component with several Roles

You can make a component providing several roles by implementing the corresponding interfaces. However note that each role will produce a different instance. This is even true if the class implements the Singleton annotation; one understands that singletons are "by roles".

@Component
@Singleton
@Named("mymacro")
public class MessageMacro implements Macro, EventListener
...

It is generally recommended to separate roles implementations in different classes as much as possible.

List and Map injections

You may want 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 or Map defined. 

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

@Inject
private List<Macro> macros;

or

@Inject
private Map<String, Macro> macros;

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

Getting access to the Component Manager

Component's ComponentManager

Automatic dependency injection is great and easy, but there are times when you don't know at compile time what you want injected. For these situations you can inject the ComponentManager. For example to get the ComponentManager in which the component is registered you can do:

@Inject
private ComponentManager componentManager;

See below for the API available on ComponentManager.

Root Component Manager

Since 4.1.4 you can access the root ComponentManager the following way:

@Inject
@Named("root")
private ComponentManager componentManager;
Context Component Manager

Context Component Manager is a proxy Component Manager allowing to access a different Component Manager depending on the context (associated to the current wiki, the current user, etc.).

Since 4.1.4 you can access the context ComponentManager the following way:

@Inject
@Named("context")
private Provider<ComponentManager> componentManagerProvider;

We generally use a provider so that if it can't find any context ComponentManager it fallback on component's ComponentManager.

Before 4.1.4 you can directly access the context ComponentManager with:

@Inject
@Named("context")
private ComponentManager contextComponentManager;

but this is going to fail if no context ComponentManager can be found.

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 using a priority in your META-INF/components.txt files. The implementation with the highest priority will be used (highest means with the smaller value, 0 being the maximum priority and default priority is 1000 when not specified).

For example to register the com.acme.MyComponentImplementation component implementation with a priority of 500 you'd write:

500:com.acme.MyComponentImplementation

Prior to 3.3M1 we used to support overrides (which is still supported for backward compatibility) 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). The overriding Component needed to be defined in both files: META-INF/components.txt (because the new implementation was a component itself) and META-INF/component-overrides.txt.

Component Manager

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

<T> void release(T component) throws ComponentLifecycleException;
<T> void registerComponent(ComponentDescriptor<T> componentDescriptor) throws ComponentRepositoryException;
<T> void registerComponent(ComponentDescriptor<T> componentDescriptor, T componentInstance) throws ComponentRepositoryException;
<T> boolean hasComponent(Type role);
<T> boolean hasComponent(Type role, String roleHint);
<T> T getInstance(Type role) throws ComponentLookupException;
<T> T getInstance(Type role, String roleHint) throws ComponentLookupException;
<T> Map<String, T> getInstanceMap(Type role) throws ComponentLookupException;
<T> List<T> getInstanceList(Type role) throws ComponentLookupException;
<T> ComponentDescriptor<T> getComponentDescriptor(Type role, String roleHint);
<T> List<ComponentDescriptor<T>> getComponentDescriptorList(Type role);
void unregisterComponent(Type role, String roleHint);
void unregisterComponent(ComponentDescriptor< ? > componentDescriptor);

There's only one instance of the root Component Manager (default hint) 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.

Namespaces

Component API comes with "namespace" concept. The format of the namespaces is <type>:<id>. The format of the <id> depend on each <type>

For example the namespace representing the wiki mywiki is wiki:mywiki, the namespace for the user MyUser from wiki mywiki is user:mywiki:XWiki.MyUser.

Since 9.0RC1 it's possible to use org.xwiki.component.namespace.Namespace as helper to parse and manipulate namespace and it should be used more and more in various APIs.

Typed extension of org.xwiki.component.namespace.Namespace are also provided:

  • org.xwiki.model.namespace.UserNamespace since 9.6RC1
  • org.xwiki.model.namespace.DocumentNamespace since 9.6RC1
  • org.xwiki.model.namespace.SpaceNamespace since 9.6RC1
  • org.xwiki.model.namespace.WikiNamespace since 9.6RC1

Execute some code in the context of a different namespace

Since 10.5 and 9.11.6, a component has been introduced to be able to execute some code as if it were in a different context. The component is:

  • org.xwiki.component.namespace.NamespaceContextExecutor

Namespaced Component Manager

[since 6.4M2]

A ComponentManager instance associated to a specific namespace (a wiki, a user, etc.) will also implement org.xwiki.component.manager.NamespacedComponentManager which provide the following methods:

String getNamespace();

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
@Singleton
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.

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

Don't use static to initialize variables in a component. First, using static is an anti pattern and we have the notion of Singleton in Components for that purpose. In addition if you do this then your component will get the static initialized when the Component Annotation Loader registers components instead of when your component is called the first time it's used, thus penalizing the startup cost of XWiki

Component Disposal

If your Component implementation needs to perform some cleanup when XWiki is stopped, you'll need to make it implement the org.xwiki.component.phase.Disposable interface. For example:

@Component
@Singleton
public class DefaultJobExecutor implements JobExecutor, Initializable, Disposable
{
   ...

   @Override
   public void dispose() throws ComponentLifecycleException
   {
       // Perform some cleanup here.
   }
}

You are then guaranteed that when XWiki stops or when your extension is uninstalled, your component's dispose() method will be called.

The order used by the ComponentManager to dispose its components is computed based on declared dependencies.

XWiki 6.2.1+ However there might be cases when you wish a Component's dispose() method to be called before or after all other components. In this case you can use the DisposePriority annotation. The default priority is 1000. A higher value will mean disposing after all other components not having this annotation. For example:

@Component
@Singleton
@DisposePriority(10000)
public class DefaultHibernateSessionFactory implements HibernateSessionFactory, Disposable
{
...
   @Override
   public void dispose() throws ComponentLifecycleException
   {
      ... called after other components in order to shut down the database late...
   }
...
}

Component Logging

If your Component implementation needed to log something it can get injected a SLF4J Logger automatically. For example:

import org.slf4j.Logger;
...
@Component
@Singleton
public class DefaultObservationManager implements ObservationManager
{
   @Inject
   private Logger logger;

   public void doSomething()
   {
       this.logger.info("Some info level logging");
       ...
   }
}

Priori to 3.1, if your Component implementation needed to log something it would have needed to implement the org.xwiki.component.phase.LogEnabled interface. However in order to make it even easier we were providing a org.xwiki.component.logging.AbstractLogEnabled class that your component could simply extend.

For example:

@Component
public class DefaultObservationManager extends AbstractLogEnabled implements ObservationManager
{
   public void doSomething()
   {
        getLogger().info("Some info level logging");
       ...
   }
}

See the Logger interface for more details on the logging API.

Component Metadata

XWiki 6.4 If your Component implementation needs to get access to its component metadata (i.e. its ComponentDescriptor) then it can get it injected automatically. For example:

import org.xwiki.component.descriptor.ComponentDescriptor;
...
@Component
@Singleton
public class MyComponentImpl implements MyComponent
{
   @Inject
   private ComponentDescriptor<MyComponent> descriptor;

   public void doSomething()
   {
        String hint = this.descriptor.getRoleHint();
       ...
   }
}

Component Manager Initialization

If you're running your code inside an XWiki Enterprise environment (for example) then you won't need to bother about initializing the Component Manager since this is done automatically by the Container Module.

However if you're using XWiki as a library in your own application you'll need to initialize the Component Manager to be able to perform look ups of other Components.

This is easily achieved by using this:

// Initialize Rendering components and allow getting instances
EmbeddableComponentManager componentManager = new EmbeddableComponentManager();
componentManager.initialize(this.getClass().getClassLoader());

[Since 5.0] When you don't need this manager and its components you should also make sure to call the following method 

componentManager.dispose()

to free all resources that components of this manager might hold.

Access from Scripts

There's a component script service that can be used to get the Component Manager and Component instances. For example from Groovy:

def cm = services.component.componentManager
def instance = services.component.getInstance(SomeRole.class)
def instance = services.component.getInstance(SomeRole.class, "some hint")

Note that getInstance calls will return null if the Component cannot be looked up and the exception can be retrieved using services.component.lastError.

In Velocity getInstance methods are accessible since 6.1.

Now if you need to register a new Component implementation you'll need to get hold of the Component Manager in which you wish to register it (Root Component Manager or Component Manager specific to a given subwiki) by using the getComponentManager(String namespace) API. 

For example to register your component in the Root Component Manager (i.e. it'll be visible from the main wiki and in all subwikis), you'd write in Groovy:

In 6.4.1 and later:

def cm = services.component.getRootComponentManager()
cm.registerComponent(...)

Before 6.4.1:

def cm = services.component.getComponentManager(null)
cm.registerComponent(...)

And to register your component so that it's visible only in the mywiki subwiki, you'd write in Groovy:

def cm = services.component.getComponentManager("wiki:mywiki")
cm.registerComponent(...)

or only for the global user with the reference xwiki:XWiki.MySelf:

def cm = services.component.getComponentManager("user:xwiki:XWiki.MySelf")
cm.registerComponent(...)

Overriding Components from a Wiki Page

There are 2 solutions for doing this:

If you wish to override only some methods of an existing component you can use the Solution 1, by creating a class that extends the existing component's class and that overrides only the method you wish to modify. Then, you register your component over the existing one.

One advantage of Wiki Components (Solution 1) is that they are registered when XWiki starts, whereas if you choose Solution 2, you need to handle registration yourself. If you wish to use Solution 2 but still have your component registered at startup you'll need to implement an EventListener component and listen to the ApplicationReadyEvent or the WikiReadyEvent (for subwikis), and perform the component registration in the onEvent() method.

Component Events

There are several events related to Components that you could listen to, for example in order to execute some code whenever a component is registered:

  • org.xwiki.component.event.ComponentDescriptorAddedEvent: Event sent to tell that a new Component Descriptor has been registered.
  • org.xwiki.component.event.ComponentDescriptorRemovedEvent: Event sent to tell that a new Component Descriptor has been unregistered.

Tutorial

See the "Writing an XWiki Component" tutorial.

Internals

The default ComponentManager implementation is in EmbeddableComponentManager (in the xwiki-commons-component-default module). It's instantiated in the XWikiServletContextListener class (in the xwiki-platform-container-servlet module).

Accessing components in legacy code

The XWiki project is older that this component concept and sometimes you can find yourself in code which cannot really inject components, you should ideally try to convert it to the components world. But sometimes it's simply not possible(XWiki authenticators, Java based schedulers, etc). You have workarounds provided by com.xpn.xwiki.web.Utils, specifically:

  Utils.getComponent(SomeRole.class)
  Utils.getContextComponentManager()
  Utils.getRootComponentManager()

Get Connected