Nuxeo Runtime

Table of Contents

27.1. Overview
27.1.1. Main Goals
27.1.2. Main Features
27.2. What is OSGi?
27.3. OSGi Support
27.3.1. Supported Features
27.3.2. Unsupported Features
27.3.3. Planned Features
27.4. Component Model
27.4.1. What are components?
27.4.2. Main Features
27.4.3. Planned Features
27.4.4. Adaptable Components
27.4.5. Flexible Model
27.4.6. Component Life Cycle
27.4.7. Component Extensibility
27.5. Supported Host Platforms
27.5.1. JBoss Integration
27.5.2. Eclipse Integration
27.6. Using Nuxeo Runtime
27.6.1. Creating Components
27.6.2. Using components
27.6.3. XML Component Descriptors
27.7. Integration tests for Nuxeo Runtime applications
27.7.1. The NXRuntimeTestCase base class
27.7.2. Frequent patterns
27.8. Detailed Architecture
27.9. References

27.1. Overview

Nuxeo Runtime is the foundation of the Nuxeo infrastructure. It handles deployment and extensibility of components to target platforms. This component allows the whole Nuxeo infrastructure to be easily ported between Java platforms (Java EE, OSGi, etc.) and features an easy plug-in mechanism that any component can use to declare extension points that can be used by other components to extend the former one.

Nuxeo Runtime uses the OSGi component model and a set of adapters to deploy POJO components to Java host platforms, such as Eclipse/Equinox, or a Java EE 5 application server such as JBoss or WebLogic. When deployed, Nuxeo Runtime components become actual host platform components. For example on JBoss the component is seen as a MBean, while when deployed on Geronimo it is seen as a GBean and on Eclipse it is seen as a native Eclipse plug-in. In short, Nuxeo Runtime offers a new and seamless way to make your Java EE applications and components extensible (as Eclipse developers are already used to).

Nuxeo Runtime is not specific to the Nuxeo platform, it is a generic deployment and extension system that can be used in any Java or Java EE application.

Forget specific build of your applications for a dedicated project or customer and enjoy “Code once, deploy anywhere” for real!

27.1.1. Main Goals

One of the main requirements of the “Nuxeo Core” component is to be deployable on both the JBoss and Eclipse platforms. To ease development and allow as much as code reuse, this requirement raised the need of a common component and packaging model that may be deployed and used on both of these platforms without any code change or repackaging.

To fulfill these needs, Nuxeo Runtime was developed as the foundation layer for all Nuxeo components. Nuxeo Runtime is not a standalone framework. It is, basically, a component model running on top of an existing platform and providing a common, platform-independent, model to power the applicative components of an application. It is our component architecture allowing flexible and true componentization of applications.

Besides its component model, Nuxeo Runtime also defines a common model for packaging. The adopted model is the OSGi bundle model. Actually, OSGi bundles are regular JARs containing an OSGi manifest file.

OSGi technology is more and more popular and is currently used by Eclipse, Geronimo and Jonas as their runtime framework.

This way, applications based on Nuxeo Runtime can run on different platforms without modifications by using an single component and packaging model - without having to care about platform specificity.

27.1.2. Main Features

The main features provided by Nuxeo Runtime are:

  1. Native OSGi support

  2. Extensible component model through extension points

  3. Adapters to support host platforms (JBoss and Eclipse support is built-in by default)

27.2. What is OSGi?

OSGi (Open Services Gateway initiative) is an open standards organization founded by Sun Microsystems, IBM, Ericsson and others in March 1999.

OSGi defines a modular and complete Java-based service framework. The deployment units used by this framework are called bundles, so we will refer them as OSGi bundles.

OSGi bundles are normal Java libraries (JAR files) containing a special manifest file (META-INF/MANIFEST.MF) describing all aspects related to the bundle, like: the bundle name, description, bundle dependencies, exported packages, the bundle classpath, the bundle activator and many other OSGi-defined features.

Here is a typical OSGi manifest file:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: NxRuntimeEclipseDemo Plug-in
Bundle-SymbolicName: org.nuxeo.runtime.demo.eclipse.Demo; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: org.nuxeo.runtime.demo.eclipse.demo.Activator
Bundle-Vendor: Nuxeo
Bundle-Localization: plugin
Require-Bundle: org.eclipse.ui,
  org.eclipse.core.runtime,
  org.nuxeo.runtime.demo.HelloWorld,
  org.nuxeo.runtime

The bundle activator is a Java object that is called whenever the bundle is started and stopped by the framework. This is the only way available to the application to access the framework functionalities.

Besides bundles and bundle management, OSGi provides a service registry and an API to register the services provided by a bundle and to lookup these services. Also, OSGi defines a Declarative Services specification that significantly simplifies the service-oriented programming model. Through this model, services can be defined in XML files inside the bundle and automatically deployed by the framework.

