0
votes

We have been using OSGI with Jboss 7.0.1 and have multiple bundles supporting our application. We are trying to adapt the versioning strategy of Major.Minor.Micro for Interface + Service Implementation + Consumer based model, but it seems our strategy might not be right.

When we bump up the minor version for api and service the consumer is unable to use the new service without having to do package refresh.

Below is the usecase.

Package com.helloworld.api is exported with version 1.0.0 from bundle Helloworld API (interface bundle)

public interface IHelloService {
    public void sayHello(String abc);
}

Menifest File for Helloworld API

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Helloworld API
Bundle-SymbolicName: exp1.com.helloworld.api
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.helloworld.internal.Activator
Bundle-Vendor: ABC
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.4.0"
Bundle-ClassPath: .
Export-Package: com.helloworld.api;version="1.0.0"

Implemented by HelloServiceImpl from bundle Helloworldservice

public class HelloServiceImpl implements IHelloService {

@Override
  public void sayHello(String abc) {
      System.out.println(" \n\n ~~~~~~~~~ "+abc+" ~~~~~~~~~  " + "   \n\n");
  }
}

Menifest File for Helloworldservice

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Helloworldservice
Bundle-SymbolicName: exp1.com.helloworld.service
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.helloworldservice.internal.Activator
Bundle-Vendor: ABC
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: com.helloworld.api;version="1.0.0",
 org.osgi.framework;version="1.3.0"
Bundle-ClassPath: 

Consumed by Consumer from bundle Consumer Service (Using service tracker or Google Guice approach). It keeps on calling sayHello in loop.

HelloWorldServiceProxy.getInstance().getHelloWorldService().sayHello("abc  " + "Index" + i);

Menifest File for Consumer Service

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Consumer Service
Bundle-SymbolicName: exp1.com.consumer
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.consumer.internal.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: com.helloworld.api;version="1.0.0",
 org.osgi.framework;version="1.3.0",
 org.osgi.util.tracker;version="1.4.0"

After that we make the modification to api and add new method. This results into bumping up the minor version to 1.1.0. To support this we implement the new method in service but expect the consumer to be untouched.

package com.helloworld.api is exported with version 1.1.0 from bundle Helloworld API

public interface IHelloService {
   public void sayHello(String abc);
   public void sayHello(String abc, String def, int a);
}

Menifest File for Helloworld API

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Helloworld API
Bundle-SymbolicName: exp1.com.helloworld.api
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.helloworld.internal.Activator
Bundle-Vendor: ABC
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.4.0"
Bundle-ClassPath: .
Export-Package: helloworld.api;version="1.1.0"

Implemented by HelloServiceImpl from bundle Helloworldservice

public class HelloServiceImpl implements IHelloService {
  @Override
  public void sayHello(String abc) {
    System.out.println(" \n\n ~~~~~~~~~ "+abc+" ~~~~~~~~~  " + "   \n\n");
  }
  @Override
  public void sayHelloNew(String abc, String def, int a) {
    System.out.println(" \n\n ~~~~~~~~~ "+abc+" NEW METHOD ### WITH CHANGE ### ~~~~~~~~~  " + abc +"  " + def +"   \n\n");
  }
}

Menifest File

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Helloworldservice
Bundle-SymbolicName: exp1.com.helloworld.service
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.helloworldservice.internal.Activator
Bundle-Vendor: ABC
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: com.helloworld.api;version="1.1.0",
 org.osgi.framework;version="1.3.0"
Bundle-ClassPath:

However, now when we deploy HelloWorld Api and HelloWorld Service bundles, the consumer bundle is unable to start using the new service exported with package 1.1.0. We are having to refresh the consumer bundle in order to make it work.

Are we doing something wrong here ?

So far we have been deploying new bundles without any version (defaults to 0.0.0). Deploying new bundles with same package revision works fine for micro changes but any method signature change or new method usage results into NoSuchMethod exception until we perform the global package refresh from Jboss. That is the reason we decided to use Jboss version strategy.

1
What is the meaning of BKM? - Harald Wellmann
Best Known Method :) - Manan

1 Answers

0
votes

After you install/update new bundles you should ALWAYS refresh. No exceptions. The only reason that refresh is not integrated with update/install is that you can now install/update multiple bundles before you refresh.

I am also a bit puzzled about your examples. You talk about semantic versioning but your imports are from [1.3.0,∞), so there is no semantic version in play? (It also helps to remove unnecessary information like most of the headers.)

So what is the best practice?

First, a service is defined by a package. For this service you can have two types of interfaces: interfaces implemented by the consumers and interfaces by the providers. A consumer interface is only changed in a non-backward compatible when for a major release bump. A provider interface is broken on a minor release bump. (major.minor.micro) OSGi supports the @ConsumerType (default) and @ProviderType annotations for an interface.

If you use bnd then you automatically get an import range. I.e. if your IHello (better not append it with service) starts at 1.0:

 IHello.java:
 package com.example.hello;

 public interface IHello {
   void sayHell();
 }

 @Version("1.0.0")
 package-info.java:

 import org.osgi.annotation.versioning.Version;

Since the default case this is a @ConsumerType we will get the following import:

 Import-Package: com.example.hello;version='[1.0,2)'

However, the IHello interface likely represents a provider. So we should annotate it with the @ProviderType:

 IHello.java:
 package com.example.hello;

 import org.osgi.annotation.versioning.ProviderType;

 @ProviderType
 public interface IHello {
   void sayHell();
 }

If you now write a provider that implements the IHello service bnd will detect the version of the com.example.hello package. Since it sees that you implement it, it will write out the following import:

 Import-Package: com.example.hello;version='[1.0,1.1)'

If we had written a client of the IHello service our import would have been [1.0,2).

If we now add a method to IHello we should bump the version to 1.1 since IHello was a @ProviderType. (If it had been a @ConsumerType we would have been forced to bump it to 2.0)

 package-info.java:
 @Version("1.1.0")
 package-info.java:

 import org.osgi.annotation.versioning.Version;

 IHello.java:
 package com.example.hello;

 import org.osgi.annotation.versioning.ProviderType;

 @ProviderType
 public interface IHello {
   void sayHell();
   void sayHello(String s);
 }

We now effectively broke the provider of the IHello service, which is exactly what we want! This will prevent calling a 1.1 service that is backed by an implementation that was compiled against 1.0.

Summary:

  • refresh is mandatory after install/update.
  • Used bnd