Whilst performing some code-cleanups on Eclipse, Lars made the observation “This looks unnecessarily hard – why isn’t there a simple API for this?”.
The code in question was acquiring an instance of
DebugOptions, which is
used liberally throughout Eclipse to determine if an option is present to
enable debugging. Actually the use of
DebugOptions itself wasn’t that
much of an issue (though the
getBooleanOption is unable to determine
whether or not a boolean value is present or is
false – but that’s
another bug). The problem is that looking up an OSGi service is more than
a single-liner, and as such, is the type of thing that probably causes
more pain than it needs.
There’s three ways of getting services in OSGi, in reverse order of ease of use:
- Acquire the
BundleContext(such as via the
BundleActivatoror through a handler like
FrameworkUtil) and then use
- Use a
ServiceTrackerto keep a cache of the services available for quick return
- Use Declarative Services to instantiate your component and have the services injected in directly
The problem with using Declarative Services is that you yield not only the acquisition of the service but also the component lifetime. It also precludes the use of static methods or integration with other APIs that expect to manage object creation (or indeed, use object creation with constructors or builder patterns).
The other two use a non-trivial amount of code to get running, all for the sake of satisfying a ‘Please give me an instance’ request.
Fortunately, Java 8 provides a simple way of abstracting this; using the
Supplier interface. A supplier is something that, when asked, returns
an instance of a particular request. It’s also used in a number of different
collections to defer object acquisition until it’s required. This fits in
with what we’re trying to do – get an instance of a service. So how might
it look in OSGi?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
This provides a
Supplier for a given service, and the only two parameters
that are required are the generic target required (e.g.
the calling class (so that the
BundleContext can be resolved). The API
doesn’t get much simpler than that. This is how it looks when using it:
1 2 3 4
No worrying about dependencies other than the
OSGiTracker class, and
the client side API is trivial.
However, in most cases there’s no need to keep a
on since you are using it in a one-shot basis. If the service isn’t there,
you want to use a default without changing anything else. As a result,
there’s an alternative implementation that can be used:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
The API for using this is almost identical:
1 2 3 4
See the difference? It’s the type of supplier we are using. Otherwise the
field type and use case is identical. That makes it easy to switch between
the two; in fact, we could wrap this in another supplier if we wanted to
default to everything being
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
This could be wrapped to prevent any
NullPointerException being thrown if the
service isn’t present:
The move to Java 8 facilitates these kinds of improvements, and should be
part of Eclipse. The question is, where should the above go? Some shared
package would make sense, but does this belong in
org.eclipse.equinox.util? Does this even make sense to add to Eclipse?
Your thoughts are welcome; reach out to me @alblue.