For a complete definition of OSGi, see Wikipedia.

One of the main goals of Nuxeo Runtime is to natively support OSGi frameworks and to use the OSGi bundle model for packaging and deployment. A second goal is to align the Nuxeo Runtime component model with the OSGi Declarative Specifications.

27.3. OSGi Support

Nuxeo Runtime provides built-in integration over OSGi-compliant frameworks. This means Nuxeo Runtime-based components can run “as is” on any OSGi enabled platform.

On other platforms like JBoss, an adapter is required. Nuxeo Runtime eases the creation of such adapters by providing an abstract OSGi adapter that can be customized for any platform.

Note: it doesn't mean you can transform any platform into a fully OSGi-compliant platform using Nuxeo Runtime adapters. This is mainly due to the fact that the adapter is using in the background the host platform's class-loading and deployment model that is incompatible with OSGi specifications.

Adapters only mimic an OSGi environment, using native host platform features, for applications running on top of Nuxeo Runtime.

Many OSGi features are not yet provided by the adapter – but we hope to add more and more features. If you are interested in helping on this, do not hesitate :-).

Currently, one of the most important features that is missing is OSGi service support, but we are working on this and hope to provide it soon.

When running on OSGi-enabled platforms, no adapter is used and thus all OSGi features are available as given by the host platform. Components are running natively on the OSGi platform without any alteration.

Currently we provide two built-in adapters:

  1. JBoss OSGi adapter – used to deploy OSGi bundles on JBoss AS 4.x

  2. Test OSGi adapter – used for JUnit testing and can be used on any simple Java application that is not using a complex class loading or deployment mechanism.

27.3.1. Supported Features

Currently, Nuxeo Runtime adapters can provide the following OSGi features:

  1. OSGi Bundle deployment

  2. The manifest is loaded, the class path processed and the bundle activator instantiated

  3. BundleActivator support

  4. Activators are notified each time a bundle is started and stopped

  5. Fake Bundle and BundleContext implementations that adapts OSGi operations to native operations of the host platform

  6. Thus, Bundle Activators can use the common operations defined by the OSGi API

  7. Bundle lifecycle and framework events support

  8. Bundle dependencies (as specified in the manifest)

27.3.2. Unsupported Features

The following OSGi features are not supported (yet):

  1. The OSGi service layer

  2. The OSGi security layer

  3. The OSGi class-loading specifications (the class-loading mechanism of the host platform is used)

  4. Some methods of interfaces Bundle and BundleContext (unimplemented Methods will thrown an UnsupportedOperationException exception)

27.3.3. Planned Features

  1. Supporting the OSGi service layer

  2. Implementing the OSGi declarative services based on the runtime component model

27.4. Component Model

The component model provides a flexible way to define, register and locate components. It was designed in order to reuse the same component model on very different platforms like JBoss and Eclipse.

A full support of OSGi declarative service specifications is planned in the mid-term future. Moreover, Nuxeo components can describe any kind of components, not only services.

27.4.1. What are components?

A definition from Wikipedia: “A software component is a system element offering a predefined service and able to communicate with other components”.

Components as defined by the Nuxeo model are logical units that may depend on and/or extend one another.

The Nuxeo Runtime is responsible for providing a common API to register, locate or extend components. Components are commonly registered using XML descriptor files.

Components can either be declared as subunits of an OSGi bundle or as independent components - as standalone XML files or programmatically registered components.

Components are commonly declared as part of an OSGi bundle through XML descriptors. To declare a component, you need to create an XML description file, put it somewhere in the bundle and specify the “Nuxeo-Component” header in the bundle manifest to load components at bundle activation.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorldExtension Plug-in
Bundle-SymbolicName: org.nuxeo.runtime.demo.HelloWorldExtension
Bundle-Version: 1.0.0
Bundle-Vendor: Nuxeo
Bundle-Localization: plugin
Require-Bundle: org.nuxeo.runtime.demo.HelloWorld
Nuxeo-Component: OSGI-INF/helloworld-extension.xml

27.4.2. Main Features

  1. Declarative components through XML descriptors: XML component descriptors are tightly integrated with OSGi – you can specify which components should be deployed at bundle installation phase by using a custom manifest header “Nuxeo-Component”.

  2. Dependency between components: components are installed only when all their prerequisites are met. If prerequisites are not met the components will be put in a pending state until their dependencies are completely resolved. In the same manner when uninstalling a component all the components depending on it will be moved to the pending state.

  3. Extensibility through extension points: each component can let other components extend itself by defining a set of extension points. Other components (or the component itself) may then plug extensions into one of the declared extension points. This flexible extension mechanism draw inspiration from the Eclipse extension points.

  4. Life Cycle Events: component life cycle events are fired by the runtime to anyone interested in. See Adaptable Components for a common use case.

  5. OSGi integration: the component model is about to be fully integrated with OSGi and will be soon compliant with the OSGi declarative service model.

  6. Platform Independence: the component model can be used on any platform. It provides a single API to register and look-up components – the Nuxeo Runtime native API may be used (and in the future, the OSGi service API will be available too).

