0

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:

  1. Problems with Set if the service implementations don't properly implement equals and hashCode.
  2. Race condition: the service may become inactive after my isActive check.
Community
  • 1
  • 1
Alix
  • 927
  • 7
  • 21

2 Answers2

0

No this falls outside the scope of DS. If you want to directly instantiate the class yourself then you will have to use OSGi APIs like ServiceTracker to obtain the service references.

Update:

See the following suggested code. Obviously there are a lot of different ways to do this, depending on what you actually want to achieve.

public interface MyServiceProvider {
    MyService getService();
}

...

public class MyClass {

    private final MyServiceProvider serviceProvider;

    public MyClass(MyServiceProvider serviceProvider) {
        this.serviceProvider = serviceProvider;
    }

    void doStuff() {
        MyService service = serviceProvider.getService();
        if (service != null) {
            // do stuff with service
        }
    }
}

...

public class ExampleActivator implements BundleActivator {

    private MyServiceTracker tracker;

    static class MyServiceTracker extends ServiceTracker<MyService,MyService> implements MyServiceProvider {
        public MyServiceTracker(BundleContext context) {
            super(context, MyService.class, null);
        }
    };

    @Override
    public void start(BundleContext context) throws Exception {
        tracker = new MyServiceTracker(context);
        tracker.open();

        MyClass myClass = new MyClass(tracker);
        // whatever you wanted to do with myClass
    }

