This page describes how the Nuxeo Platform is modular, and how bundles, components and extension points relate to each other to let you create a fully customized application.
The Goal: Easy Customization and Integration
One of the main goals of the Nuxeo Platform is to provide an easy and clean way to customize the platform for your application needs. That way:
- No need to hack the system to make it run
- Your custom code will be based on maintained extension points and interfaces and will be able to be easily upgraded
For that, Nuxeo Platform provides the following patterns:
- Bundle: A bundle is a "plugin". It is most of the time a ".jar" file with a specific structure that aims at deploying a new set of features on the Nuxeo server. Thanks to this "bundle" notion, developers can deliver their new features in a standalone JAR that the platform will know how to start. As a result, your customization is also delivered as a plug-in, like the 10s of plug-ins that are part of the Nuxeo ecosystem, and that you can find on GitHub or the Nuxeo Marketplace.
- Components and services: A component is a software object declared via XML (and that may reference a Java class) that is used to expose some services in the framework. Thanks to this architecture, it is possible to expose a new service anywhere in the Java code executed in the platform. Services are auto-documented: you can see the list on Nuxeo Platform Explorer.
- Extensions: An extension is a mechanism leveraged by the services to let platform users inject customization in the core of the implementation. It is a pattern used frequently on products such as Mozilla, Chrome, or Eclipse. Thanks to this architecture, it is possible to go very deep in product customization only with XML or using our Nuxeo Studio visual environment, without any coding. You can see the list of all extension points in Nuxeo Platform Explorer. Contributions to extensions are delivered in a custom bundle.
Implementing your own bundle, your will be able to contribute to existing extensions so as to customize things. For instance, you can:
- Define custom schemas and Document types (supported by Nuxeo Studio),
- Define custom forms (supported by Nuxeo Studio),
- Define custom lifecycles (supported by Nuxeo Studio),
- Enforce business policies:
- Use content automation (supported by Nuxeo Studio),
- Write custom listener scripts,
- Customize the UI:
- Make your own branding (supported by Nuxeo Studio),
- Add buttons, tabs, links, views (supported by Nuxeo Studio),
- Build your own theme via the ThemeManager,
- Add workflows.
When existing extensions are not enough, you can declare your own services or leverage existing ones in Java code.
In Nuxeo, everything can be configured:
Bundles, Component, Services and Extension Points
Inside Nuxeo Platform, software parts are packaged as Bundles. A bundle is a Java archive (JAR) packaged so that it works inside a Nuxeo Application.
- An OSGi-based manifest file,
- Java classes,
- XML components,
- A deployment descriptor.
The manifest file is used to:
- Define an id for the bundle,
- Define the dependencies of the bundles (i.e.: other bundles that should be present for this bundle to run),
- List XML components that are part of the bundle.
Here is an example of a MANIFEST file:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: NXCoreConvert Bundle-SymbolicName: org.nuxeo.ecm.core.convert Bundle-Localization: plugin Require-Bundle: org.nuxeo.ecm.core.api, org.nuxeo.ecm.core.convert.api Bundle-Vendor: Nuxeo Export-package: org.nuxeo.ecm.core.convert.cache, org.nuxeo.ecm.core.convert.extension, org.nuxeo.ecm.core.convert.service Bundle-Category: runtime Nuxeo-Component: OSGI-INF/convert-service-framework.xml
Here we can see that this bundle:
- Is named
- Depends on two other bundles:
- Contains one XML component:
Nuxeo bundles are deployed on the Nuxeo server via the Nuxeo runtime that behaves partially like an OSGi framework. An OSGi framework provides:
- A lifecycle model for Java modules,
- A service model.
When an OSGi framework starts it will try to load all bundles installed in the system. When all dependencies of a bundle are resolved, the framework starts the bundle. Starting a bundle means invoking a bundle activator if any is declared in the manifest file.
This way each bundle that registers an activator is notified that it was started - so the bundle activator can do any initialization code required for the bundle to be ready to work. In the same way when a bundle is removed the bundle activator will be notified to cleanup any held resources. OSGi frameworks provides listeners to notify all interested bundles on various framework events like starting a bundle, stopping another one, etc.
This mechanism provides a flexible way to build modular applications which are composed of components that need to take some actions when some resources are become available or are removed. This lifecycle mechanism helps bundles react when changes are made in the application. Thus, an OSGi bundle is notified when all its dependencies were resolved and it can start providing services to other bundles. OSGi is also proposing a service model - so that bundles can export services to other bundles in the platform.
There are two major differences between the default Nuxeo Runtime launcher and an OSGi framework:
- Nuxeo is using single class loader for all bundles. It doesn't interpret OSGi dependencies in the manifest.
- Nuxeo services are not exposed as OSGi services.
A component is a piece of software defined by a bundle used as an entry point by other components that want to contribute some extensions or to ask for some service interface. A component is an abstract concept - it is not necessarily backed by a Java class and is usually made from several classes and XML configuration.
The XML components are XML files, usually placed in the
OSGI-INF directory, that are used to declare configuration to Nuxeo Runtime.
Each XML component has a unique id and can:
- Declare requirement on other components,
- Declare a Java component implementation,
- Contain a XML contribution,
- Declare a Java contribution.
A Java component is a simple Java class that is declared as component via an XML file.
Components usually derive from a base class provided by Nuxeo Runtime and will be available as a singleton via a simple Nuxeo Runtime call:
Usually, components are not used directly, they are used via a service interface. For that, the XML components can declare which service interfaces are provided by a given component. The component can directly implement the service interface or can delegate service interface implementation to an other class. Components must be accessed using the interfaces they provide and not through real implementation classes. Once declared the service will be available via a simple Nuxeo Runtime call:
The list of existing services can be found on the Nuxeo Platform Explorer.
One of the corner stones of the Nuxeo Platform is to provide components and services that can easily be configured or extended. For that, we use the extension point system from Nuxeo Runtime that was inspired from Equinox (Eclipse platform).
This extension point system allows you to:
- Configure the behavior of components (i.e. contribute XML configuration),
- Extend the behavior of components (i.e. contribute Java code or scripting).
Basically, inside the Nuxeo Platform, the pattern is always the same:
- Services are provided by components,
- Components expose extension points.
The same extension point system is used all over the platform:
- Inside Nuxeo Runtime itself,
- Inside Nuxeo Core (configure and extend document storage),
- Inside the Nuxeo Service layer (configure and extend ECM services),
- Inside the UI layer (assemble building blocks, contribute new buttons or views, configure navigation, ...).
Each Java component can declare one or several extension points.
These extension points can be used to provide:
- Additional code (i.e. plug-in system).
So most Nuxeo services are configurable and pluggable via the underlying component.
Declaring an Extension Point
Extension points are declared via the XML component that declares the Java component.
Here is a simple example:
<?xml version="1.0"?> <component name="org.nuxeo.ecm.core.convert.service.ConversionServiceImpl"> <documentation> Service to handle conversions </documentation> <implementation class="org.nuxeo.ecm.core.convert.service.ConversionServiceImpl"/>* <service> <provide interface="org.nuxeo.ecm.core.convert.api.ConversionService"/>* </service> <extension-point name="converter"> <documentation> This extension can be used to register new converters </documentation> <object class="org.nuxeo.ecm.core.convert.extension.ConverterDescriptor"/> </extension-point> <extension-point name="configuration"> <documentation> This extension can be used to configure conversion service </documentation> <object class="org.nuxeo.ecm.core.convert.extension.GlobalConfigDescriptor"/> </extension-point> </component>
What we can read in this XML component is:
- A Java component (via the
<component>tag) with a unique id (into the
nameattribute) is declared;
- This component declares a new service (via the
- The declaration of the
ConvertServiceinterface (used to also fetch it) implemented by
- This service expose two extension points:
- One to contribute configuration (
- One to contribute some Java code (new
- One to contribute configuration (
Each extension point has his own XML structure descriptor, to specify the XML fragment he's waiting for into this extension point:
This description is defined directly into these classes by annotations. Nuxeo Runtime instanced descriptors and delivers it to the service each time a new contribution of these extension points is detected.
Each Nuxeo extension point uses this pattern to declare configuration possibilities, service integration, behavior extension, etc. You understand this pattern, you will understand all extension points in Nuxeo. And you can use this infrastructure to declare your own business services.
Contributing to an Extension Point
XML components can also be used to contribute to extension points.
For that, the XML component needs to:
- Be referenced in a manifest bundle,
- Specify a target extension point,
- Provide the XML content expected by the target extension point.
Expected XML syntax is defined by the XMap object referenced in the extension point declaration.
Here is an example contribution to an extension point:
<?xml version="1.0"?> <component name="org.nuxeo.ecm.platform.convert.plugins"> <extension target="org.nuxeo.ecm.core.convert.service.ConversionServiceImpl"point="converter"> <converter name="zip2html" class="org.nuxeo.ecm.platform.convert.plugins.Zip2HtmlConverter"> <destinationMimeType>text/html</destinationMimeType> <sourceMimeType>application/zip</sourceMimeType> </converter> </extension> </component>
Extension Points Everywhere
The Nuxeo Platform uses extension points extensively, to let you extend and configure most of the features provided by the platform (see all extension points available in the platform for instance).
All the bundles included in a Nuxeo Application are part of different plug-ins (from the core plug-ins to the high level ones). A minimal application is represented by a single plugin - the framework itself (which is itself packaged as a bundle). This is what we are naming Nuxeo Runtime. Of course launching the Nuxeo Runtime without any plug-in installed is useless - except a welcome message in the console nothing happens. But, starting from Nuxeo Runtime you can build a complete application by installing different plug-ins (depending on the type of your application you may end up with tens of bundles).
A basic Nuxeo Application is composed at least of two layers of plug-ins: the Runtime layer and the core one.
Packaging and Deployment
The layered architecture impacts the way we package features in the Nuxeo Platform.
In order to keep as much deployment options as possible and let you choose what you deploy and where, each feature (workflow, relations, conversions, preview ...) is packaged in several separated bundles.
Typically, this means that each feature will possibly be composed of:
- An API Bundle that contains all interfaces and remotable objects needed to access the provided services;
- A Core Bundle that contains the POJO implementation for the components and services;
- A Facade Bundle that provides the JEE bindings for the services (JTA, Remoting, JAAS ...);
- A Core Contrib Bundle that contains all the direct contributions to the Repository (Document types, listeners, security policies ...);
- Client bundles.
All the bundles providing the different layers of the same feature are usually associated to the same Maven artifact group and share the same parent POM file. This is basically a bundle group for a given feature.
Now you may want to understand how those packages are deployed on a Nuxeo server.