27.4.3. Planned Features

  1. Complete integration with OSGi declarative services specifications

  2. Component lookup through JNDI

27.4.4. Adaptable Components

The runtime implementation may adapt registered components to native components of the host platform. This can be done using the component life cycle notifications.

The JBoss runtime implementation is already doing this to adapt runtime components into JBoss MBean services.

This way, components may be seamlessly integrated into the host platform, thus leveraging the host platform functionalities (for example, MBean service management on JBoss)

27.4.5. Flexible Model

You should not be afraid by the “component model” denomination. The runtime component model is not limiting in any way your objects nor imposing extra rules in the development. You don't need to modify your existing objects to derive or implement some runtime classes in order to plug them into the component registry. Objects implementing a component may be of any kind.

The only limitation is that component objects must have a public constructor without arguments (the default constructor) so that they can be instantiated via newInstance method on Class.

Anyway, if you want to benefit from the extension points mechanism or to respond to component life cycle events like activation or deactivation, then you should either implement the Component interface, or define some methods with a given signature in your object so that they will be called by using Java reflection. See below for more details on this.

In conclusion, the component model is not limiting your objects in any way, it only gives you the capability to register your components, extend and look them up in the same way on any platform supported by Nuxeo Runtime.

27.4.6. Component Life Cycle

A component has 3 main life cycle states:

  1. Registered: the component registration information was created and inserted into the registry. The component dependencies are not yet processed or resolved, so the component cannot be activated and would block other registrations depending on this component.

  2. ResolvedAll: dependencies of this component are satisfied. The component can be safely activated.

    Other unresolved components waiting for a resolved component are notified and if they have no more dependencies, they will be resolved too.

  3. ActivatedComponent: activation may occur immediately a component is resolved, or programmatically at the user request, or lazily the first time the component is referred. The only requirement for a component to be able to activate is to be resolved.

    Currently only the immediate activation mode is supported.

    When an activated component is deactivated it is put back in the resolved state. If it is unregistered it will be put in the resolved state then an unresolved event is fired and the component regresses to the registered sate and then it is removed from the registry.

    When a component is respectively activated or deactivated the runtime will invoke the activate, respectively the deactivate method of the component, if any. Implementing life cycle methods or not is the programmer's choice. These methods can be used to initialize and destroy the component in the given context. Components are not forced to implement any one of these methods.

    The activation of a component signifies the component is available to be used by other components so that the component should be correctly initialized when it enters this state. Here is the complete life cycle of a component:

    1. XXX ADD GRAPHIC HERE

27.4.7. Component Extensibility

One of the most important feature of the component model is the extension mechanism that enable components to extend one another.

How does it work? Imagine you have a component A that manages an action menu for the application. It wants to let other components contribute actions in an easy and flexible way – for example by using XML files to describe these actions.

To be able to do this, component A should declare an extension point, let's say “actions”. This way other components willing to contribute some actions to the action menu managed by the component A can contribute these actions to the extension point “actions” exposed by the component A.

Obviously one component may declare any number of extension points and any number of extensions contributed to other components.

Components may declare extension points and extension contributions using a simple XML syntax like the following:

<?xml version="1.0"?>
<component name="org.nuxeo.ecm.core.schema.TypeService">
  <implementation class="org.nuxeo.ecm.core.schema.TypeService"/>	
  <extension-point name="doctype">
    <object class="org.nuxeo.ecm.core.schema.DocumentTypeDescriptor"/>
  </extension-point>
  <extension-point name="schema">
    object class="org.nuxeo.ecm.core.schema.SchemaBindingDescriptor"/>
  </extension-point>	  
  <extension target="org.nuxeo.ecm.core.schema.TypeService" point="doctype">
    <doctype name="File" extends="Document">
      <schema name="common"/>
      <schema name="file"/>
    </doctype>
  </extension
</component>

You can see here a component declaring two extension points and contributing an extension to its own doctype extension point.

The content of an extension element is specific to the target extension point. The extension element content is known only by the extension point. How are handled extensions not conforming to the extension point schema is unpredictable – generally they will be ignored and some errors will be logged. There is, for now, no mechanism of validating XML extensions like in Eclipse.

