OSGi Bundle Repository

This article is based on OSGi in Action , published on April 2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code 'java40beat' and get 40% discount on eBooks and pBooks ]

also read:

Introduction

The OSGi Bundle Repository (OBR) is officially not an OSGi standard specification; rather, it’s a proposal for a specification, internally referred to as RFC 112 in the OSGi Alliance. Because OBR is only an RFC, its details may change in the future, but it’s still a useful tool as it is.

OBR started life as the Oscar Bundle Repository, which was associated with the Oscar OSGi framework (which ultimately became the Apache Felix framework). OBR is intended to address two aspects of bundle deployment:

  • Discovery-Provide a simple mechanism to discover which bundles are available for deployment
  • Dependency deployment—Provide a simple mechanism to deploy a bundle and its transitive set of dependencies

To achieve the first goal, OBR defines a simple bundle repository with an API for accessing it and a common XML interchange format for describing deployable resources. An OBR repository can refer to other OBR repositories, defining a federation of repositories. But it’s not necessary to define federations, so it’s possible to create independent repositories specifically for your own purposes and applications. One of the main goals of OBR was simplicity, so it’s easy for anyone to provide a bundle repository. One of the benefits of using an XML-based repository format is that no server-side process is needed (although server-side processes are possible). Figure 1 shows the federated structure of an OBR repository.

The key concept of an OBR repository is a generic description of a resource and its dependencies. A resource is an abstract entity used to represent any type of artifact such as a bundle, a certificate, or a configuration file. The resource description allows an agent to discover applicable artifacts, typically bundles, and deploy them along with their transitive dependencies. Each resource description has

  • Zero or more requirements on other resources or the environment
  • Zero or more capabilities used to satisfy other resources’ requirements

Resource requirements are satisfied by capabilities provided by other resources or the environment. OBR maps bundle metadata from Import-Package and Require-Bundle headers onto resource requirements and from Export-Package and Bundle-SymbolicName headers onto resource capabilities. Figure 2 shows the relationship among the repository entities.

Using this information, an OBR implementation is able to resolve a consistent set of bundles for deployment given an initial set of bundles to be deployed. OBR‘s dependency resolution algorithm is basically the same as the framework’s dependency-resolution algorithm.

OBR vs. framework resolution

Although the dependency-resolution algorithms for OBR and the framework are similar, they aren’t identical. OBR starts from a given set of bundles and pulls in resources from its available repositories in an attempt to satisfy any dependencies. The framework’s resolution algorithm will never pull in additional resources; it only considers installed bundles.

Another gotcha is the fact that the current OBR RFC doesn’t currently mandate uses constraints when resolving dependencies. This can lead to unexpected failures at execution time if a uses constraint prevents bundles from resolving. OBR is an active area of work within the OSGi Alliance, so future revisions of the RFC may address this issue.

With this overview of OBR, let’s look at how you can create a repository for it.

Creating OBR repositories

To illustrate how to create an OBR repository, let’s use the bundles from the service-based paint program example. The repository is just an XML file containing the metadata of the bundles. We’ll go through the entries in the XML file and explain the schema along the way. Assume you have the bundles from the example in a directory called paint-bundles. The directory contains the paint frame bundle, the API bundle, and the three shape bundles:

paint-bundles/
		frame-4.0.jar
		circle-4.0.jar
		triangle-4.0.jar
		shape-4.0.jar
		square-4.0.jar

