Inspect the magic of Eclipse Fragments

In the post ‘Basics about OSGi Classloading’ we have learned the fundamental knowledge how Classes are defined and loaded within OSGi.
And we have worked with ‘pure’ OSGi Bundles. But there is another ‘type’ of a Bundle, called Fragment. Basically a Fragment is an extension to its Host-Bundle.

And because such a Fragment will lead to a ‘special’ kind of Classloading within Equinox, we will take a look at what happens if we load a Class out of a Fragment.

This is our test setup (get it from git@github.com:codescale/osgi-playground.git):

designfragmentclassloading-2011-04-14-23-21.png

Our starting point is a Console Service to control the test executions. This Bundle does have a dependency to ‘org.codescale.bundle.a’ which does have the Fragment ‘org.codescale.fragment.a’. This Fragment does define another dependency to ‘org.codescale.bundle.b’.

To learn about the behavior of OSGi and Fragments we will go through these six parts:

  1. Load a Class from the Fragment whereat the package is only exported by the Bundle A
  2. Load a Class from the Fragment whereat the package is also exported by the Fragment
  3. Load a Class from Bundle A whereat the package is only exported by the Fragment
  4. Load a Class from Bundle B from a Class inside of the Bundle A at what this bundle does not have the necessary dependency
  5. Load a resource from Bundle A from within a Class of Bundle A
  6. Load a resource from the Fragment from within a Class of Bundle A

Let’s start…

If you have setup a workspace in Eclipse with the source from git@github.com:codescale/osgi-playground.git you can start an OSGi Framework using the prepared launcher from the project ‘org.eclipse.console’.

Now we start an OSGi Framework with all the four bundles started.

Part 1
Enter ‘loadClass org.codescale.bundle.a.PublicBundleClass‘ into the console. After executing this command you will see the following output:

Load the class 'org.codescale.bundle.a.PublicBundleClass', called by bundle 'org.codescale.console'.
The class was loaded by 'org.codescale.bundle.a' instance '64212394'.
...

In the first line we see that the ‘Console Bundle’ is going to load a class which seems to be ‘Bundle A’. Let’s have a look at this bundle.

eclipse3-2011-04-14-23-21.png

There is not such a Class. But let’s take a look at the fragment.

eclipse2-2011-04-14-23-21.png

Here it is Ok what happens? As we’ve learned, the place to step into is the ‘BundleLoader#findClassInternal’.
(1) The first Bundle-ClassLoader which will be checked is the one of the ‘Console Bundle’, of course. (2) There the required ‘Bundle A’ is identified to be a possibility to load the Class, because this bundle does export the necessary package. (3) Within this bundle, firstly the ClasspathManager tries to load the class from the bundle itself, then (if this approach fails) all the fragments are called.

eclipse2-2011-04-14-23-211.png

This is the code that does iterate through all the fragments and tries to load the class.

for (int i = 0; i < fragments.length; i++) {
 ClasspathEntry[] fragEntries = fragments[i].getEntries();
 for (int j = 0; j < fragEntries.length; j++) {
  result = findClassImpl(classname, fragEntries[j], hooks);
  if (result != null)
   return result;
 }
}

That’s how it happens, that the class will be loaded by ‘Bundle A’.

Part 2
Enter loadClass org.codescale.fragment.a.PublicFragmentClass into the console to get a class loaded whose package is exported by the fragment. The output will be:

Load the class 'org.codescale.fragment.a.PublicFragmentClass', called by bundle 'org.codescale.console'.
The class was loaded by 'org.codescale.bundle.a' instance '1762688005'.
...

O.K. the class is once again loaded by ‘Bundle A’. How?
Stepping through the ‘BundleLoader’ we see that the ‘Bundle A’ does have all his exported packages, plus the one of the Fragment. That’s the key point! The Bundle description of ‘Bundle A’ and its Fragment are merged. This will happen in ‘org.eclipse.osgi.internal.module.ResolverBundle’ e.g. #getExportedPackages().
With the help of the ‘PackageAdmin’ Service we can get some infos out of ‘Bundle A’.

Symbolic-Name: org.codescale.bundle.a
The bundle exported packages:
 > org.codescale.bundle.a
 > org.codescale.fragment.a
 > org.codescale.bundle.internal.a
