Understanding the ‘unresolved constraint’, ‘missing requirement’ message from Apache Felix

It’s pretty common while developing an OSGi bundle that your imports and exports won’t quite match what you need or what exists in the server you’re deploying to. This can show up as NoClassDefFoundError, ClassNotFoundException or as log output in a stacktrace from bundle resolution. Hall, Pauls, McCullough and Savage did a great job of covering NCDFE and CNFE in “OSGi In Action” (chapter 8), let’s take a look at figuring out what the bundle resolution stacktrace is telling us. (I make nothing from the sales of “OSGi In Action” and suggest it to anyone interested in OSGi.)

Just like learning to read the stacktrace from an exception in Java is key to debugging, so is true about the dependency resolution messages from an OSGi container. Below is the output from Apache Felix when it encountered a missing dependency required by a bundle:

ERROR: Bundle org.sakaiproject.nakamura.webconsole.solr [124]: Error starting slinginstall:org.sakaiproject.nakamura.webconsole.solr-1.2-SNAPSHOT.jar (org.osgi.framework.BundleException: Unresolved constraint in bundle org.sakaiproject.nakamura.webconsole.solr [124]: Unable to resolve 124.0: missing requirement [124.0] package; (package=org.apache.solr.client.solrj) [caused by: Unable to resolve 84.0: missing requirement [84.0] package; (package=org.sakaiproject.nakamura.api.lite) [caused by: Unable to resolve 86.0: missing requirement [86.0] package; (&(package=com.google.common.collect)(version>=9.0.0)(!(version>=10.0.0)))]])
org.osgi.framework.BundleException: Unresolved constraint in bundle org.sakaiproject.nakamura.webconsole.solr [124]: Unable to resolve 124.0: missing requirement [124.0] package; (package=org.apache.solr.client.solrj) [caused by: Unable to resolve 84.0: missing requirement [84.0] package; (package=org.sakaiproject.nakamura.api.lite) [caused by: Unable to resolve 86.0: missing requirement [86.0] package; (&(package=com.google.common.collect)(version>=9.0.0)(!(version>=10.0.0)))]]
    at org.apache.felix.framework.Felix.resolveBundle(Felix.java:3443)
    at org.apache.felix.framework.Felix.startBundle(Felix.java:1727)
    at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1156)
    at org.apache.felix.framework.StartLevelImpl.run(StartLevelImpl.java:264)
    at java.lang.Thread.run(Thread.java:619)

What you have here is a stacktrace with a lengthy message. The important part of the stacktrace for us is the message.

ERROR: Bundle org.sakaiproject.nakamura.webconsole.solr [124]: Error starting slinginstall:org.sakaiproject.nakamura.webconsole.solr-1.2-SNAPSHOT.jar (org.osgi.framework.BundleException: Unresolved constraint in bundle org.sakaiproject.nakamura.webconsole.solr [124]: Unable to resolve 124.0: missing requirement [124.0] package; (package=org.apache.solr.client.solrj) [caused by: Unable to resolve 84.0: missing requirement [84.0] package; (package=org.sakaiproject.nakamura.api.lite) [caused by: Unable to resolve 86.0: missing requirement [86.0] package; (&(package=com.google.common.collect)(version>=9.0.0)(!(version>=10.0.0)))]])

This message is pretty simple but the structure is common for nastier messages (i.e. deeper resolution paths before failure). Let’s pull it apart to see what’s happening in there.

ERROR: Bundle org.sakaiproject.nakamura.webconsole.solr [124]: Error starting slinginstall:org.sakaiproject.nakamura.webconsole.solr-1.2-SNAPSHOT.jar

This very first part tells us that an error occurred while trying to load the org.sakaiproject.nakamura.webconsole.solrbundle. Nice start, but not quite the crux of the matter. Let’s keep reading.

org.osgi.framework.BundleException: Unresolved constraint in bundle org.sakaiproject.nakamura.webconsole.solr [124]: Unable to resolve 124.0: missing requirement [124.0] package; (package=org.apache.solr.client.solrj) [caused by: Unable to resolve 84.0: missing requirement [84.0] package; (package=org.sakaiproject.nakamura.api.lite) [caused by: Unable to resolve 86.0: missing requirement [86.0] package; (&(package=com.google.common.collect)(version>=9.0.0)(!(version>=10.0.0)))]])

Phew, that’s a lot of text! This is the heart of what we need though, so let’s break it down to make more sense of it.