    @Override
    public void stop(BundleContext context) throws Exception {
        tracker.close();
    }

}
Neil Bartlett
  • 23,743
  • 4
  • 44
  • 77
  • Thank you, I thought that may be the way but I'm not quite sure about the usage (how to pass the services from the ServiceTrackerCustomizer to MyClass). I've updated my question with a possible implementation and some new questions ;) Could you please have a look? I want to learn the best practices to deal with this situation. Thanks! – Alix May 29 '15 at 09:22
  • I'll add a better suggestion. In particular there is no need to implement a registry because OSGi already has a Service Registry! – Neil Bartlett May 29 '15 at 20:20
  • But in your example you're instantiating `MyClass`, once, in the `BundleActivator`'s `start` method. What I want is to be able to instantiate `MyClass` any time, and to have an arbitrary number of `MyClass` instances. In that case, what would you recommend? Thanks for your help! :) – Alix Jun 01 '15 at 06:40
  • Yes you can do that too, no problem. – Neil Bartlett Jun 01 '15 at 18:10
  • Thanks Neil. I've posted my (improved) solution as answer to my question. Thanks for pointing me in the right direction :) – Alix Jun 03 '15 at 08:04
  • Oh great so you give yourself the credit for answering the question, and you ignore my advice not to implement a duplicate service registry alongside the existing OSGi registry. Nice way to encourage people to help you in future... – Neil Bartlett Jun 03 '15 at 10:26
  • Wow, Neil, I have to say I'm really disappointed: so far every answer I've seen from you was great (I even updated a comment from a user wishing they could subscribe to you to see every answer you post -- I fully agreed). The reasons why I answered the question myself are: 1) Your first answer was simply "use `ServiceTracker`". 2) Your second answer was not appropriate for my solution since it created a single instance of `MyClass` on bundle activation rather than an arbitrary number of instances at any point during the bundle's lifecycle. .... – Alix Jun 03 '15 at 14:09
  • ...3) To be honest I didn't understand your comment "Yes you can do that too, no problem" but I thought I wouldn't pressure you for more answers since I had already asked several times. I understood this to mean that you approved of my suggested solution. So far the code you posted is not appropriate for me. 4) I have provided a complete, working, documented answer with code written exclusively by me, not you. I don't see how your answers are more useful than this to people with a scenario similar to mine. – Alix Jun 03 '15 at 14:15
  • If you have a suggestion for how to use the OSGi registry for this and you post it, I will choose _that_ suggestion as the correct answer. Until then I am the only one who has provided a full answer to my original question. – Alix Jun 03 '15 at 14:16
  • _"Nice way to encourage people to help you in future..."_ I would hope for people to help because they want to help, not because they want reputation. I didn't choose my answer over yours to improve my reputation but because I considered it more useful for people facing the same situation in the future. If you disagree, please explain. – Alix Jun 03 '15 at 14:18
  • One more comment about the OSGi registry vs. my own registry: I wanted to avoid OSGi-specific dependencies for `MyClass`. With my solution both `MyClass` and the registry are OSGi-independent. If you have a better idea I'll be happy to hear it. – Alix Jun 03 '15 at 14:21
  • 3) My comment "yes you can do that" simply meant that the code sample I gave was not limited to one instance, you can trivially use it to create more. – Neil Bartlett Jun 03 '15 at 23:12
  • Regarding the reputation issue: you're right, I do want to help people rather than just score points. From my point of view I have posted a solution that you have rejected for no apparent reason, then posted and self-accepted what looks like an inferior solution. I don't think that helps newbies when they come to StackOverflow looking for good examples. – Neil Bartlett Jun 03 '15 at 23:13
  • Also... looking at your code, you actually create *zero* instances of MyClass. Surely one is better than none? Have I misunderstood your requirements completely? – Neil Bartlett Jun 03 '15 at 23:18
  • I do think you have misunderstood my requirements. I _do not_ want the instantiation of `MyClass` to be in any way linked to the activation of the bundle. – Alix Jun 08 '15 at 06:34
  • _Also, looking at your code, you actually create zero instances of MyClass_. That's because the part of my code that instantiates `MyClass` is external to all of this. Look at `AnotherClass` in my original question. – Alix Jun 08 '15 at 06:34
  • _3) My comment "yes you can do that" simply meant that the code sample I gave was not limited to one instance, you can trivially use it to create more._ I do not want to create multiple instances _in `ExampleActivator.start(BundleContext)`_. I don't want the activator to play any part in instantiation. If you look at `MyClass` you will see that it needs to be instantiated with a `String` and an `int`. Those cannot be known by the activator; they're determined later. – Alix Jun 08 '15 at 06:36
  • _From my point of view I have posted a solution that you have rejected for no apparent reason_ I have explained the reasons repeatedly. Do you still disagree? – Alix Jun 08 '15 at 06:37
  • "I do not want the instantiation of MyClass to be in any way linked to the activation of the bundle". Here lies the problem. If you want `MyClass` to be instantiated in a vacuum, with no connection to the OSGi lifecycle, then of course you can't make use of the OSGi lifecycle, including services. Your class `AnotherClass` instantiates `MyClass` but who instantiates `AnotherClass`?? It's all just floating on nothing... – Neil Bartlett Jun 08 '15 at 10:44
  • It's not floating on nothing, I just don't see the point of posting my entire code base here when it is irrelevant in this particular case. The only thing that is relevant is that it cannot be dependent on the OSGi lifecycle. _then of course you can't make use of the OSGi lifecycle_ I know, that's even in my question's title and I repeated it several times in my original question and in the course of our conversation. – Alix Jun 08 '15 at 11:51
  • Basically: the solution I posted really does solve my problem; it is integrated with the rest of my code base and it works. I instantiate `MyClass` whenever I want, as often as I want, and sometimes it can get an instance of `MyService` and sometimes not (if none is available at the moment), but that's ok, it can deal with that. – Alix Jun 08 '15 at 11:53
  • If you don't have a better suggestion that matches my requirements I think we need to agree that my solution is the best(/only) one posted here, so you should remove the downvote. Or not, up to you. I got a solution that works (and IMO is perfectly valid) and that's fine with me. – Alix Jun 08 '15 at 11:54
  • Unfortunately stackoverflow won't let me remove the downvote. My objection remains that this is a poor example for other users, since you implement a parallel service registry that partially duplicates the OSGi Service Registry. I didn't want your full source code of course, I just wonder how *anything* gets created in your application if not ultimately from a `BundleActivator`. As an analogy, in a non-OSGi Java application you might say "I want this created without any connection to `main()`". It doesn't make sense because everything ultimately starts from `main()`. – Neil Bartlett Jun 08 '15 at 12:52
  • _Unfortunately stackoverflow won't let me remove the downvote._ Ok, nevermind. – Alix Jun 08 '15 at 14:58
  • _My objection remains that this is a poor example for other users, since you implement a parallel service registry that partially duplicates the OSGi Service Registry._ It's just 1 singleton class that provides access to this particular kind of service without creating direct dependencies on OSGi bundles/classes. This way `MyClass` is completely OSGi-agnostic. I would consider this good practice. – Alix Jun 08 '15 at 14:58
  • _I just wonder how anything gets created in your application if not ultimately from a BundleActivator_ Of course there is a `BundleActivator` involved, but the important thing here is whether there is a direct or indirect dependency between this activator and other classes like `MyClass`, `AnotherClass`, etc. The whole(/main) point of OSGi is decoupling and inversion of control. Are you really criticizing my attempt to decouple as much as possible? I think 1 very simple class (my registry) is a fair price to pay for that. – Alix Jun 08 '15 at 15:01
  • Anyway... let's drop this (unless you feel like going on). It seems that we can go on forever like this and never agree. – Alix Jun 08 '15 at 15:02