But Nuxeo Runtime provides an easy way to map XML extensions to real Java objects through an XML mapping mechanism called XMap. You can see that each extension point in the example above is specifying an object tag. This means the content of the XML extension should be mapped through XMap to an object instance of this type. When no object tag is specified, extensions are returned as DOM elements and thus the component should perform itself the DOM parsing of extension contributions.

For details on the XML mapping, see the XMap documentation and/or the JavaDoc.

XXX TODO ADD GRAPHIC

27.4.7.1. Use Cases

Here is the list of some use cases of the extension mechanism identified in the context of the Nuxeo ECM Platform:

  1. to define actions and menus

  2. to define content schemas (by importing XSD files)

  3. to define views (view ids mapped to JSF/XHTML pages)

  4. to define content objects (associate a Content Schema with a class that will provide required methods for the content object)

  5. to define permissions (usable in security annotations)

  6. to define PageFlows for Seam that, optionally, can extend existing ones

  7. to define business processes (in jBPM)

  8. to define content transformations (doc -> pdf, doc -> html, odf -> pdf, odf -> html, etc.)

  9. to define rules for the rule engine (that can be bound to some objects to run a rule only in a specific folder)

  10. scriptable extensions that define scripts binded to interpreters like JavaScript, Groovy, Jython, JRuby, etc

  11. to define JMS/Event queues

  12. to define event types

  13. to define security policies

  14. to define Access Control Policies

  15. to define NXCore storage backends (JCR, SQL, LDAP, etc.)

  16. to define query engines

  17. to define indexing engines

27.5. Supported Host Platforms

27.5.1. JBoss Integration

Nuxeo Runtime provides a SAR package containing the Runtime and the JBoss OSGi adapter. This package is an OSGi bundle that acts as the OSGi system bundle.

Any package (directory, .sar, .jar, .ear, .war or any other JBoss-supported archive) will be treated as an OSGi bundle if it contains a valid OSGi manifest. In order for these bundles to be deployed, you need to have NXRuntime.sar already deployed in JBoss.

Besides the OSGi adapter and the auto-registration of components through bundle manifest, the JBoss adapter adds the capability to deploy runtime components as XML files located outside OSGi bundles through the JBoss deployment mechanism. This feature can be useful to register components that provide extensions to other components that can be described by plain XML without any code dependency.

Example of a plain XML component that contributes new document types:

<?xml version="1.0"?>
<component name="org.nuxeo.ecm.core.CoreExtensions">
  <extension target="org.nuxeo.ecm.core.schema.TypeService" point="doctype">
    <doctype name="File" extends="Document">
      <schema name="common"/>
      <schema name="file"/>
    </doctype>
    <doctype name="Folder" extends="Document">
      <schema name="common"/>
      <facet name="Folderish"/>
    </doctype>
    <doctype name="Workspace" extends="Document">
      <schema name="common"/>
      <facet name="Folderish"/>
    </doctype>
    <doctype name="Domain" extends="Document">
      <schema name="common"/>
      <facet name="Folderish"/>
    </doctype>
  </extension>
</component>
      

The NXRuntime.sar library offers two JMX services:

  1. The adapter service (nx:service=adapter)

    1. deploys OSGi bundles and declared components

    2. provides information about deployed bundles and components through the JBoss JMX Console

  2. The XML component deployer (nx:name=bundleDeployer,type=deployer) that can deploy XML descriptors as OSGi components

27.5.1.1. Installation

Deploy the NXRuntime.sar in JBoss, then deploy your OSGi bundles as common JBoss packages.

That's all, your bundles are deployed and activated.

27.5.2. Eclipse Integration

For Eclipse, a NXRuntime.jar bundle is provided. Since Eclipse is OSGi-compliant, Nuxeo Runtime will not install any adapter (so it is not intervening on the bundle deployment).

When running on OSGi platforms, the main role of the runtime is to register components declared inside OSGi bundles (as seen previously through their manifest).

Because Eclipse is not starting automatically OSGi bundles (it starts them only on demand or on class loading), you need to update Eclipse's config.ini and configure it to start Nuxeo Runtime (i.e. org.nuxeo.runtime) when Eclipse starts:

osgi.bundles=org.eclipse.equinox.common@2:start, org.eclipse.update.configurator@3:start, org.eclipse.core.runtime@start,org.nuxeo.runtime@start

27.5.2.1. Installation

Update the config.ini file as described above, then copy NXRuntime.jar inside the Eclipse plugin directory.

Start Eclipse. You're done!

27.6. Using Nuxeo Runtime

27.6.1. Creating Components

Components may be created either from XML descriptor files, or programatically.

In order to register components you always need a runtime context.

27.6.1.1. Runtime Context

A runtime context is the context where a component is registered. Contexts should be always associated to the bundle containing the component classes. Through the context, a component can access the runtime service and can load classes and retrieve resources from its bundle and other visible bundles.