(
    org.osgi.framework.BundleException: Unresolved constraint in bundle org.sakaiproject.nakamura.webconsole.solr [124]: Unable to resolve 124.0: missing requirement [124.0] package; (package=org.apache.solr.client.solrj)
        [
            caused by: Unable to resolve 84.0: missing requirement [84.0] package; (package=org.sakaiproject.nakamura.api.lite)
            [
                 caused by: Unable to resolve 86.0: missing requirement [86.0] package; (&(package=com.google.common.collect)(version>=9.0.0)(!(version>=10.0.0)))
            ]
        ]
)

What are those [number]s in the message?

The numbers in the message tell us the bundle ID on the server.

Unresolved Package Name Bundle ID Where Resolution Failed
org.apache.solr.client.solrj 124
org.sakaiproject.nakamura.api.lite 84
com.google.common.collect 86

Once you pull apart the message it becomes more obvious that it has structure and meaning! The structure of the message tells us that bundle 124 depends on a package from bundle 84 which depends on a package from bundle 86 which is unable to resolve com.google.common.collect;version=[9.0.0, 10.0.0). The innermost/very last message tells us the root of the problem; the dependency resolver was unable to find com.google.common.collect at version=[9.0.0, 10.0.0). Now we have somewhere to start digging.

How To Fix This

I suggest one of the following steps:

  1. Add a bundle that exports the missing package with a version that matches the required version
  2. Change the version to match an exported package already on the server

In this particular environment, com.google.common.collect;version=10.0.0 is what our server has deployed. The descriptor above specifically blocks any version not in the 9.x.x range. We generate the OSGi manifest by using the Maven Bundle Plugin which uses the BND tool to generate the manifest. In BND version > 2.1.0, the macro for versions was changed. Our solution has ranged from rolling back to bnd version=2.1.0 OR define the macro differently. The results are the same; the version segment in the manifest header becomes com.google.common.collect;version>=9.0.0 which finds our bundle of com.google.common.collect;version=10.0.0.


Notes about environment

The above message and stacktrace originated from a Sakai OAE environment which is built on Apache Sling and thusly Apache Felix. We use an artifact ID that is the root package of the bundle (org.sakaiproject.nakamura.webconsole.solr). This has the side effect that our bundle names look like package names in the message but gives a very clear naming convention.

Advertisements

When To Immediately Activate An OSGi Component

OSGi has a fantastic feature for immediate components [1] and delayed components [2]. This allows components to delay their possibly expensive activation until the component is first accessed. At the very least it allows the OSGi platform to consume resources as needed. No sense is sucking up those server resources for something that can wait.

As developers start their adventure into OSGi, I’ve noticed a pattern creeping up that I’m not sure folks are aware of: just because you like your component doesn’t mean it needs to start immediately. Unless your component has something it needs to do before other components can do their work, there’s little need to start a component immediately. A lot of times, the work can be moved into the bundle activator or is fine being delayed.

People often see that a component does something in its activation method and assume it should start immediately. The questions to ask yourself are:

  • Will the things that need to use this component have a reference to it?
    Yes? Delayed activation.
  • Before activation, will functionality be missing that should be available as soon as possible?
    Yes? Immediate activation.
  • Can something else do the job of activating the component at a later time?
    Yes? Delayed activation.

Registering with an external service, such as JMS, is a perfect scenario for an immediate component. If the JMS service were in OSGi, declarative services could do the work of making sure service trackers know your component exists, but OSGi has no jurisdiction over external resources. Binding to a JMS topic requires manual interaction and therefore should happen by way of immediate component activation.

Getting and setting of properties is not a case for immediate component activation. Neither is because the service is referenced by something else.

When using declarative services, a reference to your not-yet-started component will be given to anything that has said it wants to collect an interface you implement. This requires no effort on the part of the service being referenced and thusly does not require the referenced service to be activated immediately. Fret not! On first access of this service by any consumer the service will get activated.

OSGi has not been the fastest thing for me to learn but understanding the nuances of little things like this really helps me understand why it is a good approach at decoupled services and modular software construction.

1. OSGi compendium spec version 4.2, section 112.2.2
2. OSGi compendium spec version 4.2, section 112.2.3

Set OSGi Service Reference Target through Configuration

Passing properties into a component or component factory is pretty integral to OSGi.

A property can be a scalar or list of values.

@Component(configurationFactory = true, policy = ConfigurationPolicy.REQUIRE)
public class GreatExample {
  private static final String DEFAULT_VALUE = "someVal";
  @Property(value = DEFAULT_VALUE)
  static final String PROP_SOME_VAL = "prop.some.val";
  private String someVal;

  @Property
  static final String PROP_SOME_LIST = "prop.some.list";
  private List<String> someList;

