Table of Contents
Objective: To explain the basic principles and practice behind OSGi, component-based software development, and the use of extension points to add new functionality to an existing system.
The wheel is an extension of the foot, the book is an extension of the eye; clothing, an extension of the skin, electric circuitry, an extension of the central nervous system. H. Marshall McLuhan, Canadian communications theorist (1910-1980).
If you have any comments, questions, or general-purpose harassment you would like give us about this book, then please use the comment form at the bottom of each page! We promise that we will try to incorporate any feedback you give (minus the profanity, of course), will respond to your questions, and credit you appropriately.
OSGi, née 1999 as the Open Services
Gateway Initiative, is many things to many people. A full discussion of
the history of, viewpoints on, and capabilities of OSGi is beyond the
scope of this book and would likely fill an entire volume on its own! We
encourage readers who are interested in OSGi's origins and rationale to
consult http://en.wikipedia.org/wiki/OSGi
and
http://www.osgi.org/Main/HomePage
for more detailed information
as well as http://osgilook.com/
for recent information about
tutorials and books. This book will adopt the utilitarian approach of only
discussing OSGi as it relates to developing software for the Nuxeo
platform. As of January 2009, Nuxeo uses version 4.1 of the OSGi standard,
also known as JSR-291 within the Java Community Process.
At its core, the OSGi "model" is a way of managing components or,
more colloquially, "blobs of code." [Definition: A
component in OSGi is a set of related Java classes
that should be kept together and managed as a unit]. For example,
if we create a component called org.nuxeo.foo
made of
several Java classes, an OSGi container will manage these classes as one
entity. [Definition: An OSGi container is a program
that manages components, usually many simultaneously, and enforces rules
about the way that components can behave and interact with each
other.] A container, for example, is responsible for loading a
component from disk-where the component is just a lifeless set of Java
classes with some configuration information-into memory and starting it
running.
As a developer of functionality or applications based on Nuxeo, your
goal will be create a bundle.[Definition: A bundle, in
Nuxeo-OSGi parlance, is a single file that contains all the information
about a component]; this single file is in "jar" format, nearly
identical to the "jar archives" that Java developers use to distribute
libraries of code. We can now start an example: the
org.nuxeo.foo
logical unit of functionality, is
contained in a bundle, and stored on disk as a jar file, probably with the
filename something like nuxeo-foo.jar
. This file
contains all the compiled Java .class
files needed
for the functionality of foo
, plus some extra files
like a MANIFEST.MF
. When the container loads the
bundle nuxeo-foo.jar
to begin using the component, it
will read the MANIFEST.MF
file to get any special
instructions that the bundle's author had for the container about this
bundle. Then the container will try to find a special class, designated in
the manifest file, and send that special class a life cycle message, such
as start()
.
[Definition: The life cycle of a component is the
set of states that it passes through during its execution.]
Managing the life cycle of its components is a critical part of a
container's job. As the life cycle proceeds, the container tells the
component status information by calling methods on a class in the
container. As stated before, one of these is start()
and there, obviously, is a stop()
that indicates the
component is being shutdown and will soon be removed from the container.
There are a number of other parts to the life cycle, but these can be be
ignored for now. When you write a "program" that "runs on Nuxeo" it can be
more precisely stated that you have a component that can be successfully
loaded by the Nuxeo container and that passes through the life cycle of a
component; during some of these life cycle phases your code will be
running and can take whatever actions you can devise for a Java
program.
As we stated previously, an OSGi container typically manages many
components at once, and Nuxeo is no exception. By using facilities
provided by the container one component, like
org.nuxeo.foo
, can find another component, say
org.nuxeo.bar
, so that functionality exposed by
bar
can be consumed by foo
. All of
these interactions, both with the container, and between the two
components, are implemented either as Java code or in configuration files
provided in the component's bundle. All of the interactions between
components are mediated through the container to enforce security
restrictions, or any other policy choices for that matter, that the
container decides.
Let's look at a fictional MANIFEST.MF
file that
might be packaged into the bundle nuxeo-foo.jar
to
tell the Nuxeo container some critical information. This is not a complete
manifest file, but should help you get a sense of what information the
container and bundle might exchange.
Administrator
# # The manifest is located in the jar file of the bundle at META-INF/MANIFEST.MF # Bundle-Version: 5.2.0 Bundle-Name: nuxeo's foo project Bundle-SymbolicName: org.nuxeo.foo Nuxeo-Require: org.nuxeo.bar
If you look at this manifest
file, you will see some critical bits of information for a container to
know about our component in Key:Value
form; lines that start with # are comments. First, you
will note the bundle's version is set to "5.2.0". This is more important
than one might initially think, because many OSGi containers can handle
having multiple, different versions of a bundle loaded simultaneously! The
different versions are distinguished by this version information. You can
also see two versions of the "name" of the bundle. The first of these is
just for human consumption and can be any text. The latter name,
SymbolicName,
is the name that
other components will use to refer to this bundle. It
is customary, although not required, that we use the Java "reverse domain"
convention for naming a component and we will follow that convention
throughout this book.
Finally, the most interesting line is the last one. This line tells
the container that the bundle and component referred to by this manifest
file cannot function properly without access to the bundle
org.nuxeo.bar
. Obviously, the container will take some
action, such as searching through its already loaded bundles or perhaps
certain directories in the file system, to see if the bundle
org.nuxeo.bar
can be located. The target bundle must
itself have a manifest file with the line
Bundle-SymbolicName: org.nuxeo.bar
so
that it can recognized properly! If the target bundle cannot be located,
then foo
will be rejected and will not proceed through
the normal life cycle. As you will see in future lessons, this ability to
combine functionality from many bundles is a key reason to use the OSGi
model and construct an application in this
component-oriented way.
If you have been paying careful attention in this lesson, you will have noticed that we have been deliberately imprecise about exactly what role "Nuxeo" plays with respect to OSGi. To clarify, the Nuxeo 5.2 server is a subset OSGi container. By subset, we mean that the Nuxeo container does not fully implement the OSGi specification for containers; it implements only enough of the OSGi specification to load, manage, and execute components in an "OSGi-like" way.
However, we often use the word "platform" to describe Nuxeo, it's the 'P' in Nuxeo 5 EP! "Platform" is used because the code you get when you download Nuxeo 5 EP includes hundreds of components that have been written by the Nuxeo development team. These components perform many useful functions such as indexing documents, searching for documents based on query terms, and managing user accounts. These components are well-formed with respect to the OSGi specifications and will run in OSGi compliant containers. We know this is true because we've tried it! The reason for this difference in terms of conformance to the specification is that there are a number of open-source OSGi containers available (see sidebar) so it is unecessary for Nuxeo to provide a "real" OSGi server. It is also in the future plan to ship our components bundled with a different, fully-compliant OSGi container, such as Glassfish, avoiding our container entirely if you need a true OSGi environment.
So, to be strictly correct, we should be careful to distinguish when we are referring to the Nuxeo OSGi container as opposed to one of the legion of the Nuxeo components that are downloaded with the container. However, the noun "Nuxeo" has entered common usage to mean either or both of these parts of the software and we will continue this unfortunate tradition in cases where it is not too confusing; c'est la vie.
Nuxeo 5 exposes dozens of extension points. [Definition: An
extension point a place where a component allows
other components to extend its functionality.] At a high level,
an extension point is a way that a component can say, "you are likely to
want to put some code here later." For example, consider an imaginary
component org.acme.calculator
that exposes the
extension point trigFunction
. The
calculator
component is suggesting, we might even say
"announcing", to the world that if some other component inside the
container has a trigonometric function that might be useful to add to the
calculator, the calculator
component will utilize that
new function.
Let us revisit our previous, fictional component and bundle,
org.nuxeo.foo
and imagine that the component has an
implementation of a special trigonometric function called
reverseTangentOnMars
. The component's manifest file
would need to change to inform the container about
foo
's contribution to the extension point. [Definition: We
use the term contribution to refer to the
functionality supplied to the extension point by another
component.] We often use the verb "export" or "exposes" for the
component that supplies the extension point. Thus, our example is one that
has “the calculator
component exposing the
extension point trigFunction
” and “the
foo
component contributing
reverseTangentOnMars
to that extension
point.”
The slightly changed manifest file for
org.nuxeo.foo
looks like this:
# # The manifest is located in the jar file of the bundle at META-INF/MANIFEST.MF # Bundle-Version: 5.2.0 Bundle-Name: nuxeo's foo project Bundle-SymbolicName: org.nuxeo.foo Nuxeo-Require: org.nuxeo.bar, org.acme.calculator Nuxeo-Component: OSGI-INF/calc-contrib.xml
You will note that our bundle now also requires the calculator bundle and multiple dependencies can be separated with a comma.
The last line of the manifest file, marked with
Nuxeo-Component
, refers to another
file inside the bundle's jar file. That file is in the
OSGI-INF
directory and is called
calc-contrib.xml
. It is customary for bundles that
want to make contributions to extension points to put the configuration
files in this directory and have the configuration fie name end with
-contrib.xml
. Let's examine a slightly simplified,
yet still imaginary, version of this file:
<?xml version="1.0"?> <!-- The contribution to the calc extension point is contained in the jar file of the bundle at OSGI-INF/calc-contrib.xml. This file's content is an xml fragment. --> <component name="org.nuxeo.foo.trig"> <extension target="org.acme.calculator" point="trigFunction"> <trigfunction buttonName="rtom" longName="reverseTangentOnMars" implementedBy="org.nuxeo.foo.ReverseTangentOnMars"/> </extension> </component>
Extension point declarations like the one above are actually
instructions for two different pieces of code, the
OSGi container and the component that exposes the extension point. The XML
nodes component
and extension
are
intended for the container and the innermost tag,
trigfunction
in this case, is for the extension point
itself, part of the org.acme.calculator
component. The
outermost tag, sadly, is called "component" and this is a bit confusing.
For now, it is ok to think of this as creating a special component-like
org.nuxeo.foo.trig-that exists only as the contribution to
the extension point. (Strict OSGi has no notions of extension points, so
Nuxeo was forced to extend things a bit.) The name of this "special
purpose" component must be unique; it is normal to use a name that is
prefixed by the name of the surrounding OSGi component to insure there is
no collision of names, as we have done above.
The extension tag is used to tell the container which extension
point is being contributed to and which component owns that extension
point. If the extension point cannot be found as part of the bundle named
as the target
, an error will be generated and the
bundle foo
will likely be rejected for the normal
component life cycle.
The XML fragment inside the extension
tag is completely
defined by the extension point. Different extension points obviously need
different information about the contribution being provided, so the
documentation for each extension point must be consulted to see exactly
what is "needed" to contribute to the extension point. In this example, we
have imagined that the calculator
component is
displaying a user interface so it needs both a
buttonName
and a longName
to show
the user. Further, the calculator
is expecting to
receive the name of a Java class contained in the
contributor's bundle that meets some Java interface
(defined elsewhere in the documentation!) that can actually perform the
function contributed. This is supplied by org.nuxeo.foo
in the implementedBy
attribute; this value would likely
be checked by the exposing component to insure that it met the correct
interface or had other desired properties.
The theory behind extension points should be clear even from this simple example: Extension points allow code written by one party-such as the calculator implementor-to be extended by another bundle supplied at a later time that is (and was) unknown to the calculator implementors. As we will see in the later sections, the Nuxeo platform exploits this ability in a great many ways.
If you navigate inside your Nuxeo installation, you will be able to see the complete list of the bundles that come with the download of Nuxeo EP:
iansmith@Photo /nuxeo $ cd /nuxeo/tools/NuxeoEP5/NuxeoServer/server/default/deploy/nuxeo.ear iansmith@Photo /nuxeo/tools/NuxeoEP5/NuxeoServer/server/default/deploy/nuxeo.ear $ ls -l system | wc -l 126 iansmith@Photo /nuxeo/tools/NuxeoEP5/NuxeoServer/server/default/deploy/nuxeo.ear $ cp system/nuxeo-platform-audit-client-5.1.6.jar /tmp iansmith@Photo /nuxeo/tools/NuxeoEP5/NuxeoServer/server/default/deploy/nuxeo.ear $ cd /tmp iansmith@Photo /tmp $ jar xf nuxeo-platform-audit-client-5.1.6.jar iansmith@Photo /tmp $ cd META-INF
In the listing above we have navigated to the directory of the Nuxeo
server, nuxeo.ear
, that contains the server's code
and the accompanying bundles. We then used ls and
wc to count the number of bundles shipped with the
system. The total is about 125 (there are a couple of extra lines in the
ls output). We have chosen a bundle and copied it to
the /tmp
directory which we then switched to and
unpacked the bundle with the jar command. Since jar
unpacks the bundle by default in the current directory, three new
directories have been created, org
,
META-INF
, and OSGI-INF
. The
org
directory and its subdirectories contain the
compiled code for this bundle, so we will ignore that for now. We have
gone into the META-INF
directory to examine this
bundle's manifest file. This is the full MANIFEST.MF
of a real bundle:
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven Built-By: julien Build-Jdk: 1.5.0_16 Bundle-ManifestVersion: 1 Bundle-Version: 5.1.6.0-t20081007-151949 Bundle-Name: Nuxeo ECM Audit Web Fragment Bundle-SymbolicName: org.nuxeo.ecm.platform.audit.web;singleton:=true Bundle-Category: web,stateless Require-Bundle: org.nuxeo.ecm.actions,org.nuxeo.ecm.platform.audit.api ,org.nuxeo.ecm.webapp.core Provide-Package: org.nuxeo.ecm.platform.audit.web,org.nuxeo.ecm.platfo rm.audit.web.delegate,org.nuxeo.ecm.platform.audit.web.listener,org.n uxeo.ecm.platform.audit.web.listener.ejb,org.nuxeo.ecm.platform.audit .web.listener.events Nuxeo-Component: OSGI-INF/actions-contrib.xml Nuxeo-RequiredBy: org.nuxeo.ecm.war
While this bundle has some
extra information that our previous, simplified examples did not, a lot of
this information is informational and not critical to the correct
functioning of the bundle. A key item to notice is the
Bundle-SymbolicName
field that we discussed before: As
you would expect, it contains the bundle's name in the typical format.
However, it contains a suffix, ;singleton=true. Roughly, this means that
there can be only one copy of this bundle in the container at a time. For
now, all the bundles you write should also have this suffix on their
symbolic name field.
As you can see from this example, you can split a long line in a
manifest file over two or more lines if you begin the 2nd and all
following lines with a space. While this is good to know when reading
manifests, normally this type of formatting is handled for you by the tool
that builds your jar file, so you can simply write your manifest with long
lines. You can see that this bundle provides a large number of Java
packages; these are associated with the Provide-Package
key. Finally, you can see the
Nuxeo-Component
line that references the contribution
to an extension point contained in another file of the bundle. We will now
look at this contribution file, actions-contrib.xml
,
in the directory OSGI-INF
.
<?xml version="1.0" encoding="UTF-8">
<component name="org.nuxeo.ecm.platform.audit.web">
<documentation>
NXAudit action contributions.
@version 1.0
@author <a href="mailto:[email protected]">XXXXX XXXXX</a>
</documentation>
<extension target="org.nuxeo.ecm.platform.actions.ActionService" point="actions">
<documentation>
Contribute a new action related to document history.
</documentation>
<action id="TAB_CONTENT_HISTORY" link="/incl/tabs/document_history.xhtml"
enabled="true" label="action.view.history" icon="/icons/file.gif"
order="100">
<category>VIEW_ACTION_LIST</category>
<filter id="view_content_history">
<rule grant="false">
<type>WorkspaceRoot</type>
<type>SectionRoot</type>
<type>Server</type>
</rule>
</filter>
<filter-id>mutable_document</filter-id>
</action>
</extension>
</component>
The purpose of this contribution is to add a new action, a new thing
the user can do with a document, to documents of the type
mutable-document. The details of the contribution are not particularly
important at this point, it is the structure that matters. This
contribution's XML fragment has the two outermost tags,
component
and extension
, that tell
the Nuxeo server (the container) which extension point is being
contributed to. Each of these tags can optionally contain a
documentation
node and this author has graciously done
so both for the component
and the
extension
. We apologize for skimping on documentation
in our last example, but it seemed unnecessary clutter as we explained
extension points! The component that is exporting
this extension point is the ActionService, shown in the
target
attribute of the extension
tag in its fully-qualified form, and the extension point is called
actions
as shown in the point
attribute.
The child of the extension
node (after the useful
documentation
node) is the meat of this contribution.
This particular snippet of XML is unique to this extension point and only
by looking at the documentation for the extension can one decipher the
meaning of all the various parts of this fragment. We will not bother to
explain the particulars of this here, but only ask the reader to note that
this is related to the user interface shown by the Nuxeo server through
the web since it has references to xhtml
and
gif
files.
It is worth thinking more globally about what has been covered in the last three lessons. You have learned
The Nuxeo system is written in Java
The Nuxeo server is an OSGi container that manages bundles of Java code
The Nuxeo server is launched and contained within a complete application server (JBoss)
The Nuxeo server exposes a web interface (via JBoss) that allows you to accomplish ECM tasks through your web browser
The Nuxeo systems ships with a large number of bundles
The Nuxeo bundles use the same mechanisms, such as OSGi components and extension points, as any other program would
What does this mean when taken altogether?
The Nuxeo server is "piggybacking" on the abilities of JBoss to allow web access. This is not a "built in" property of OSGi containers.
The Nuxeo server itself is just a shell ("container") that could contain any type of application
The additional bundles that "come with" the Nuxeo server provide the ECM functionality as OSGi components
By choosing a set of bundles, yours or those written by Nuxeo, you can create different sets of functionality and thus different "ECM-oriented applications"
At the beginning of this book, we said that this was a book for Java programmers who would like to develop ECM-oriented applications. We hope it is now clear both how you do that - write your own bundle and load it into the Nuxeo container - and why we have introduced you to the concepts covered in these first few lessons. You are now ready to write a bundle for Nuxeo... and change the world.