RuntimeContext objects depend on the current implementation of the runtime service.

Nuxeo Runtime provides three implementations of the RuntimeContext interface:

  1. org.nuxeo.runtime.model.impl.DefaultRuntimeContext: this is a simple implementation of a context designed to be used outside of an OSGi environment. This context uses the current thread context class loader. It should only be used in simple Java applications like JUnit tests that are not supporting OSGi bundles.

  2. org.nuxeo.runtime.osgi.OSGiRuntimeContext: this context can be used on any platform supporting OSGi bundles. This context uses the bundle's ClassLoader to load classes and find resources.

    This is the right context to use when using Nuxeo Runtime and it is working on any platform as long as it is an OSGi platform or you have an OSGi adapter for it.

  3. org.nuxeo.runtime.jboss.JBossRuntimeContext: this is a JBoss specific context. It is used by the JBoss runtime implementation to load components deployed as standalone XML files. This context is wrapping the DeploymentInfo JBoss object.

Once you have a runtime context object, you can start registering components.

27.6.1.2. Creating components from XML descriptor files

To create a component using its XML description, follow these steps:

  1. Write the XML description of the component.

    Example of a simple XML descriptor:

    <?xml version="1.0"?>
    <component name="org.nuxeo.runtime.EventService">
      <implementation class="org.nuxeo.runtime.services.event.EventService"/>
      <extension-point name="listeners">
        <object class="org.nuxeo.runtime.services.event.ListenerDescriptor"/>
      </extension-point>
    </component>
    
  2. Load the XML file and register the component.

    In order to register a component, we always need a runtime context:

    // retrieve the current bundle
    Bundle bundle = ...
    // create a context given the current bundle object
    RuntimeContext context = new OSGiRuntimeContext(bundle);
    // load the component XML file given its location relative to the bundle root
    context.deploy(“OSGI-INF/MyComponent.xml”);

Note

The current bundle object is usually retrieved from a BundleActivator in the start(BundleContext context) method. You can also lookup other bundles by their symbolic names given a Bundle object.

The context has several method of deploying (e.g. installing) components. For example, the method used previously is identical to:

// load the component XML file given its location relative to the bundle root
URL url = context.getLocalResource(“OSGI-INF/MyComponent.xml”);
if (url != null) {
    context.deploy(url);
}

27.6.1.3. Automatic deployment of components

Another, and the easiest way to deploy components is to let the bundle deploy them when started.

This is the recommended method of deploying components.

This can be done by specifying the local paths of the XML description files inside the bundle manifest by using the Nuxeo-Component header as in the following example:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorldExtension Plug-in
Bundle-SymbolicName: org.nuxeo.runtime.demo.HelloWorldExtension
Bundle-Version: 1.0.0
Bundle-Vendor: Nuxeo
Bundle-Localization: plugin
Require-Bundle: org.nuxeo.runtime.demo.HelloWorld
Nuxeo-Component: OSGI-INF/MyComponent.xml, OSGI-INF/MySecondComponent.xml

This way, as soon as the bundle is started, the component will be automatically deployed.

XML component are contained as resource files in that bundle and their path should be specified as relative to the bundle root

27.6.1.4. Creating components programmatically

This method of creating components is not recommended since it is internal to Nuxeo Runtime and it depends on the implementation.

Here is an example on how you can use the API to manually register a component. We assume you are running in an OSGi environment and you have a reference to the bundle object containing the component you want to register.

// retrieve the current bundle
Bundle bundle = ...
RegistrationInfoImpl ri = new RegistrationInfoImpl();
// create a context associated to the current bundle
ri.context = new OSGiRuntimeContext(bundle); 
ri.name = new ComponentName(“my.component”);
// set the class name of the component to register
ri.implementation= “org.nuxeo.runtime.example.MyComponent”;
// register the component
Framework.getRuntime().getComponentManager().register(ri);

27.6.2. Using components

27.6.2.1. Responding to life cycle events

When a component is deployed, Nuxeo Runtime will check its dependencies and if all of them are resolved, the component is resolved and activated. (In the future, lazy activation or activation on demand will be supported too). If component dependencies are not satisfied, the component will be put in a pending queue until all of its dependencies are resolved.

When activating a component, the runtime will check if the component defines the activate life cycle method and if true, it will call it to get a chance to the component to initialize itself.

The same thing is done when deactivating the component - the runtime will check if the component defines the deactivate life cycle method and if true, it will call it to give a chance for the component to dispose itself properly.

