Search code examples
javaosgi

Service registered as type, and implements interface,but is not able to be used as type


I ran into peculiar behavior when trying to resolve a reference with OSGi. There seems to exist a contradiction between how the resolution is handled, and what the bytecode indicates.

I wanted to resolve an OBR Repository with Felix, outside of the framework.

What piqued my interest was that even though I ask for a service of type org.osgi.service.repository.Repository, and I am able to get a service reference for that type, and I am able to get an object from that service reference, and that object's .getClass().getInterfaces() method returns org.osgi.service.repository.Repository (which I would think indicates that the object could be cast to Repository), both Repository.class.isAssignableFrom and instanceof return false for this object.

I looked at the sourcecode for OSGiRepositoryImpl and saw that it was in fact a subtype of Repository

import org.osgi.service.repository.Repository;

class OSGiRepositoryImpl implements Repository {
   // ...
}

I then looked at how the type was being registered in the activator, and saw that it was in fact registering it (presumably) correctly

import org.osgi.service.repository.Repository;
// ...

public void start(BundleContext context) {
    context = context;
    logger = new Logger(context);
    this.m_repoAdmin = new RepositoryAdminImpl(context, logger);
    context.registerService(RepositoryAdmin.class.getName(), this.m_repoAdmin, (Dictionary)null);
    context.registerService(Repository.class.getName(), new OSGiRepositoryImpl(this.m_repoAdmin), (Dictionary)null);
    // ...
}

So everything that I am able to look at seems to indicate that this should work.

I tried similar code using Comparable and Integer, and got the results I expected (can safely by cast).

package net.zephyrion.pokemod.launcher;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.osgi.service.repository.Repository;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;

public final class Launcher {

    public static void main(String[] args) throws BundleException {
        Framework framework = null;
        try {
            Map<String, String> frameworkConfig = new HashMap<>();
            frameworkConfig.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);

            FrameworkFactory frameworkFactory = ServiceLoader.load(FrameworkFactory.class).iterator().next();
            framework = frameworkFactory.newFramework(frameworkConfig);

            framework.start();

            BundleContext frameworkContext = framework.getBundleContext();

            System.out.println("Starting/Installing bundles...");
            Files.list(Paths.get("./", "bundles")).forEach(path -> {
                try {
                    System.out.println("method one path:\t" + path);
                    String absolutePath = path.toAbsolutePath().toString();
                    Bundle bundle = frameworkContext.installBundle("file:///" + absolutePath);
                    System.out.println("Starting bundle:\t" + bundle);
                    bundle.start();
                } catch (BundleException e) {
                    e.printStackTrace();
                }
            });
            System.out.println();

            printNumberExample();
            printInformation(frameworkContext);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            if (framework != null) {
                framework.stop();
                System.exit(0);
            }
        }
    }

    private static void printInformation(BundleContext frameworkContext) {
        ServiceReference<?> repositoryReferenceByName = frameworkContext.getServiceReference("org.osgi.service.repository.Repository");
        System.out.println("Repository Reference by Name:\t" + repositoryReferenceByName);

        Object serviceByName = frameworkContext.getService(repositoryReferenceByName);
        Class<?> serviceClass = serviceByName.getClass();

        System.out.println("interfaces implemented by " + serviceClass + ": {");
        Arrays.stream(serviceClass.getInterfaces()).forEach(interfaceClass -> System.out.println("\t" + interfaceClass));
        System.out.println("}");
        System.out.println();

        System.out.println("Repository Service by Name:\t\t" + serviceByName);
        System.out.println("Repository Service by Name Assignable from Repository:\t\t" + Repository.class.isAssignableFrom(serviceByName.getClass()));
        System.out.println("Repository Service by Name instanceof Repository:\t\t" + (serviceByName instanceof Repository));

        Repository repository = Repository.class.cast(serviceByName);
        System.out.println("Repository:\t" + repository);
    }

    private static void printNumberExample() {
        Integer number = 42;
        boolean comparableIsAssignableFromInteger = Comparable.class.isAssignableFrom(number.getClass());

        System.out.println("Comparable is assignable from Integer:\t" + comparableIsAssignableFromInteger);

        System.out.println("interfaces implemented by " + Integer.class + ": {");
        Arrays.stream(Integer.class.getInterfaces()).forEach(interfaceClass -> System.out.println("\t" + interfaceClass));
        System.out.println("}");

        Comparable comparableNumber = (Comparable) number;
        System.out.println("Comparable number:\t" + comparableNumber);
    }
}

