osgi aem bundle resolver hook example header image

OSGi: Control resolving process of the bundle

In this article, we will take a look at how to control resolving process for bundles, in particular, how to restrict access to the bundle only for a specific set of OSGi bundles. This may be useful when you want to give access to the bundle only to authorized ones and prevent others from using it for satisfying imported packages. In such cases, you should take a look at interfaces ResolverHookFactory and ResolverHook.

ResolverHookFactory

It has one method:

ResolverHook begin(Collection<BundleRevision> triggers);

It’s called by Felix each time resolve process begins. As a parameter, it accepts a collection of bundles which triggered this resolving process. This method returns hook, which has 4 methods but we are interested only in one of them.

Here is how our ResolverHookFactory can look like:

package com.taradevko.aem.hook;

import org.osgi.framework.hooks.resolver.ResolverHook;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.framework.wiring.BundleRevision;
import org.apache.felix.scr.annotations.*;
import java.util.Collection;

@Component(immediate = true)
@Service(value = ResolverHookFactory.class)
public class TestHooksFactory implements ResolverHookFactory {
    public ResolverHook begin(Collection<BundleRevision> triggers) {
        return new TestResolverHook();
    }
}

In our case, we are not interested in bundles which trigger resolving process so we just create and return our resolver hook.

ResolverHook

In our example we will use next method:

void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates);

There we can filter out candidates (other bundles) to be wired to the bundle specified in requirement object. In other words:

  • requirement – we will use it to extract the name of the bundle, which needs some requirements to be resolved;
  • candidates – this object contains candidates for resolving given requirement. We need to remove from this collection any candidates which are not allowed to be used by the bundle from above.

 

package com.taradevko.aem.hook;

import org.osgi.framework.hooks.resolver.ResolverHook;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRequirement;
import org.osgi.framework.wiring.BundleRevision;
import java.util.*;

public class TestResolverHook implements ResolverHook {

    private static final Map<String, List<String>> restictions = new HashMap<>();
    static {
        restictions.put("org.apache.abdera.parser", Arrays.asList("org.apache.abdera.server", "org.apache.abdera.client"));
    }

    @Override
    public void filterResolvable(Collection<BundleRevision> candidates) {    }

    @Override
    public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) {    }

    @Override
    public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
        candidates.removeIf(candidate -> {

            final String candidateName = candidate.getRevision().getSymbolicName();
            final String requirementName = requirement.getRevision().getSymbolicName();

            final List<String> allowedList = restictions.get(candidateName);
            return allowedList != null && !allowedList.contains(requirementName);
        });
    }

    @Overrideh
    public void end() {    }
}

In filterMatches, we iterate over the candidates and check if we have any restrictions for this bundle. If found – we check if requirement (bundle) is allowed to use this candidate. If it’s not allowed – we remove the candidate from the collection (and as a result, removed bundle will not be used by Felix to resolve requirement).

As an example I took org.apache.abdera.parser which should be accessible only by org.apache.abdera.server and org.apache.abdera.client bundles.

For testing purposes I’ve created a test bundle with one class – Activator, which imports package from the “protected” bundle:

<Import-Package>org.apache.abdera.parser,*</Import-Package>

Result

Let’s deploy our test bundle and see if all requirements of it are resolved:

#From test module dir
mvn clean install -PautoInstallBundle

osgi aem bundle resolving example test bundle

So bundle is in active status and all requirements are resolved. Let’s deploy the bundle with resolver hook.

#From hook module dir
mvn clean install -PautoInstallBundle

osgi aem resolver hook bundle

Now we need to trigger resolving process so our resolver hook will prevent test bundle from resolving restricted dependency. Let’s trigger package refresh for test bundle and see what happen next – now test bundle is in installed state and in the console (error.log) we can see the error message like:

osgi aem bundle resolving unable resolve error

That’s it. Now only restricted set of bundles can use org.apache.abdera.parser.

Note: in the real world, you would want to control start level of the hook bundle to make sure that it’s started at the beginning (probably start level 1) so no bundle will bypass your resolver hook.

All code from this axample can be found in the github repository.

Your thoughts are welcome