  @Reference
  GreatService greatService;
  ...
  // later in the activate method
  protected void activate(Map<?, ?> props) {
    // do work to get properties and setting locally if needed.
    // For a good reference of how to properly retrieve properties
    // take a look at
    // http://svn.apache.org/viewvc/sling/trunk/bundles/commons/osgi/src/main/java/org/apache/sling/commons/osgi/OsgiUtil.java?view=markup
  }
}

The above example is terribly terse but the idea is pretty obvious. Now, from this, a more complex case can be built.

Let’s say you have different implementations of GreatService and you need to configure the above class to use a certain one. This is usually done with the ‘target’ property using ldap query syntax. This is what we’ll use to specify the wanted reference without hardwiring the particular implementation to our service consumer.

@Component
@Service
@Property(name="type", value="really")
public class ReallyGreatService implements GreatService {
}

Using this arbitrary property, we are able to give a ‘type’ to this implementation. We can pick a particular implementation using this in conjunction with the reference target.

@Reference(target="(type=really)")
GreatService greatService;

While this is handy it is a compile time solution. The more flexible way to go about this is to make this a configuration time concern. Using the properties passed in when configuring the new GreatExample instance, we can tell the service reference what target to filter on.

protected void activate(BundleContext bc) {
  // registering ReallyGreatService manually to make the example clearer.
  // could also be registered using SCR as the above annotations setup.
  GreatService gs = new ReallyGreatService();
  Hashtable gsProps = new Hashtable();
  gsProps.put("type", "really");
  ServiceRegistration reg = bc.registerService(GreatService.class.getName(), gs, gsProps);

  GreatExample ge = new GreatExample();
  Hashtable props = new Hashtable();
  props.put("greatService.target", "(type=really)"); // desired implementation of GreatService
  ServiceRegistration reg = bc.registerService(GreatExample.class.getName(), ge, props);
}

If you have a specific service you want to bind to (I don’t question your reasons; just giving examples), you can use the service.pid property of the service to set the target.

protected void activate(BundleContext bc) {
  GreatExample ge = new GreatExample();
  Hashtable props = new Hashtable();
  props.put("greatService.target", "(service.pid=com.myCompany.SomeGreatServicePid)"); // desired implementation of GreatService
  ServiceRegistration reg = bc.registerService(GreatExample.class.getName(), ge, props);
}

Update (2011-01-10): Added more details to example and corrected class references.

JavaMail in OSGi

UnsupportedDataTypeException

Sun decided ages ago that JavaMail and the Java Activation Framework should be released as separate artifacts and no one really cared. If you need JavaMail, you knew to also include JAF as Sun’s JavaMail page also told what version of JAF to use. Using this same approach, I created and deployed separate bundles for javax.activation and javax.mail. As soon as I tried to send the first test email, I found trouble. The most basic of email content types, text/plain, could not be sent.

javax.activation.UnsupportedDataTypeException: no object DCH for MIME type text/plain at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:885)

This sent me digging into the source of javax.activation and javax.mail to find the fix.
The problem here is that when the Java Activation Framework starts up, it loads META-INF/mailcap.defaults then looks for META-INF/mailcap files in other libraries in the same classloader. These mailcap files define handlers for specific types of content (eg. text/plain). Since JAF and JavaMail were in separate bundles and thusly classloaders, the mailcap file in JavaMail couldn’t be seen by JAF.
The fix I went with was to merge the 2 bundles into 1. I didn’t want to do this at first but after I saw that JAF uses an internal class named MailcapCommandMap, I decided that if the Sun developers felt it fine to give JAF explicit knowledge of JavaMail, it was fine with me to marry them to the same bundle.
If you’re using Equinox, you can use buddy classloaders without merging the bundles together, but I have a couple of problems with buddy classloaders.

  1. They are not part of the OSGi spec and break your ability to move to another container.
  2. They create a situation where a parent bundle has to have knowledge of children or “buddy” bundles. This goes against the basic whiteboard approach of OSGi.

Unable to resolve sun.security.util

Another fun bit to note is that starting with JavaMail 1.4.1, there is use of the sun.security.util package. If your OSGi container exports sun.* classes, you won’t notice this. If your OSGi container doesn’t export this or you run on a non-Sun JVM, this seems like a blocking issue. Fear not! After digging through the JavaMail code I find a comment in the SocketFetcher class that reads:

/*
 * First, try to use sun.security.util.HostnameChecker,
 * which exists in Sun's JDK starting with 1.4.1.
 * We use reflection to access it in case it's not available
 * in the JDK we're running on.
 */

This let me know that I can set this package to optional in my import list and all would be well. In other words, your bundle manifest should have something like this:

Import-Package: sun.security.util;resolution:=optional