-1

Solution: use ServiceTracker (as suggested by Neil Bartlett)

Note: if you want to see the reason for the downvote please see Neil's answer and our back-and-forth in its comments.

In the end I've solved it using ServiceTracker and a static registry (MyServiceRegistry), 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.getInstance().register(myService);
        return myService;
    }

    @Override
    public void modifiedService(ServiceReference<MyService> reference, MyService service) {
    }

    @Override
    public void removedService(ServiceReference<MyService> reference, MyService service) {
        bundleContext.ungetService(reference);
        MyServiceRegistry.getInstance().unregister(service);
    }

}
/**
 * A registry for services of type {@code <S>}.
 *
 * @param <S> Type of the services registered in this {@code ServiceRegistry}.<br>
 *            <strong>Important:</strong> implementations of {@code <S>} must implement
 *            {@link #equals(Object)} and {@link #hashCode()}
 */
public interface ServiceRegistry<S> {

    /**
     * Register service {@code service}.<br>
     * If the service is already registered this method has no effect.
     *
     * @param service the service to register
     */
    void register(S service);

    /**
     * Unregister service {@code service}.<br>
     * If the service is not currently registered this method has no effect.
     *
     * @param service the service to unregister
     */
    void unregister(S service);

    /**
     * Get an arbitrary service registered in the registry, or {@code null} if none are available.
     * <p/>
     * <strong>Important:</strong> note that a service may become inactive <i>after</i> it has been retrieved
     * from the registry. To check whether a service is still active, use {@link #isActive(Object)}. Better
     * still, if possible don't store a reference to the service but rather ask for a new one every time you
     * need to use the service. Of course, the service may still become inactive between its retrieval from
     * the registry and its use, but the likelihood of this is reduced and this way we also avoid holding
     * references to inactive services, which would prevent them from being garbage-collected.
     *
     * @return an arbitrary service registered in the registry, or {@code null} if none are available.
     */
    S getService();

    /**
     * Is {@code service} currently active (i.e., running, available for use)?
     * <p/>
     * <strong>Important:</strong> it is recommended <em>not</em> to store references to services, but rather
     * to get a new one from the registry every time the service is needed -- please read more details in
     * {@link #getService()}.
     *
     * @param service the service to check
     * @return {@code true} if {@code service} is currently active; {@code false} otherwise
     */
    boolean isActive(S service);

}
/**
 * Implementation of {@link ServiceRegistry}.
 */
public class ServiceRegistryImpl<S> implements ServiceRegistry<S> {

    /**
     * Services that are currently registered.<br>
     * <strong>Important:</strong> as noted in {@link ServiceRegistry}, implementations of {@code <S>} must
     * implement {@link #equals(Object)} and {@link #hashCode()}; otherwise the {@link Set} will not work
     * properly.
     */
    private Set<S> myServices = new HashSet<S>();

    @Override
    public void register(S service) {
        myServices.add(service);
    }

    @Override
    public void unregister(S service) {
        myServices.remove(service);
    }

    @Override
    public S getService() {
        // Return whatever service the iterator returns first.
        for (S service : myServices) {
            return service;
        }
        return null;
    }

    @Override
    public boolean isActive(S service) {
        return myServices.contains(service);
    }

}
public class MyServiceRegistry extends ServiceRegistryImpl<MyService> {

    private static final MyServiceRegistry instance = new MyServiceRegistry();

    private MyServiceRegistry() {
        // Singleton
    }

    public static MyServiceRegistry getInstance() {
        return instance;
    }

}
public class MyClass {

    private String myString;
    private int myInt;

    public MyClass(String myString, int myInt) {
        this.myString = myString;
        this.myInt= myInt;
    }

    public void doStuff() {
        MyService myService = MyServiceRegistry.getInstance().getService();
        if (myService != null) {
            myService.doTheStuff();
        } else {
            // Some fallback mechanism
        }
    }

}

If anyone wants to use this code for whatever purpose, go ahead.

Community
  • 1
  • 1
Alix
  • 927
  • 7
  • 21