2

I need multiton instance of a given OSGI component, i.e. some bundles will get the same instance of the implementation, and other bundles need another instance. I need to use XML files instead of annotations (like @Component) if possible. I am using a hybrid OSGI 4.3 platform consisting bundles from eclipse & felix.

Let's say, my service interface looks like this:

public interface SocketService {

    // Does nothing if already listening on given port
    public void startListening(int port);

    public String getNextMessage();
}

Declarative XML file looks like as follows and works fine:

<?xml version="1.0" encoding="UTF-8"?>
  <scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="SocketService">
    <implementation class="declarativemultiton.service.impl.SocketServiceImpl"/>
    <service>
      <provide interface="declarativemultiton.service.SocketService"/>
    </service>
</scr:component>

These are the consumer classes, with same same semantics:

public class Consumer1 {

    public void activate() {
        System.out.println("Consumer1 activated");
    }

    public void setSocketService(SocketService service) {
        System.out.println("Consumer1 got SocketService@" + System.identityHashCode(service));
    }
}

public class Consumer2 {

    public void activate() {
        System.out.println("Consumer2 activated");
    }

    public void setSocketService(SocketService service) {
        System.out.println("Consumer2 got socketservice@" + System.identityHashCode(service));
    }
}

And their component definition XML's:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Consumer1" immediate="true">
   <implementation class="declarativemultiton.consumer1.Consumer1"/>
   <reference bind="setSocketService" cardinality="1..1" interface="declarativemultiton.service.SocketService" name="SocketService" policy="static"/>
</scr:component>

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Consumer2" immediate="true">
   <implementation class="declarativemultiton.consumer2.Consumer2"/>
   <reference bind="setSocketService" cardinality="1..1" interface="declarativemultiton.service.SocketService" name="SocketService" policy="static"/>
</scr:component>

Everthing works as expected, and both components get the same instance:

Socket Service Impl activated
Consumer1 got SocketService@1769618707
Consumer1 activated
Consumer2 got socketservice@1769618707
Consumer2 activated

I need Component1 and Component2 to acquire different SocketService instances, and Component2 and Component3 (not shown) to have same SocketService instance.

If I change the configuration-policy to "require" no consumer component gets activated:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="require" name="SocketService">
   <implementation class="declarativemultiton.service.impl.SocketServiceImpl"/>
   <service>
      <provide interface="declarativemultiton.service.SocketService"/>
   </service>
</scr:component>    

This is where I am lost, I don't know how to pass configuration dynamically to the SocketService. I have been pulling my hair while reading about ConfigurationAdmin, ManagedService, ManagedServiceFactory, ComponentFactory etc. I could not found one concrete, concise solution. There are some conflicting approaches, like this answer https://stackoverflow.com/a/4129464/330464 says not to use ManagedService, but a Karaf Tutorial http://sully6768.blogspot.com.tr/2012/09/declarative-services-with-karaf-part-4.html addresses its use.

Community
  • 1
  • 1
b10y
  • 839
  • 9
  • 19

4 Answers4

1

I think your use of service properties to identify or distinguish between the SocketFactory instances and target properties on the consuming component to determine which one is used is the right approach, but I recommend doing it with configurations rather than multiple component xml's per implementation class. I'm not quite clear how many implementation classes for the SocketFactory or consumer you have. To illustrate I'm going to assume one each.

Instead of copying the component xml and modifying the properties there, you can use configuration of DS components through config admin. Peter Kriens has a good explanation of DS including configuration here: http://enroute.osgi.org/services/org.osgi.service.component.html. Here are some steps that cover setting up references using configuration in more detail:

  1. add configuration-pid to your component xmls. Lets assume socketFactory for the socketFactory and consumer for the consumer.

  2. install config admin. I think both the felix and eclipse ones work fine, I've only used the felix one.

  3. In a bundle where you want to arrange for a socket factory to get started, execute some code that gets the ConfigAdmin instance and call something like

    Configuration sfc = ca.createFactoryConfiguration("socketFactory", null);
    Hashtable<String, Object> props = new Hashtable<String, Object>();
    props.put("socketType", "MyType");
    sfc.update(props);
    

If this code is in the bundle with the SocketFactory code, you can leave out the null parameter. This will result in a SocketFactory component with a socketType=MyType service property, similar to what you did with component properties.

  1. In a bundle where you want to arrange for a consumer to be set up, do something similar but with the target filter:

    Configuration cc = ca.createFactoryConfiguration("consumer", null);
    Hashtable<String, Object> props = new Hashtable<String, Object>();
    props.put("SocketService.target", "(socketType=MyType)");
    cc.update(props);
    

This will result in a consumer component with the specified target filter.

You can perhaps alternatively struggle with e.g. felix fileinstall to install the configurations from property files. This is quite declarative but I don't have any experience with it and I've seen some people have trouble getting all the pieces lined up so it works. At work I have the luxury of using a system that works off of xml: it generates a schema from metatype, and combines an xml configuration document (conforming to the generated schema) with metatype to generate configurations. It can generate the target filter elements from e.g. nested xml. I often wish there was an open source version of this.

By the way, you might want to revisit your avoidance of the spec ds annotations. They aren't used at runtime but instead are processed by bnd when you assemble your bundle. (you are using bnd, aren't you?). Bnd will generate component.xml for the lowest possible ds version compatible with your component and it will do a lot more validation than you can do manually (at least than I can do manually).

0