What's interesting is that if I make a bundle, install the bundle in the framework, start it, and then try to resolve a Repository within the framework, it works just fine.

package net.zephyrion.pokemod.launcher;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.repository.Repository;

import java.util.Arrays;

public class TestActivator implements BundleActivator {

    @Override
    public void start(BundleContext bundleContext) throws Exception {
        System.out.println("=======================================");
        System.out.println("Bundle context:\t" + bundleContext);
        ServiceReference<Repository> repositoryReference = bundleContext.getServiceReference(Repository.class);
        System.out.println("Repository reference in activator:\t" + repositoryReference);

        Object serviceByName = bundleContext.getService(repositoryReference);
        Class<?> serviceClass = serviceByName.getClass();

        System.out.println("interfaces implemented by " + serviceClass + ": {");
        Arrays.stream(serviceClass.getInterfaces()).forEach(interfaceClass -> System.out.println("\t" + interfaceClass));
        System.out.println("}");
        System.out.println();

        Repository repository = (Repository) serviceByName;
        System.out.println("Repository Service:\t" + repository);

        System.out.println("Repository Service Assignable from Repository:\t\t" + Repository.class.isAssignableFrom(serviceByName.getClass()));
        System.out.println("Repository Service instanceof Repository:\t\t" + (serviceByName instanceof Repository));
    }

    @Override
    public void stop(BundleContext bundleContext) throws Exception {

    }
}

Below is the output

Framework context:  org.apache.felix.framework.BundleContextImpl@5ba23b66
Starting/Installing bundles...
method one path:    .\bundles\org.apache.felix.bundlerepository-2.0.10.jar
Starting bundle:    org.apache.felix.bundlerepository [1]
method one path:    .\bundles\org.osgi.service.obr-1.0.2.jar
Starting bundle:    org.osgi.service.obr [2]
method one path:    .\bundles\zlauncher.jar
Starting bundle:    launcher [3]
=======================================
Bundle context: org.apache.felix.framework.BundleContextImpl@482f8f11
Repository reference in activator:  [org.osgi.service.repository.Repository]
interfaces implemented by class org.apache.felix.bundlerepository.impl.OSGiRepositoryImpl: {
    interface org.osgi.service.repository.Repository
}

Repository Service: org.apache.felix.bundlerepository.impl.OSGiRepositoryImpl@6767c1fc
Repository Service Assignable from Repository:      true
Repository Service instanceof Repository:       true

Comparable is assignable from Integer:  true
interfaces implemented by class java.lang.Integer: {
    interface java.lang.Comparable
}
Comparable number:  42
Repository Reference by Name:   [org.osgi.service.repository.Repository]
interfaces implemented by class org.apache.felix.bundlerepository.impl.OSGiRepositoryImpl: {
    interface org.osgi.service.repository.Repository
}

Repository Service by Name:     org.apache.felix.bundlerepository.impl.OSGiRepositoryImpl@6767c1fc
Repository Service by Name Assignable from Repository:      false
Repository Service by Name instanceof Repository:       false
java.lang.ClassCastException: Cannot cast org.apache.felix.bundlerepository.impl.OSGiRepositoryImpl to org.osgi.service.repository.Repository
    at java.lang.Class.cast(Class.java:3369)
    at net.zephyrion.pokemod.launcher.Launcher.printInformation(Launcher.java:79)
    at net.zephyrion.pokemod.launcher.Launcher.main(Launcher.java:50)

Process finished with exit code 0

Note: I know the preferred way of getting a service is to use OSGi DS. But because I'm embedding the framework, and providing an easy to use launcher, I want to be able to use service objects outside the context of the framework.

Why am I able to resolve the service within the framework, but not outside the framework, even though, the service reference returns the same object? Why is it that the same object can be cast to a Repository within the framework, but not outside of it?


Solution

  • At runtime, a Class is uniquely identified by the ClassLoader which loads it. So the same exact class file can be loaded by different class loaders and not be equals. So what is likely happening here is that your code is getting the interface class loaded from a different class loader than the implementation class.

    Update your test code to also display the class loaders of the interface class.

    You will probably find that there is more than one bundle exporting the interface class. You must ensure the service consumer and service provider use the same service interface class (loaded from the same class loader).