Table of Contents
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!
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.
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.
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:
JBoss OSGi adapter – used to deploy OSGi bundles on JBoss AS 4.x
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.
Currently, Nuxeo Runtime adapters can provide the following OSGi features:
OSGi Bundle deployment
The manifest is loaded, the class path processed and the bundle activator instantiated
BundleActivator support
Activators are notified each time a bundle is started and stopped
Fake Bundle and BundleContext implementations that adapts OSGi operations to native operations of the host platform
Thus, Bundle Activators can use the common operations defined by the OSGi API
Bundle lifecycle and framework events support
Bundle dependencies (as specified in the manifest)
The following OSGi features are not supported (yet):
The OSGi service layer
The OSGi security layer
The OSGi class-loading specifications (the class-loading mechanism of the host platform is used)
Some methods of interfaces Bundle and BundleContext
(unimplemented Methods will thrown an
UnsupportedOperationException
exception)
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.
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
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”.
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.
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.
Life Cycle Events: component life cycle events are fired by the runtime to anyone interested in. See Adaptable Components for a common use case.
OSGi integration: the component model is about to be fully integrated with OSGi and will be soon compliant with the OSGi declarative service model.
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).
Complete integration with OSGi declarative services specifications
Component lookup through JNDI
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)
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.
A component has 3 main life cycle states:
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.
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.
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:
XXX ADD GRAPHIC HERE
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
Here is the list of some use cases of the extension mechanism identified in the context of the Nuxeo ECM Platform:
to define actions and menus
to define content schemas (by importing XSD files)
to define views (view ids mapped to JSF/XHTML pages)
to define content objects (associate a Content Schema with a class that will provide required methods for the content object)
to define permissions (usable in security annotations)
to define PageFlows for Seam that, optionally, can extend existing ones
to define business processes (in jBPM)
to define content transformations (doc -> pdf, doc -> html, odf -> pdf, odf -> html, etc.)
to define rules for the rule engine (that can be bound to some objects to run a rule only in a specific folder)
scriptable extensions that define scripts binded to interpreters like JavaScript, Groovy, Jython, JRuby, etc
to define JMS/Event queues
to define event types
to define security policies
to define Access Control Policies
to define NXCore storage backends (JCR, SQL, LDAP, etc.)
to define query engines
to define indexing engines
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:
The adapter service (nx:service=adapter
)
deploys OSGi bundles and declared components
provides information about deployed bundles and components through the JBoss JMX Console
The XML component deployer
(nx:name=bundleDeployer,type=deployer
) that can
deploy XML descriptors as OSGi components
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
Components may be created either from XML descriptor files, or programatically.
In order to register components you always need a 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:
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.
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.
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.
To create a component using its XML description, follow these steps:
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>
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”);
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); }
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
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);
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:
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; }
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 { ... } ... }
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(); }
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.
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.
The name
attribute.
This should be unique relative to the parent component and is used to identify the extension points inside a component.
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:
listeners
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>
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.
target
The target attribute specifies the name of the component providing the extension point
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.
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 ... }
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:
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.
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.
property
This element can be used to define random properties that will be available later to the component when it will be created.
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.
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.
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.
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.
To load a whole OSGI bundle, use the deployBundle
method, whose parameter is the bundle symbolic name, as specified in
its manifest.
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.
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.
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")); } }
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.
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
.
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."
Nuxeo.org website: http://www.nuxeo.org/
OSGi website: http://www.osgi.org/
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