You don't say which bundles Component1, 2 and 3 are in. You can use servicefactory=true to get DS to make a distinct instance of SocketService for each consuming bundle. In DS 1.3 (on Core R6), you can even use the new PrototypeServiceFactory support to make distinct instances for all consuming components. However, if you want to have complete control where some arbitrary components use the same and others use different instances of SocketFactory, then you have a problem which can be very hard to solve with DS.

BJ Hargrave
  • 9,324
  • 1
  • 19
  • 27
  • I intentionally left out mentioning bundles, in my opinion it shouldn't matter. serviceFactory=true does not really solve my problem, I cannot make bundle packaging design decisions based on single component. – b10y Oct 16 '15 at 19:22
  • It might not matter in your opinion, but it does matter in fact. Bundles are the units of deployments and have an activation context. Since servicefactory does not solve your problem, then I don't think DS can help in your requirement to have control over which instances of a service are used by an arbitrary component. Instance control is one of the things DS does and how it does it well specified. – BJ Hargrave Oct 17 '15 at 09:56
0

Copying XML files of the service solved my problem, but probably it is not the real-OSGI way.

First we copy the component descriptor for the service, but adding a property attribute to distinguish it from the other implementation:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="SocketService2">
   <implementation class="declarativemultiton.service.impl.SocketServiceImpl"/>
   <service >
      <provide interface="declarativemultiton.service.SocketService" />
   </service>
   <property name="socketType" type="String" value="server"/>
</scr:component>

Then we add target attribute to the consuming components:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Consumer2" immediate="true">
   <implementation class="declarativemultiton.consumer2.Consumer2"/>
   <reference bind="setSocketService" cardinality="1..1" interface="declarativemultiton.service.SocketService" 
        name="SocketService2" policy="static" target="(socketType=server)"/>
</scr:component>

Service implementation class gets the given properties in a Map with the activate method:

public class SocketServiceImpl implements SocketService {

    public void activate(Map<String, Object> props) {
        System.out.println("SocketServiceImpl@" + System.identityHashCode(this) + " activated with props: "
                + Joiner.on(';').withKeyValueSeparator("=>").join(props.entrySet()));
    }

    @Override
    public void startListening(int port) {
        //
    }

    @Override
    public String getNextMessage() {
        //
        return null;
    }

}

With just copying the XML file, not the implementation class, nor exposing implementation we can supply separate instances to the consumers:

SocketServiceImpl@936816937 activated with props: component.name=>SocketService;component.id=>0;objectClass=>[Ljava.lang.String;@5062e9b4
Consumer1 got SocketService@936816937
Consumer1 activated
SocketServiceImpl@2029093081 activated with props: objectClass=>[Ljava.lang.String;@37426497;socketType=>server;component.name=>SocketService2;component.id=>1
Consumer2 got socketservice@2029093081
Consumer2 activated
Consumer3 got SocketService@2029093081
Consumer3 activated

As said, I suspect there is a better way to accomplish this but I am fed up with the divergent documentation available.

b10y
  • 839
  • 9
  • 19
  • You created 2 SocketService components (different component descriptions) distinguished by service properties. Then your consumers can select the SocketService based upon the value of that service property. This is a fine solution to your use case. Your original question asked about multiple instances of a single SocketService component. This solution is a single instance for each of multiple SocketService components. I am glad you come up with a solution which addressed your requirements. – BJ Hargrave Oct 18 '15 at 15:20
0

First, your problem sounds a bit fishy? How can you share a service if different receivers can specify different ports to listen on? The startListening method sounds like it was left from an earlier try to share the same instance and dispatch behind its facade's to different servers?

I am assuming that you want to wire a service to different bundles. As David says, this is best done with configuration.

The approach is to create SocketListener services through factory configurations and use the target filter of a reference to get the right one:

@Component
public class Consumer1 {
  @Reference( target="(group=1)")
  SocketListener listener;

}

@Component
public class Consumer2 {
  @Reference( target="(group=2)")
  SocketListener listener;

}

@Designate( Config.class, factory=true )
@Component
public class SocketListenerImpl extends Thread 
  implements SocketListener {
  @interface Config {
    int port();
    String group();
  }
  private ServerSocket server;

  @Activate void activate(Config config ) {
    server = new ServerSocket( config.port());
    super.start();
  }

  public void run() { ... }

  @Override public String getNextMessage() { ... }
}

Just go to web console and edit your configuration there; it will show a GUI for the properties. The group property is not used in the code but will be available as service property so the consumers can do their target selection on it.

Note that the target filter can be overridden by configuration also. A property like target.listener=(group=3) on the com.example.Consumer1 PID would do the job.

P.S. This example is the current OSGi R6 but each feature is also possible with the bnd annotations used in R4.3.

P.S2. As David said, look at http://enroute.osgi.org/services/org.osgi.service.coordinator.html and https://github.com/osgi/osgi.enroute.examples/tree/master/osgi.enroute.examples.component.application Don't hesitate to provide a PR if you find that it is lacking.

Peter Kriens
  • 15,196
  • 1
  • 37
  • 55
  • Problem may sound fishy, most parts were left out for the sake of the example. Nevertheless, main necessity is to retrieve different or same instance of the component implementation. Your answer seems to be implementing multiton pattern based on "group" attribute but I cannot find my way to replace this code into DS component XML files, specifically the "@Designate" and "@interface" annotation. – b10y Oct 20 '15 at 10:21
  • The Designate creates metatype.xml. The Designate is only necessary to get WebConsole support so you can leave it out. You can also get rid of the Config and use a Map. In the bnd annotations Jar you find a Configurable that can do the same trick. Just don't want to show it the 'old' way. – Peter Kriens Oct 22 '15 at 07:19