There are two ways to define life cycle methods:

  1. By implementing the org.nuxeo.runtime.model.Component interface

    In this case, a cast to Component interface is performed and the life cycle methods are called.

    public interface Component extends Extensible {
        public void activate(RuntimeContext context) throws Exception;
        public void deactivate(RuntimeContext context) throws Exception;
    }
  2. By simply declaring a public or protected methods on the component object using the right signature.

    In this case the Java reflection mechanism is used to call the methods.

    public class MyComponent {
        ...
        public void activate(RuntimeContext context) throws Exception {
            ...
        }
        public void deactivate(RuntimeContext context) throws Exception {
            ...
        }
       ...
    }

27.6.2.2. Looking up components

After a component is activated, it can be retrieved using the Nuxeo Runtime API.

There are several methods to look-up a component:

  • Looking up the component by its name:

    HelloComponent hc = (HelloComponent) Framework.getRuntime().getComponent(
            "org.nuxeo.runtime.demo.HelloComponent");
  • Looking up the ComponentInstance object corresponding to this component. This object is a proxy to the component object:

    ComponentInstance ci = Framework.getRuntime().getComponentInstance(
            "org.nuxeo.runtime.demo.HelloComponent");
    if (ci != null) {
        HelloComponent hc = (HelloComponent) ci.getInstance();
    }

27.6.2.3. Working with extension points

Now let's take a look at how a component may define an extension point and how other components may use this extension point to contribute extensions.

Defining an extension point

A component may define any number of extension points. Extension points are identified inside a component by a unique name. We will describe here how to define extensions using the XML descriptor file. Extension points can also by created by hand using the internal API of Nuxeo Runtime but this is no recommended and it is not documented here.

Extension points are specified in the XML component descriptor using the extension-point tag. This tag has a required attribute name and one or more optional object sub-tags.

  1. The name attribute.

    This should be unique relative to the parent component and is used to identify the extension points inside a component.

  2. The object sub-tag can be used to define what kind of objects are contributed by XML extensions. These objects will be created from the extension XML fragment by using the XMap engine that maps XML to Java objects through through Java annotations.

    If no object sub-tag is specified, the extension will be contributed as a DOM element.

    The object tag has a required class attribute that specifies the class name of the objects to contribute.

    The object class will be loaded using the context of the bundle that defined the extension point.

Example of a component declaring two extension points:

  1. listeners

  2. asyncListeners

    <?xml version="1.0"?>
    <component name="org.nuxeo.runtime.EventService">
      <implementation class="org.nuxeo.runtime.services.event.EventService"/>
      <extension-point name="listeners">
        <object class="org.nuxeo.runtime.services.event.ListenerDescriptor"/>
      </extension-point>
      <extension-point name="asyncListeners">
        <object class="org.nuxeo.runtime.services.event.AsyncListenerDescriptor"/>
      </extension-point>
    </component>

Contributing an extension

Once a component declaring some extension points has been activated, other components may contribute extensions to that extension point.

To declare an extension, the extension tag is used. This tag must contains a target and a point attribute.

  1. target

    The target attribute specifies the name of the component providing the extension point

  2. point

    The point attribute is the extension point name.

The extension element may contain arbitrary XML. The actual XML content is recognized only by the extension point to where the extension is contributed. This means you should know the correct format for the extension XML.

For this reason, it is important for components to document their extension points. If the extension point is using XMap to map XML to Java objects, then you can use annotations existing on the contribution object class to know the XML format. These annotations are easy to understand and can be used as well as a documentation for the XML extension format.

If you are familiar with Eclipse extension points, you may wonder why Nuxeo Runtime is not using an XSD schema to define the content of an XML extensions. The reason is simple: because inside our ECM project we need to be able to define any type of XML content - even configuration files from external tools we use like for example a Jackrabbit repository configuration. Defining and maintaining XSD schemas for this kind of extensions would be painful.

Anyway, using XMap to map extensions to real Java objects makes it easy to use extensions.

Here is an example on how a component is declaring some contributions to the previously defined extension points:

<?xml version="1.0"?>
<component name="my.component">
  <implementation class=”MyComponent"/>
  <extension target="org.nuxeo.runtime.EventService" point="listeners">
    <listener class="org.nuxeo.runtime.jboss.RepositoryAdapter">
      <topic>repository</topic>
    </listener>
    <listener class="org.nuxeo.runtime.jboss.ServiceAdapter">
      <topic>service</topic>
    </listener>
  </extension>
</component>

You can see how the component is declaring an extension to the listeners extension point defined by the component org.nuxeo.runtime.EventService

The result of this declaration is that the EventService will register two listeners, one listening on events from the “repository” topic, the other on events from the “service” topic.

Registering contributed extension

Extensions are contributed to the target extension point immediately after the component declaring these extensions is activated. If the target component (the component declaring the extension point) was not yet activated, the contributed extensions are put in a pending queue and they will be contributed as soon as the target component is activated.