You could create the repository XML file by hand, but you can use several different tools to create one instead. This example uses BIndex (http://www.osgi.org/Repository/BIndex), which is provided by the OSGi Alliance. For Maven users, there’s also Maven support, which we’ll discuss in appendix A. To create a repository using BIndex, run the following from above the bundles directory (this example assumes you’re in the chapter10/combined-example/ directory of the companion code):

java -jar bindex.jar -r repository.xml -n Paint paint-bundles/*.jar

This creates a repository.xml file that contains the metadata of the bundles from the example. The main XML element is a repository tag defining the repository:

<repository lastmodified='20090215101706.874' name='Paint'>
	...
	</repository>

The lastmodified attribute is used as a timestamp by the OBR service to determine whether something has changed. The most interesting element is the <resource> tag: it describes a bundle you want to make available. The created repository XML file contains one resource block per bundle. The shape API bundle converted into OBR is as follows.

Listing 1 Shape API bundle converted into OBR repository XML syntax

<resource id='org.foo.shape/4.0.0' presentationname='shape'
		symbolicname='org.foo.shape' uri='paint-bundles/shape-4.0.jar'
		version='4.0.0'>
		<size>
			5742
		</size>
		<license>

http://www.apache.org/licenses/LICENSE-2.0

		</license>
		<documentation>

http://code.google.com/p/osgi-in-action/

		</documentation>
		<capability name='bundle'> 							#1
			<p n='manifestversion' v='2'/> 						#1
			<p n='presentationname' v='shape'/> 					#1
			<p n='symbolicname' v='org.foo.shape'/> 				#1
			<p n='version' t='version' v='4.0.0'/> 					#1
		</capability> 									#1
		<capability name='package'> 							#2
			<p n='package' v='org.foo.shape'/> 					#2
			<p n='version' t='version' v='4.0.0'/> 					#2
		</capability> 									#2
		<require extend='false' filter='(&(package=org.foo.shape) 	 		#3
			(version>=4.0.0)(version<5.0.0))' multiple='false' name='package' 	#3
			optional='false'> 							#3
			Import package org.foo.shape ;version=[4.0.0,5.0.0) 			#3
		</require> 									#3
	</resource>
	#1 Capability element representing bundle
	#2 Capability element representing package
	#3 Requirement element

The capability elements (#1) and (#2) represent what the bundle provides. In this case, (#1) represents the bundle itself, because the bundle can be required (for example, Require-Bundle), whereas (#2) represents the package exported by the bundle. Bundle dependencies are represented as requirement elements, such as the one for an imported package (#3). Both capabilities and requirements have a name, which is actually a namespace; it's how capabilities are matched to requirements. For example, capabilities representing exported packages and requirements representing imported packages both have the package namespace.

In general, a capability is a set of properties specified using a <p> element with the following attributes:

  • n—The name of the property
  • v—The value of the property
  • t—The type of the property, which is one of the following:
    • string—A string value, which is the default
    • version—An OSGi version
    • uri—A URI
    • long—A long value
    • double—A double value
    • set—A comma-separated list of values

Looking more closely at the bundle capability (#1), you see it's a fairly straightforward mapping from the bundle identification metadata:

Bundle-ManifestVersion: 2
	Bundle-Name: Simple Paint API
	Bundle-SymbolicName: org.foo.shape
	Bundle-Version: 4.0

Likewise, the package capability (#2) is also a simple mapping from the bundle's Export-Package header:

Export-Package: org.foo.shape;version="4.0"

A requirement is an LDAP query over the properties of a capability. So, to match a requirement to a capability, first the namespace must match. If that matches, the requirements LDAP query must match the properties supplied by the capabilities. Even with the LDAP query, the package requirement (#3) is a fairly easy mapping from the Import-Package header:

Import-Package: org.foo.shape;version="[4.0,5.0)"

One reason the filter (#3) looks somewhat more complicated than necessary is that version ranges aren't directly supported by the filter syntax and must be expressed as the lower and upper bound.

If your bundle had a Require-Bundle, Fragment-Host, or Bundle-Execution-Environment header, it would be mapped to requirements. Even though the mappings are straightforward, it's still nice to have a tool like BIndex doing this for you.

You can even integrate BIndex into in your build cycle so your repository is updated whenever your bundles change.

The repository XML is all well and good, but you're probably wondering how you can use repositories in your management agent. You don't need to know anything about the XML format to use OBR. All you need to do is grab the service implemented by OBR and use it. Let's take a closer look at this.

Browsing OBR repositories

The best way to familiarize you with how to use repositories is to give an example and explain what it does along the way. Let's use the shell example again and extend it with a new command to add/remove/list repositories and browse the bundles inside them. The programmatic entry point to the OBR specification is the RepositoryAdmin service, which is represented by the following interface:

public interface RepositoryAdmin {
		Resource[] discoverResources(String filterExpr);
		Resolver resolver();
		Repository addRepository(URL repository) throws Exception;
		boolean removeRepository(URL repository);
		Repository[] listRepositories();
		Resource getResource(String respositoryId);
	}

This RepositoryAdmin service provides centralized access to the federated repository. An OBR implementation implements this interface as well as the other types referenced by it. Figure 3 shows the relationships among the involved entities.

The code in the following listing shows the code for the new obr-repo command. It uses RepositoryAdmin to add, remove, and list repositories as well as to discover resources.

Listing 2 OBR repository shell command example

public class RepositoryCommand extends BasicCommand {
		public void exec(String args, PrintStream out, PrintStream err)
		throws Exception {
			args = args.trim();
			RepositoryAdmin admin = getRepositoryAdmin();
			if (admin != null) { 									#1
				if ("list-urls".equalsIgnoreCase(args)) { 					#1
					for (Repository repo : admin.listRepositories()) { 			#1
						out.println(repo.getName() + " (" + repo.getURL() + ")"); 	#1
					} 									#1
				} else if (args != null) {
					if (args.startsWith("add-url")) { 					#2
						admin.addRepository( 						#2
						new URL(args.substring("add-url".length()))); 			#2
					} else if (args.startsWith("remove-url")) { 				#3
						admin.removeRepository( 					#3
						new URL(args.substring("remove-url".length()))); 		#3
					} else if (args.startsWith("list")) { 					#4
						String query = (args.equals("list")) 				#4
						? "(symbolicname=*)" 						#4
						: args.substring("list".length()).trim(); 			#4
						for (Resource res : admin.discoverResources(query)) { 		#4
							out.println(res.getPresentationName() + " (" 		#4
							+ res.getSymbolicName() + ") " + res.getVersion()); 	#4
						}	 							#4
					} 									#4
				} else {
					out.println(
					"Unknown command - use {list-urls|add-url|remove-url|list}");
				}
			} else {
				out.println("No RepositoryAdmin service found...");
			}
		}
		private RepositoryAdmin getRepositoryAdmin() {
			...
		}
	}
	#1 Lists repositories
	#2 Adds repository
	#3 Removes repository
	#4 Discovers resources

The obr-repo command has the following subcommands: list-url, add-url, remove-url, and list. A RepositoryAdmin provides access to a number of repositories referenced by URLs. You implement the list-url subcommand (#1) to list these repositories by retrieving the RepositoryAdmin service and calling its listRepositories() method, which gives you access to the associated Repository objects. In this case, you loop through the repositories and print their names and URLs.

You can add or remove repository URLs with the add-url and remove-url subcommands, respectively. As you can see at (#2) and (#3), there’s a one-to-one mapping to the addRepository() and removeRepository() methods of the RepositoryAdmin service.

Finally, the list subcommand expects an LDAP query which it passes to discover-Repositories() to discover resources (#4). If no query is specified, all resources are listed. You loop through the discovered resources and print their presentation name, symbolic name, and version.

You can now use this command to configure repositories and discover bundles.

After you’ve discovered a bundle you want to use, you need to deploy it. You’ll implement a separate command for that next.

Deploying bundles with OBR

Discovering bundles is one half of the OBR story; the other half is deploying them and their dependencies into the framework. The RepositoryAdmin.getResolver() method gives you access to a Resolver object to select, resolve, and deploy resources.

A Resolver has these methods:

public interface Resolver {
		void add(Resource resource);
		Requirement[] getUnsatisfiedRequirements();
		Resource[] getOptionalResources();
		Requirement[] getReason(Resource resource);
		Resource[] getResources(Requirement requirement);
		Resource[] getRequiredResources();
		Resource[] getAddedResources();
		boolean resolve();
		void deploy(boolean start);
	}

The process for deploying resources is fairly simple. Follow these steps:

  1. Add desired resources using Resolver.add().
  2. Resolve the desired resources’ dependencies with Resolver:resolve().
  3. If the desired resources resolve successfully, deploy them with Resolver.deploy().

The following listing implements an obr-resolver shell command to resolve and deploy resources.

Listing 3 OBR resolver shell command example

public class ResolverCommand extends BasicCommand {
		public void exec(String args, PrintStream out, PrintStream err)
		throws Exception {
			RepositoryAdmin admin = getRepositoryAdmin();
			Resolver resolver = admin.resolver();
			Resource[] resources = admin.discoverResources(args);
			if ((resources != null) && (resources.length > 0)) {
				resolver.add(resources[0]); 							#1
				if (resolver.resolve()) { 							#1
					for (Resource res : resolver.getRequiredResources()) {
						out.println("Deploying dependency: " +
						res.getPresentationName() +
						" (" + res.getSymbolicName() + ") " + res.getVersion());
					}
					resolver.deploy(true); 							#2
				} else { 									#2
					out.println("Can not resolve " + resources[0].getId() +
					" reason: ");
					for (Requirement req : resolver.getUnsatisfiedRequirements()) {
						out.println("missing " + req.getName()
						+ " " + req.getFilter());
					}
				}
			} else {
				out.println("No such resource");
			}
		}
		private RepositoryAdmin getRepositoryAdmin() {
			...
		}
	}
	#1 Resolves resource
	#2 Deploys bundle

You first get the Resolver from the RepositoryAdmin service. Then you use the RepositoryAdmin.discoverResources() method with a LDAP filter argument to discover a resource to deploy. If you find any resources, you add the first one to the Resolver and call resolve() to resolve its dependencies from the available repositories (#1). If the resource is successfully resolved, you print out all of the dependencies of the resource you’re deploying. Then you use Resolver.deploy() to install and start the discovered bundle and its dependencies (#2). If the resource couldn’t be resolved, you print out the missing requirements.

To run this example, go to the chapter10/combined-example/ directory of the companion code. Type ant to build the example and java -jar launcher.jar bundles to execute it. To interact with the shell, use telnet localhost 7070. This example uses the Apache Felix OBR implementation (http://felix.apache.org/site/apache-felix-osgi-bundle-repository.html). The following session uses the obr-repo and obr-resolver commands:

-> obr-repo add-url file:repository.xml
	-> obr-repo list-urls
	Paint (file:repository.xml)
	-> obr-repo list
	circle (org.foo.shape.circle) 4.0.0
	frame (org.foo.paint) 4.0.0
	shape (org.foo.shape) 4.0.0
	square (org.foo.shape.square) 4.0.0
	triangle (org.foo.shape.triangle) 4.0.0
	-> obr-resolver (symbolicname=org.foo.paint)
	Deploying dependency: shape (org.foo.shape) 4.0.0
	-> obr-resolver (symbolicname=org.foo.shape.circle)

In this session, you first use the add-url subcommand to add your repository containing the paint program bundles. You verify the configured repository using the listurl subcommand. Using the list subcommand, you browse the bundles contained in the repository. Then, you use the obr-resolver command with an LDAP filter to select and deploy the paint-frame bundle, which also installs its dependencies. Finally, you install the circle bundle.

Summary

One of the key management tasks is deploying bundles to the OSGi framework. You can use multiple techniques to do so, including rolling your own approach or using technologies like OBR and Deployment Admin. We showed you how to begin using OBR to discover and deploy your bundles.

Comments

comments

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Speak Your Mind

*