Search code examples
javamavenosgiosgi-bundle

How to call one of the methods in my Bundle-A jar file after it has been installed into OSGi container


I have recently started working on OSGi framework. I have one bundle named Bundle-A. I want to call one of the methods in Bundle-A jar from my main application.

I have loaded and installed Bundle-A from my main application. Below is my code for my main application where I am installing Bundle-A.

private void initializeModelFramework() {

    try {

        FileUtils.deleteDirectory(new File("felix-cache"));
        FrameworkFactory frameworkFactory = ServiceLoader.load(FrameworkFactory.class).iterator().next();

        Framework framework = frameworkFactory.newFramework(new HashMap<String, String>());
        framework.start();

        BundleContext bundleContext = framework.getBundleContext();

        modulesNameVersionHolder.put("Bundle-A", "1.0.0");

        List<Bundle> installedBundles = new LinkedList<Bundle>();

        String basePath = "C:\\ClientTool\\LocalStorage";

        for (Map.Entry<String, String> entry : modulesNameVersionHolder.entrySet()) {
            String version = entry.getValue();
            final String filename = name + Constants.DASH + version + Constants.DOTJAR;
            final String localFilename = GoldenModulesConstants.FILE_PROTOCOL + basePath+ File.separatorChar + filename;

            installedBundles.add(bundleContext.installBundle(localFilename));
        }   

        for (Bundle bundle : installedBundles) {
            bundle.start();// this will start bundle A
        }

        // After starting the Bundle-A, now I need to call one of the methods in Bundle-A
        for(int i=0; i<=10; i++) {
            //call processingEvents method of Bundle-A class GoldenModelFramework
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

Now the Bundle-A has been started up. Below is my Activator class for Bundle-A.

public class Activator implements BundleActivator {

    private static final String BUNDLE_VERSION_KEY = "Bundle-Version";
    private static Logger s_logger = Logger.getLogger(Activator.class.getName());

    @Override
    public void start(BundleContext context) throws Exception {

        final Bundle bundle = context.getBundle();
        final String bundleName = bundle.getSymbolicName();
        final String bundleVersion = (String) bundle.getHeaders().get(BUNDLE_VERSION_KEY);

        System.out.println(bundleName+" - "+bundleVersion);
    }

    @Override
    public void stop(BundleContext context) throws Exception {
            System.out.println("Bye.!");
    }
}

And below is the class I have in Bundle-A jar. I need to call processingEvents method from my above main application code as soon as Bundle-A has been started.

public class GoldenModelFramework {

    private static final Logger LOGGER = Logger.getLogger(GoldenModelFramework.class.getName());
    private static final long checkingAfterEveryXMinutes = 15L;


    public GoldenModelFramework() {
        // following the traditions
    }

    public static void processingEvents(final String item) {

        for (BundleRegistration.HolderEntry entry : BundleRegistration.getInstance()) {
            final String response = entry.getPlugin().process(item);
            System.out.println(response);
        }
    }
}

I am not sure what's the right way to do it? I know one way is add the dependency of this Bundle-A in my main application pom.xml file as I am using maven based project. But I don't think so that's the right way to do it. Because ultimately, I am going to have some more bundles so there should be some other way around for this which I am not aware of.

Should I am supposed to use ServiceListener or ServiceTracker here? Any simple example basis on my code will help me understand much better. Thanks.

I hope the question is clear enough. I am trying to call one of the methods in Bundle-A after it has been loaded up and installed.


Solution

  • You have several choices:

    Import the package dynamically

    You can use DynamicImport-Package instead of Import-Package. In this case bundle-A does not have to be active when the main bundle starts. Although this works I do not recommend this solution as I do not like DynamicImport-Package. In this case of course bundle A has to be a dependency of the main bundle.

    Using reflection

    You can call the method you want with reflection like the following (draft example):

    Class<GoldenModelFramework> clazz = bundleA.loadClass("GoldenModelFramework");
    Method m = clazz.getMethod("processingEvents", String.class);
    m.execute(null, myParam);
    

    This is a bit better, however this solution is still a bit foggy, I would not say this is a clean code.

    Using interface and OSGi service

    The cleanest way would need a bit of refactoring. In this case you should create an interface and in the Activator of bundle A you should register a service based on that interface. In the main bundle you should use a service tracker to catch that service and call the method on it.

    In case you really want to make your method processEvent static, the registered service object (that is based on the interface) should simply call the static method inside.

    Not to have the necessity to add bundle A as a dependency to the main bundle the interface should be taken into a third bundle that is the dependency of both, the main and A bundle.

    Although this solution seems to be the most complex I would suggest this one.

    An example:

    Create an interface and put it to a new bundle like goldenframework-api.

    public interface GoldenModelFrameworkOSGi {
        void processingEvents(final String item);
    }
    

    The goldenframework-api will be a dependency of main bundle and bundle-A. The main bundle will use it, while bundle-A will implement it.

    Here is how bundle A implements it:

    public class GoldenFrameworkOSGiImpl {
        public void processingEvents(final String item) {
            GoldenModelFramework.processEvents(item);
        }
    }
    

    Create an Activator class in bundle-A (I will leave out your code in that activator to have less typing):

    public class Activator {
      private ServiceRegistration goldenFrameworkSR;
    
      @Override
      public void start(BundleContext context) {
        goldenFrameworkSR = context.registerService(GoldenFrameworkOSGi.class, new GoldenFrameworkOSGi(), new HashTable());
      }
    
      @Override
      public void stop(BundleContext context) {
        goldenFrameworkSR.unregister();
      }
    }
    

    As you know the code of Bundle-A you can cheat a bit. When bundle-A is in Active state you can be sure that the service you need is registered. However, in the future you should think in working based on events (like using a ServiceTracker). I mean this will be a bad practice :) :

    ServiceReference sr = context.getServiceReference(GoldenServiceOSGi.class);
    GoldenServiceOSGi gs = context.getService(sr);
    gs.processEvents(...);
    context.ungetService(sr);
    

    This might solve your problem for now and you can continue with your work. However, please consider reading a book like "OSGi in Action" to have a feeling about OSGi bundle and service lifecycles so you may re-design your framework.