A component willing to declare extension points and accept contributed extensions should declare two protected or public methods: registerExtension and unregisterExtension.

This can be done either by implementing the Component interface, or by declaring these methods with their correct signatures on the component object (as we have seen before for the life cycle methods).

These two methods should have the following signature:

public interface Extensible {

    public void registerExtension(Extension extension) throws Exception;

    public void unregisterExtension(Extension extension) throws Exception;

}

Note that the Extensible interface is extended by the Component interface.

When an extension is contributed the registerExtension method is called with an argument that points to the actual contributed extension as an Extension object.

Components should use this method to do something with the extension (usually to register it somewhere).

When the component contributing the extension is deactivated, the Runtime will call the unregisterExtension method using the same Extension object as a parameter. This gives a chance to the extended component to unregister extensions when they become inactive.

Here is an example of how extensions are registered and unregistered:

public class HelloComponent implements Component {
    public final static ComponentName NAME 
        = new ComponentName("org.nuxeo.runtime.demo.HelloComponent");

    Collection<HelloMessage> messages = new ArrayList<HelloMessage>();
    
    public void registerExtension(Extension extension) throws Exception {
        Object[] messages = extension.getContributions();
        for (Object message: messages) {
            HelloMessage msg = (HelloMessage)message;
            this.messages.add(msg);
            System.out.println("Registering message: " + msg.getMessage());
        }
    }

    public void unregisterExtension(Extension extension) throws Exception {
        Object[] messages = extension.getContributions();
        for (Object message: messages) {
            HelloMessage msg = (HelloMessage)message;
            this.messages.remove(msg);
            System.out.println("Un-Registering message: " + msg.getMessage());
        }
    }
...
}

You can see how the contributed objects are fetched from the Extension object and then registered into a Java Map. These contributions are objects of type HelloMessage as defined by the extension point (using the object sub-element)

The contributions are also available as a DOM element so you can use this to retrieve contributions in the case you don't use XMap to map XML extensions to Java objects. This DOM element is corresponding to the extension element from the XML component descriptor.

So if you need to retrieve the DOM representation of the extension, you can do:

public void registerExtension(Extension extension) throws Exception {
    Element element = extension.getElement();

    // parse yourself the DOM element and extract extension data
    ...
}

27.6.3. XML Component Descriptors

In this section we will describe the most important elements composing an XML component descriptor.

You can inspect the XMap annotations on the class org.nuxeo.runtime.model.impl.RegistrationInfoImpl to find all elements that may compose an XML component descriptor.

A complete component descriptor may look like this:

<?xml version="1.0"?>
<component name="org.nuxeo.ecm.core.schema.TypeService">
    <implementation class="org.nuxeo.ecm.core.schema.TypeService"/>

    <require>org.nuxeo.ecm.core.api.ServerService</require>
    <require>org.nuxeo.ecm.core.repository.RepositoryService</require>

    <property name="author">Bogdan Stefanescu</property>
    <property name="description">The component description ...</property>

    <extension-point name="doctype">
        <object class="org.nuxeo.ecm.core.schema.DocumentTypeDescriptor"/>
    </extension-point>
    <extension-point name="schema">
        <object class="org.nuxeo.ecm.core.schema.SchemaBindingDescriptor"/>
    </extension-point>

    <extension target="org.nuxeo.ecm.core.api.ServerService"
               point="clientFactory">
        <factory class="org.nuxeo.ecm.core.api.impl.LocalClientFactory"/>
    </extension>
    <extension target="org.nuxeo.ecm.core.schema.TypeService" point="schema">
        <schema name="common" src="schema/common.xsd"/>
        <schema name="core-types" src="schema/core-types.xsd"/>
        <schema name="file" src="schema/file.xsd"/>
    </extension>
</component>

Each component is defined inside its own file. As you can see the root element is component. This element has a required attribute name. Apart this, all other sub-elements are optional.

Here is a list with all supported sub-elements:

  1. implementation

    This element is used to specify the component implementation class. The element is not required since one may define plain XML components only for contributing some extensions to other components. We will refer to these components as extension components.

  2. require

    This element can be used to specify dependencies on other components. The component will be resolved and activated only after all these dependency are resolved.

  3. property

    This element can be used to define random properties that will be available later to the component when it will be created.

  4. extension-point

    This element is used to declare extension points. A component may declare any number of extension points.

    See more details on this in Working with extension points section.

  5. extension

    This element can be used to declare extensions to other components (or to the current component itself).

    See more details on this in Working with extension points section.

27.7. Integration tests for Nuxeo Runtime applications

