Hi Rob-

I've been struggling with the same problem.

In fact, I'm guessing that this Service Provider classpath/classloader
hackery (made into a crazy best-practice by its inclusion in JDK 6 as
the java.util.ServiceLoader) is one of the main reasons why OSGi isn't
as widely adopted as it should be- OSGi is a little complicated to begin
with, and you have to bundle-ize a bunch of other people's libraries,
and then you get hit with these situations where some stuff looks like
it just won't work at all.

I also did some googling.  A few people out there are trying to adapt to
the Service Provider pattern for use in an OSGi context.  This guy's
post was discussed in a few places:

http://gnodet.blogspot.com/2008/05/jee-specs-in-osgi.html

His strategy seems to be to use the "OSGi Extender" pattern, which
basically means you write some code which registers a BundleListener
with the container, and checks every bundle for specific resources
contained within.  (This type of thing is used, for instance, by Spring
DM (dynamic modules), to look for bean configuration XML files in
bundles from which to create Spring contexts from, and to auto-register
various osgi services.)  As you know, the ServiceLoader API used by a
lot of java libraries involves files in META-INF/services/ where the
file name is the fully-qualified name of a provider interface or base
class, and the contents of the file is class names of implementations or
factories.  The idea in the linked article above is that his code
inspects the jar file of each bundle (not the same as it's classloader,
uses bundle.findEntries()) for META-INF/services/*, reads the content,
and creates a lookup table (of Provider interfaces to implementation
class names) in a static member of a public class.  The kicker is that
he then modifies the original libraries(!) to check this lookup table
before doing their regular discovery process, so that the can find the
implementation classes.  (specifically he saves information about the
original bundle which provides this metadata, and then he uses that
bundle, when requested, to load the Class of the provider
implementation, to be passed to the provider loader.  Then instances of
these classes can be created via reflection or Class.newInstance()).

This is clever, but not very appealing.  And it's not helpful in cases
where you can't (or don't want to) create modified versions of standard
tool distributions.

But there's a bigger problem here.  The ServiceLoader model has no
lifecycle!  It's based on the unchanging static classpaths used by
traditionaly executed java programs.  This seems fundamentally
incompatible with the dynamic lifecycles supported by OSGi, for both
bundles and services.  Using archive metadata to passively communicate
the class names of service implementations doesn't leave room for what
happens when, in OSGi, the bundle goes away- the API libraries which use
this pattern aren't written to ever _unload_ these implementations.  The
only times they work is when they happen to be written to simply
re-execute the metadata search each time they're asked for an
implementation, which probably shouldn't be relied on.

In OSGi, the main mechanisms used for sharing functionality across
bundles is exporting a package or registering a service.  It seems like
we should try to use these primary OSGi mechanisms before resorting to
code that pilfers the archive entries of other bundles looking for
metadata.  The only other mechanism which is less frequently discussed
is the use of bundle Fragments.  (Fragments get folded into the
classloader and archive "entries" of a parent bundle at runtime.)

I think that the preferable solution in a particular situation (if a
solution exists at all) depends on 1) what the relationship is between
API and implementation, and 2) which code you have the ability to deploy
changes to.

If you have the ability to deploy changes to either/both the api and
impls, then I think it's more straightforward:

If the client code needs to consume potentially more than one
implementation of an API, or must choose just one implementation but
doesn't care which one, then the API should just be a library and not
engage in service discovery at all. Impls should be registered in the
OSGi service registry, and client code should use ServiceTrackers (or
the like) to select one or more services.  This allows for the full
dynamism of OSGi.

If the client code needs to consume a _specific_ single impl of an API
(like choosing an XML parser implementation), then it can just import
and use that implementation directly, using package dependency
declarations.  Another option is to package up the API and the
implementation in the same bundle (and allow them to use whatever
discovery mechanism they want).  A variation of this might be to use
bundle Fragments, where the impls become Fragments and the API is the
"host" bundle.  While they become effectively the same bundle, the
fragment method allows deployment-time selection of an implementation
without creating a specific dependency in the client code.

If you have the ability to change the API bundle but the impl bundles
still expect to use the traditional ServiceLoader pattern, then doing
something like the blog post above seems okay.

If you have the ability to change the Impl bundles, but not the API
bundle, then you may be able to write an Activator in each of the impl
bundles which manually calls impl-registration methods (if any are
available) in the API bundle.  For instance, it looks like in Restlet
you can manually call engine.getRegisteredServers().add() in order to
register a ServerHelper extension. (note: I haven't tried this).

If you can't modify either the API or the Impls, but the API has manual
registration methods available, you could write a seperate helper bundle
which uses the above-mentioned Extender pattern and by-hand registers
what it finds with the API bundle.

I'm not sure any of what I've written here will be helpful to you, but
since we're both struggling with the same thing, I figure it'd be good
to get my current thoughts on this typed up.

-Dave Fogel

------------------------------------------------------
http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=1331460

Reply via email to