javaosgieclipse-rcpbndbndtools

Injecting singleton OSGi Declarative Service in Eclipse RCP


I'm trying to define a singleton OSGi service that would be used (=shared) by other plugins in my Eclipse RCP application but every plugin has its own version (from local classloader) and only the one version with the highest ranking gets injected.

The service is defined (in com.test.taskmodel bundle) as such (not using separate interface and implementation for now):

@Component(scope=ServiceScope.SINGLETON, service=TaskService.class)
public final class TaskService {

    public TaskService() {}

    @Activate
    void activate(BundleContext bundleContext) {
        //activate
    }
    
    public Result doStuff() {
        return null;
    }
}

When I build it using bnd, the resulting jar manifest contains Service-Component: OSGI-INF/com.test.TaskService.xml and the xml is:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" name="com.test.TaskService" activate="activate" deactivate="deactivate">
  <service scope="singleton">
    <provide interface="com.test.TaskService"/>
  </service>
  <implementation class="com.test.TaskService"/>
</scr:component>

There are three Eclipse RCP plugins using the service: taskbrowser, taskfilter and taskdetail. They are all virtually identical for now, each having only one Part like this:

public class TaskBrowserPart {
    
    @Inject @Service @Optional private TaskService taskService;

    @PostConstruct
    public void createControls(Composite parent) {
        if (taskService == null) {
            System.out.println("TaskBrowser TaskService null");
        } else {
            System.out.println("TaskBrowser TaskService non-null");
        }
    }
}

When I run the application I get the following output:

TaskBrowser TaskService non-null
TaskFilter TaskService null
TaskDetail TaskService null

Calling service command in the osgi console shows four registered services with only the first one being used by all the tree plugins:

{com.test.taskmodel.TaskService}={service.id=63, service.bundleid=155, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=34}
  "Registered by bundle:" com.test.taskbrowser [155]
  "Bundles using service"
    com.test.taskfilter [165]
    com.test.taskdetail [158]
    com.test.taskbrowser [155]
{com.test.taskmodel.TaskService}={service.id=65, service.bundleid=158, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=36}
  "Registered by bundle:" com.test.taskdetail [158]
  "No bundles using service."
{com.test.taskmodel.TaskService}={service.id=66, service.bundleid=159, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=37}
  "Registered by bundle:" com.test.taskmodel [159]
  "No bundles using service."
{com.test.taskmodel.TaskService}={service.id=87, service.bundleid=165, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=39}
  "Registered by bundle:" com.test.taskfilter [165]
  "No bundles using service."

Why are there multiple service instances with service.scope=bundle when I have the service defined with scope="singleton"? How do I define it in a way that only one instance of the service exists and all plugins use it? Why does Eclipse try to inject only the first service and then fails to do so in the taskfilter and taskdetail plugins because they use different classloaders and therefore the service types do not match? Why is the classloader not taken into account when selecting the service but only when injecting it? In that case I'd at least got a separate instance in every plugin, which is not what I want exactly but at least something I could work with. Any help is appreciated, thank you.


Solution

  • It looks like you are declaring the service in all bundles causing each to have their own implementation of TaskService. These are then incompatible with each other, so when first instance is returned to non-owner, it is from different class loader.

    As @Patrick Paulin mentions in his answer, you can see that in your services printout

    "Registered by bundle:" com.test.taskbrowser [155]
    "Registered by bundle:" com.test.taskdetail [158]
    "Registered by bundle:" com.test.taskmodel [159]
    "Registered by bundle:" com.test.taskfilter [165]
    

    Registered meaning a class definition was "submitted" by it (not that it created an instance).

    It can happen in Bnd when you define jar contents with broad selectors such as

    -private-package: com.test.*
    Private-Package: com.test.*
    Export-Package: com.test.*
    

    resulting in com.test.taskservice being included everywhere. There DS finds your declaration and creates identical scr:component xml in every bundle, competing with each other.

    See https://bnd.bndtools.org/heads/private_package.html

    Bnd traverse the packages on the classpath and copies them to the output based on the instructions given by the Export-Package and Private-Package headers.