The bundle does have 1 fragments:
 > org.codescale.fragment.a

There you see, that ‘Bundle A’ does have three exported packages, merged together by the bundle itself an its Fragment.

Part 3
Enter loadClass org.codescale.bundle.internal.a.InternalBundleClass into the console to get a class loaded from ‘Bundle A’ even though the package is exported by its Fragment.

Because of the previous recognition we now know that this will work. See the output:

Load the class 'org.codescale.bundle.internal.a.InternalBundleClass', called by bundle 'org.codescale.console'.
The class was loaded by 'org.codescale.bundle.a' instance '744919514'.

Both Bundle descriptions are merged, so the ‘Bundle A’ does export the necessary package through the definition by its Fragment.

Part 4
Now we’re going to load a class from ‘Bundle B’ by a class of ‘Bundle A’. This scenario is equal to the previous Part 2 and 3. The Fragment does extend the Bundle-Description of ‘Bundle A’ with an additional dependency to ‘Bundle B’.

You can step through this by entering instance org.codescale.bundle.internal.a.InternalBundleClass into the console.

Part 5
Enter instance org.codescale.bundle.internal.a.InternalBundleClass into the console to get a stream of a resource which is located in ‘Bundle A’. This will be the output:

Instantiate the class 'org.codescale.bundle.internal.a.InternalBundleClass', called by bundle 'org.codescale.console'.
The class was loaded by 'org.codescale.bundle.a' instance '2052558331'.
Created instance of org.codescale.bundle.internal.a.InternalBundleClass
The resource /resources/bundle.file was found.
Content > Hi I'm the bundle.file

First of all the Class will be loaded (as we’ve done previously) then we create an instance of it. Within the constructor a resource will be loaded.

URL url = getClass().getResource("/resources/bundle.file");

This scenario is not so special – we’ve just requested a resource within the same bundle as the class lives. Anyway let’s have a look at the ‘BundleLoader’ (this time the method ‘findResource(String, boolean)’) to get the default behavior known.

eclipse-2011-04-14-23-21.png

As you see it’s not so different to Part 1 where we’ve looked at the Stacktrace of Classloading. We also end up at the ‘ClasspathManager’.

Part 6
If you enter instance org.codescale.fragment.a.PublicFragmentClass into the console a resource from the Fragment should be loaded by a class of ‘Bundle A’. As we see in the output, the resource is loaded as well:

Instantiate the class 'org.codescale.fragment.a.PublicFragmentClass', called by bundle 'org.codescale.console'.
The class was loaded by 'org.codescale.bundle.a' instance '1609377764'.
Created instance of org.codescale.bundle.internal.a.InternalBundleClass
The resource /resources/fragment.file was found.
Content > Hi I'm the fragment.file

One more time the implementation of the ‘ClasspathManager’ does the trick.

private URL findLocalResourceImpl(String resource, int classPathIndex) {
 ...
 // look in fragments
 for (int i = 0; i &lt; fragments.length; i++) {
  ClasspathEntry[] fragEntries = fragments[i].getEntries();
  for (int j = 0; j &lt; fragEntries.length; j++) {
   result = findResourceImpl(resource, fragEntries[j].getBundleFile(), curIndex);
   if (result != null &amp;&amp; (classPathIndex == -1 || classPathIndex == curIndex))
    return result;
   curIndex++;
   }
  }

Conclusion
Now we know how a fragment does behave in a OSGi environment. A Fragment can help you to interact directly with its Host-Bundle ‘without borders’. This can be helpful if you have to test the internal implementation of a Bundle. Another imaginable region is to extend a third party library (because of this Bundle-Description merge). But be aware you can’t ‘overwrite’ a Class of your Host-Bundle. A Class will be loaded first from its own Bundle, then from the Fragments!

Finally I leave you with a question. We add a Fragment to ‘Bundle B’, add a dependency from ‘Bundle A’ to ‘Bundle B’ and (as we’ve done in Part 6) load a resource from a Class in ‘Bundle A’ out of this Fragment. Will it work?

be happy until cancelled,
Your Codescale’s

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.