I have worked on implementing the blocks framework in terms of OSGi for
some time. Not everything is working yet, but I think it is time to
start discussing the involved ideas.
As already discussed I have refactored the blocks fw to reuse as much as
possible form the servlet APIs. While studying OSGi R4 and especially
the new declarative services (DS), I have seen that much of what we want
to accomplish with blocks already is solved in OSGi, although sometimes
in a different way.
I have experimented with implementing the blocks fw in terms of DS, and
am quite happy with the results. The new architecture is less monolithic
than the previous one and there is not much code at all, most of it is
OSGi based design patterns.
--- o0o ---
So, now over to the actual design. As before we have a number of
different parts (not everything implemented yet):
* A dispatcher - where the blocks are mounted with their respective URI
prefixes.
* Inter block component management - a block can make its components
available to other blocks, and use components from other blocks.
* Component manager bridges- the components within a block can be setup
by our ECM/Spring container or maybe by some other kind of container.
* Inter block communication and polymorphism - a sitemap, and more
generally a servlet, can call servlets in other blocks. And there is
also inheritance so that one sitemap (or servlet) can extend another one.
* Dynamic deployment and configuration - the blocks can be installed,
updated, reconfigured and removed dynamically while the rest of the
framework is executing.
In the rest of the RT we will step through these parts and look at the
design. But first a little bit about DS, that is used everywhere in the
design and replaces block.xml among other things in the current design.
Declarative Services
====================
The declarative services simplify service (component) handling in OSGi
by making it possible to declare services and their dependencies in an
XML file (before service management wasn't configuration file based in
OSGi).
The DS supports setter injection. Compared to Spring it is rather small
and primitive. The cool thing about it is that it support dynamic
dependencies. A dynamic dependency can have both a setter and an un-setter.
The framework also takes care of stopping and starting non-dynamic
components when their dependencies comes and goes.
The DS can be used together with a configuration service that can
override the default configurations in the DS XML file. The
configuration service can take part of the responsibility that the
wiring file and deployer takes in the current architecture.
There is not much documentation for the DS yet, so the main references
are the specification [1] and the documentation of the service binder
[2] that was a predecessor of DS.
The dispatcher
==============
The role of the dispatcher is that the blocks (servlets) are mounted in
it based together with their URI prefixes. The dispatcher then call the
blocks based on the incoming URIs. This is already handled by the OSGi
HTTP service which provides a service that a servlet can lookup and
register it self in.
A HTTP service implementation normally contains a HTTP server. But an
alternative for embedding OSGi managed servlets in a servlet container
is to use a HTTP service that acts as a bridge to the embedding servlet
container [3].
It is also inconvenient to require the servlets to lookup the HTTP
service. It is better to use the whiteboard pattern [4], and just
register the servlets as services and have a component that reacts on
the appearance and disappearance of servlet services, and register and
unregister them in the HTTP service respectively.
Using DS a declaration (which is referred to by the Service-Component
property in the manifest file) for a "whiteboard" adapter can look like [5]:
<scr:component name="cocoon.activator">
<scr:implementation class="org.apache.cocoon.blocks.osgi.Activator"/>
<scr:reference name="LOG"
interface="org.osgi.service.log.LogService"
bind="setLog"/>
<scr:reference name="HTTP"
interface="org.osgi.service.http.HttpService"
bind="setHttpService"/>
<scr:reference name="Servlet"
interface="javax.servlet.Servlet"
cardinality="0..n"
policy="dynamic"
bind="setServlet"
unbind="unsetServlet"/>
</scr:component>
which activates the class o.a.c.blocks.osgi.Activator [6] by calling its
"void activate(ComponentContext)" method if there is one. We can also
see that the declaration refers to other services. It will not be
activated until all its references are fulfilled. In this case it
require a log service and an HTTP service to be present, and will insert
these into the Activator instance by using its setLog and setHttpService
methods.
The servlet reference is more interesting, it is dynamic an it has
cardinality 0..n. This means that the activator can be connected to many
servlets and that they can come and go dynamically. Each time a servlet
service appears the setServlet method is called with it and each time
one disappear the unsetServlet method is called.
The name attribute for the references are used for allowing service
manager style lookup using the name, within the component. The service
manager can be get through the ComponentContext.
A Servlet Service
-----------------
A bundle that provides a servlet, can register it as a service with a
declaration like [5]:
<scr:component name="cocoon.servlet3">
<scr:implementation class="org.apache.cocoon.blocks.osgi.TestServlet"/>
<scr:service>
<scr:provide interface="javax.servlet.Servlet"/>
</scr:service>
<scr:property name="path" value="/test3"/>
</scr:component>
compared to the whiteboard adapter we can see some new things, here we
provide a service to the framework and it can be refered to by the name
"cocoon.servlet3" (we should use a better naming scheme, I just adapted
some examples from the specification while implementing the above).
The declaration also contains a property: path=/test3, that is looked up
by the whiteboard adapter and used for mounting the servlet at that URI
context.
--- o0o ---
This far we can see that by using what OSGi implementations already
contain and some minimal glue code [6], we get the possiblity to
dynamically register (and unregister) servlets within a webapp.
In the next step we will see how these servlets can share (dynamic)
components.
Component Management
====================
Actually we already have inter block component management from OSGi. A
bundle (block) can provide components by declaring them as services and
it can depend on other components, possibly from other bundles by
declaring them as references.
More specifically, for a servlet to depend on some components, we can
add a number of set (and unset if we want dynamism) methods to it, and
add the corresponding references in its declaration.
So, by just using OSGi, we get much of what the block architecture is
intended for: dynamic component handling, packaging of servlets
(sitemaps), sharing of components between blocks.
Component Manager Bridges
=========================
While DS is neat, it is not as flexible and powerful as Spring and we
still have our legacy of Avalon components to take care of.
To create a bridge between OSGi services and Spring or Avalon component
management we need two kind of adapters:
* An OSGi service to ServiceManager (or BeanFactory) adapter. This
adapter just implement ServiceManager (or BeanFactory) and lookup the
components OSGi services. It could be registered as an OSGi service it
self and refered to by other components that needs a ServiceManager. We
can even get dynamism by creating the adapter with DS and explicitly
list the services that it should be able to provide, as references.
* A Spring component manager to OSGi services adapter. This adapter
register all the components that is created by the Spring container as
services. By letting the Spring container have a OSGi service to
BeanFactory adapter as parent component manager, the Spring component
manager can use components from other blocks as well, while creating new
components.
We have already implemented this kind of bridge for ECM++ [7]. Now we
need to implement it for the new Spring based container.
Inter Block Communication
=========================
The servlets (sitemaps) in the different blocks need to be able to call
each other. Also it simplifies reuse of blocks if one block can extend
another one (or rather that a servlets in one block can extend a servlet
in another one). This is achieved with the block protocol.
One way of thinking about the inter block communication is to consider
the servlet in the block to be embedded in an own container where the
the servlets of the other blocks are available through the servlet
context. This is the way I have implemented it, so other servlets can be
called through the getNamedDispatcher method of the servlet context,
with the block name as argument.
The implementation of calls to super blocks and polymorphism requires
the use of a call stack, see [8] for details.
Block properties are accessed as servlet config (and context) init
parameters.
In the OSGi implementation there is a BlockServlet that sets up the the
communication with other blocks and creates the context that the servlet
of the own block is executed within. A declaration of a BlockServlet
might look like:
<scr:component name="cocoon.blockServlet2">
<scr:implementation
class="org.apache.cocoon.blocks.osgi.BlockServlet"/>
<scr:service>
<scr:provide interface="javax.servlet.Servlet"/>
</scr:service>
<scr:property name="path" value="/test2"/>
<scr:property name="attr" value="bar"/>
<scr:reference name="blockServlet"
interface="javax.servlet.Servlet"
target="(component.name=cocoon.servlet2)"/>
<scr:reference name="block1"
interface="javax.servlet.Servlet"
target="(component.name=cocoon.blockServlet1)"/>
</scr:component>
Here we can see that we provide a service with the identifier
"cocoon.blockServlet2" that is implemented by the mentioned BlockServlet
and implements Servlet, it is mounted on the path "/test2". So the
"whiteboard" part of the dispatcher described above, will take care of
installing this block servlet in the HttpService of the framework.
The servlet reference with the special name "blockServlet" (should find
a less confusing name) refer to the servlet that is embedded by the
BlockServlet. The embeded servlet could e.g. be a sitemap servlet, and
it could get the components it needs through the mechanism described in
the sections about component management above.
The "target" attribute in a reference can contain constraints on what
service that is refered to. The constraint
"(component.name=cocoon.servlet2)" means that we want the particular
servlet that is registered under the name "cocoon.servlet2". The
constraint lanuage is the same as is used in LDAP, and it is possible to
create rather complex constraints if needed.
We can also see that there is a reference to a block servlet with the
identifier "cocoon.blockServlet1", it will be made available through the
servlet context for "cocoon.servlet2" with the getNamedDispatcher
method using the name "block1".
All the properties (path and attr) are made available as init parameters
in the servlet context for "cocoon.servlet2".
As we can see, the above DS configuration of a block servlet take care
of most of what is configured in block.xml in the current block
architecture.
The Block Protocol
------------------
OSGi have an URL service that make it possible to dynamically add
protocols that are available through java.net.URL, much like the
Excalibur sources. I have reimplemented the block source as an
URLConnection that is registered as a protocol and can be used like in
[9] (still buggy code).
Deployment
==========
I have not thought that much about deployment of OSGi based blocks, and
assume Reinhard will have ideas about that.
For packaging the OSGi framework together with needed service bundles
and an init configuration it is best to see what the Felix and
Eclipse/Equinox communities have come up with.
Most OSGi implementations provide both telnet and http based consoles
for installing, starting and stopping bundles. Deploy time configuration
(wiring.xml) seem to have less well developed tool support.
There is a configuration service that can be used for deploy time
configuration. With the configuration service one can override the
properties and target attributes for references that is given in the DS
files in the bundles.
The wiring.xml could be used for setting up the configuration service.
Conclusion
==========
It should be noted that I haven't referred to any Cocoon specifics
above. That is one of the neat things about the architecture. It is
completely orthogonal to and independent of the rest of Cocoon and it
could be used together with any servlet based web framework.
It is obviously not exactly as the block architecture that was designed
a couple of years ago. But it is rather close and by reusing so much of
OSGi we get a rather small implementation that can be interesting for
other communities. Much of the surrounding infrastuture will be needed
by other OSGi users, so much can be developed together with other
communities.
WDYT?
/Daniel
References
==========
[1] OSGI R4 specification
http://www.osgi.org/osgi_technology/download_specs.asp?section=2
[2] Service binder http://gravity.sourceforge.net/servicebinder/
[3] Servlet container embedding
http://www.eclipse.org/equinox/incubator/server/embedding_in_a_servlet_container.php
[4] Whiteboard pattern
http://www.osgi.org/documents/osgi_technology/whiteboard.pdf
[5]
http://svn.apache.org/repos/asf/cocoon/trunk/cocoon-blocks-fw/cocoon-blocks-fw-osgi-impl/META-INF/components.xml
[6]
http://svn.apache.org/repos/asf/cocoon/trunk/cocoon-blocks-fw/cocoon-blocks-fw-osgi-impl/src/main/java/org/apache/cocoon/blocks/osgi/Activator.java
[7] The bridge was part of cocoon-core in package o.a.c.core.osgi,
Reinhard moved it to the whiteboard, but the code seem to have
disappeared on its way.
[8] Obsolete description of the sitemap blocks that contain an
explanation of how polymorphism is implemented
http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=111791016006393&w=2
[9]
http://svn.apache.org/repos/asf/cocoon/trunk/cocoon-blocks-fw/cocoon-blocks-fw-osgi-impl/src/main/java/org/apache/cocoon/blocks/osgi/TestServlet2.java