While it's obviously a good thing to unit-test one's code, it's usually not enough for a module designed to be ran as part of a Nuxeo Runtime application. It's indeed likely in this situation that the module will depend on services provided by other modules, and even maybe on their default configuration. It's even not uncommon that a project-specific module consists almost exclusively of calls to generic services provided by the base software platform.

27.7.1. The NXRuntimeTestCase base class

org.nuxeo.runtime.test.NXRuntimeTestCase is a base class to write JUnit tests for Nuxeo Runtime applications. It sets up the Nuxeo Runtime environment (in the setUp method) and provides methods to control bundle and resources loading. It is designed to behave in the same manner in Maven and Eclipse situations. Therefore resources must be accessed in a way that does not depend on the actual ordering of classpath.

27.7.1.1. Loading a bundle

To load a whole OSGI bundle, use the deployBundle method, whose parameter is the bundle symbolic name, as specified in its manifest.

27.7.1.2. Loading one contribution from a bundle

Loading a whole bundle can be too heavy, or bring unwanted default configurations. Therefore, the deployContrib method is provided to load just a resource (service definition, extension point contribution, etc.) from a given bundle. It takes two arguments: the bundle symbolic name, and the path to the contrib from the top of bundle.

27.7.1.3. Loading a test resouce

For resources from the test packages, just make an OSGI bundle of the test package, which can be done by creating the META-INF/MANIFEST.MF at the top of the target jar, and use deployContrib as above.

27.7.1.4. Sample usage

The following is an excerpt from org.nuxeo.project.sample:

public class TestBookTitleService extends NXRuntimeTestCase {

    private BookTitleService service;

    private static final String OSGI_BUNDLE_NAME = "org.nuxeo.project.sample";

    private static final String OSGI_TEST_BUNDLE = "org.nuxeo.project.sample.tests";

    public void setUp() throws Exception {
        super.setUp();
        // deployment of the whole nuxeo-project sample bundle
        deployBundle(OSGI_BUNDLE_NAME);

        service = Framework.getService(BookTitleService.class);
    }

    public void testServiceContribution() throws Exception {
        // Lookup is ensured simply by making the 'test' sub-hierarchy a
        // bundle of its own, with a MANIFEST file
        deployContrib(OSGI_TEST_BUNDLE, "sample-booktitle-test.xml");
        assertEquals("FOOBAR Test", service.correctTitle("foobar"));
    }

}

27.7.2. Frequent patterns

While working on an integration test, it is always worthwhile to prescribe clearly what is to be tested: either API calls to services provided by other modules, consistency with configuration provided by other modules, default configuration for the module being tested. This is especially important for the non-regression aspect of the testing and the cost of maintaining the tests.

The use-cases discussed below require basic knowledge of the Nuxeo ECM framework. Implicitely, it is somehow assumed that the tested code has to interact with one service. In case of multiple target services, one would have to choose a pattern for each of them.

27.7.2.1. Integration test against base services with testing configuration

We want to check that the API calls from the tested component to other components have the desired effect, but we don't want to rewrite the tests each time the default configuration of the other components change. Typically, this means that we need to deploy the xml contributions that define the services we need, together with the minimal configuration to tie it up together.

Example: the search service is able to configure its indexes automatically from the schemas and core types declaration. We don't want to have to update tests if someone changes the default config that ships with nuxeo-core. Ideally, this test should use deployBundle to set up core services, test repository, etc. and then work on dedicated schemas and core types that are loaded by deployContrib.

27.7.2.2. Integration test against base services and their default configuration

One can imagine here a core event listener that uses a given schema, a component that needs access to the search service to manipulate some specific documents...

In this case, we need to load the base service and its configuration exactly as they are in the real application and we do want the test to catch errors that are due to a change in said configuration. In this pattern, we'd use deployBundle all over the place.

It's likely however that one does not want the test to rely on the default (if any) configuration of the module being tested. If the tested component doesn't carry its configuration but still needs to be deployed within Nuxeo Runtime, deployBundle can be used on itself, and then deployContrib for the test configuration, after the test package has been upgraded to an OSGI bundle.

Variant: testing of a component and the configuration that comes along. Just think of your tested module as a "base service."

27.7.2.3. Reusing test resources from another component

This is usually neither possible nor recommended. Such situations do appear in the Nuxeo code base, and the proper solution is to provide the wished resources or classes from a package, precisely like org.nuxeo.ecm.runtime.test does.

27.8. Detailed Architecture

27.9. References

  1. Nuxeo.org website: http://www.nuxeo.org/

  2. OSGi website: http://www.osgi.org/

  3. JSR 277, 291 and OSGi, Oh My! - OSGi and Java Modularity, presented by Richard S. Hall at ApacheCon Europe 2006: http://docs.safehaus.org/download/attachments/2995/osgi-apachecon-20060628.pdf