I am building a Java application on the Equinox OSGi framework and I have been using DS (declarative services) to declare referenced and provided services. So far all the service consumers I have implemented happened to be service providers as well, so it was natural for me to make them stateless (so that they can be reused by multiple consumers, rather than being attached to one consumer) and let them be instantiated by the framework (default constructor, invoked nowhere in my code).
Now I have a different situation: I have a class MyClass
that references a service MyService
but is not itself a service provider. I need to be able to instantiate MyClass
myself, rather than letting the OSGi framework instantiate it. I would then want the framework to pass the existing MyService
instance to the MyClass
instance(s). Something like this:
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
// bind
private void setMyService(MyService myService) {
this.myService = myService;
}
// unbind
private void unsetMyService(MyService myService) {
this.myService = null;
}
public void doStuff() {
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
}
public class AnotherClass {
public void doSomething(String myString, int myInt) {
MyClass myClass = new MyClass(myString, myInt);
// At this point I would want the OSGi framework to invoke
// the setMyService method of myClass with an instance of
// MyService, if available.
myClass.doStuff();
}
}
My first attempt was to use DS to create a component definition for MyClass
and reference MyService
from there:
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="My Class">
<implementation class="my.package.MyClass"/>
<reference bind="setMyService" cardinality="0..1" interface="my.other.package.MyService" name="MyService" policy="static" unbind="unsetMyService"/>
</scr:component>
However, MyClass
is not really a component, since I don't want its lifecycle to be managed -- I want to take care of instantiation myself. As Neil Bartlett points out here:
For example you could say that your component "depends on" a particular service, in which case the component will only be created and activated when that service is available -- and also it will be destroyed when the service becomes unavailable.
This is not what I want. I want the binding without the lifecycle management.
[Note: Even if I set the cardinality to 0..1
(optional and unary), the framework will still try instantiate MyClass
(and fail because of the lack of no-args constructor)]
So, my question: is there a way to use DS to have this "binding-only, no lifecycle management" functionality I'm looking for? If this is not possible with DS, what are the alternatives, and what would you recommend?
Update: use ServiceTracker
(suggested by Neil Bartlett)
IMPORTANT: I've posted an improved version of this below as an answer. I'm just keeping this here for "historic" purposes.
I'm not sure how to apply ServiceTracker
in this case. Would you use a static registry as shown below?
public class Activator implements BundleActivator {
private ServiceTracker<MyService, MyService> tracker;
@Override
public void start(BundleContext bundleContext) throws Exception {
MyServiceTrackerCustomizer customizer = new MyServiceTrackerCustomizer(bundleContext);
tracker = new ServiceTracker<MyService, MyService>(bundleContext, MyService.class, customizer);
tracker.open();
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
tracker.close();
}
}
public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer<MyService, MyService> {
private BundleContext bundleContext;
public MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyService addingService(ServiceReference<MyService> reference) {
MyService myService = bundleContext.getService(reference);
MyServiceRegistry.register(myService); // any better suggestion?
return myService;
}
@Override
public void modifiedService(ServiceReference<MyService> reference, MyService service) {
}
@Override
public void removedService(ServiceReference<MyService> reference, MyService service) {
bundleContext.ungetService(reference);
MyServiceRegistry.unregister(service); // any better suggestion?
}
}
public class MyServiceRegistry {
// I'm not sure about using a Set here... What if the MyService instances
// don't have proper equals and hashCode methods? But I need some way to
// compare services in isActive(MyService). Should I just express this
// need to implement equals and hashCode in the javadoc of the MyService
// interface? And if MyService is not defined by me, but is 3rd-party?
private static Set<MyService> myServices = new HashSet<MyService>();
public static void register(MyService service) {
myServices.add(service);
}
public static void unregister(MyService service) {
myServices.remove(service);
}
public static MyService getService() {
// Return whatever service the iterator returns first.
for (MyService service : myServices) {
return service;
}
return null;
}
public static boolean isActive(MyService service) {
return myServices.contains(service);
}
}
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
public void doStuff() {
// There's a race condition here: what if the service becomes
// inactive after I get it?
MyService myService = getMyService();
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
protected MyService getMyService() {
if (myService != null && !MyServiceRegistry.isActive(myService)) {
myService = null;
}
if (myService == null) {
myService = MyServiceRegistry.getService();
}
return myService;
}
}
Is this how you would do it? And could you comment on the questions I wrote in the comments above? That is:
- Problems with
Set
if the service implementations don't properly implementequals
andhashCode
. - Race condition: the service may become inactive after my
isActive
check.