Chapter 6. Versioning with OSGi and Spring

Java compilers can forewarn you of many flaws present in an application, but when it comes to versioning, compilers know little except the underlying Java platform version being used. This can make class versioning conflicts one of the hardest problems to correct, since they can be discovered only when an application enters Java's runtime.

A compiler can ensure an application has access to Class A, but it is only Java's runtime that will detect that an application relies on two or more versions of Class A. Often version conflicts are so subtle only you can detect them, like minor business logic modifications between versions that are only discoverable after an application is put to use.

Similarly, Java applications using well-known libraries can be difficult to deploy given the number of versions (a.k.a. releases) these libraries tend to have. With the exception of the classical README file indicating "This application requires version 2.5 of the Y library," it is you who needs to ensure the appropriate library version is used at runtime.

OSGi handles a lot of these versioning conflicts for you, warning you about them right at deployment. Next, I will discuss the overall benefits and concepts of versioning and how these are applied to OSGi and Spring.

Benefits and Concepts

Versioning is a process ingrained in any software's life cycle. Product releases use versions to distinguish between milestones, source code repositories use versioning to keep track of advances in development, and the list goes on.

However, enforcing versioning when a Java application enters its runtime proves difficult. As outlined in the OSGi introduction back in Chapter 1, Java by itself possesses a very simple class-loading mechanism that does not allow it to support more robust features like versioning.

Still, you may question what you gain by using versioning at runtime in your applications. The following list details some of these benefits:

  • Ability to deploy packages/classes using the same namespace simultaneously on a JVM (e.g., Class A requires Class B, and a newer version of Class B is required by Class C in the same application).

  • No need to use Java class loaders designed through APIs to enforce package/class isolation. Package/class clashes can be avoided by using version numbers on multiple packages by the same name.

  • Ability to enforce minimum or range version requirements at runtime (e.g., Package A requires a minimum version 1.1 of Library X or Package B requires a version between 1.0 to 2.0 of Library Y).

Application designers often try to avoid being boxed into such versioning "corners" at all costs, putting upgrades or new application functionality on hold for fear of disturbing what already works.

For others, introducing a new class or library version into an application can cause angst, as it entails either the painstaking task of retesting an entire application to ensure everything works as expected or simply taking a leap of faith ("Let's hope it all works").

Introducing new class or library versions doesn't have to be such a pain. OSGi allows your applications to use multiple versions without many worries, not only allowing you to run different versions in the same JVM, but also advising you at deployment time whether an application is lacking certain dependencies right down to specific versions.

Like any other versioning strategy, OSGi has its own characteristics:

  • Versioning is based on a three-digit notation and optional alphanumeric string (e.g., 1.3.0, 2.0.0.RC1, 1.4.0.Beta).

  • Versioning can be range bound (e.g., from 1.1.0 to 2.5.0, from 1.0.0.RC to 3.0.0).

  • Versioning can be enforced at the Java package level.

  • Versioning can be enforced at the Java bundle (JAR) level.

One of the biggest benefits of OSGi's versioning strategy is that it's performed in the MANIFEST.MF file that accompanies bundles (JARs). This simplifies the versioning process since it's not tangled up with code, but rather left to the last step in the development process, which is grouping classes into deployable units or OSGi bundles.

A MANIFEST.MF file can contain various OSGi headers that use versions values. Table 6-1 illustrates the different OSGi headers that support versioning.

Table 6-1. OSGi Headers Supporting Versioning

Directive

Purpose

OSGi Manifest Headers

 

Manifest-Version

Indicates manifest version. NOTE: This value is always set to 1.0 for OSGi bundles.

Bundle-ManifestVersion

Indicates bundle-manifest version. Note that this value is always set to 2 for bundles targeting OSGi framework v.4.0 or higher. Future OSGi versions may use a different number.

OSGi Bundle Headers

 

Bundle-Version

Assigns a bundle's version.

Bundle-SymbolicName

Serves as an identifier along with Bundle-Version. (Bundle-Version provides the number against Bundle-SymbolicName values; they both work together.)

Require-Bundle

Imports the exported packages of a Java bundle into another bundle.

OSGi Package Headers

 

Export-Package

Exports a bundle's Java packages for the benefit of other bundles.

Import-Package

Imports Java packages from another bundle.

DynamicImport-Package

Dynamically imports Java packages from another bundle.

OSGi Fragment Header

 

Fragment-Host

Assigns a fragment to a host bundle.

OSGi Spring-DM Header

 

SpringExtender-Version

Assigns a target version of a Spring extender to a host bundle.

OSGi SpringSource dm Server Header

 

Library-Version

Assigns a library's version.

Library-SymbolicName

Serves as an identifier along with Library-Version.

Import-Bundle

Imports a bundle using SpringSource dm Server semantics.

Import-Library

Imports a library using SpringSource dm Server semantics.

Application-Version

Assigns an application's version.

Application-SymbolicName

Serves as an identifier along with Application-Version.

OSGi's manifest headers Manifest-Version and Bundle-ManifestVersion always use a single-digit version value, which is the same for all applications targeting OSGi framework v4.0 or higher. As an application developer, you usually don't have to deal with these two version values, but since I am on the topic of OSGi versioning, it's important you be aware of their existence. The remaining OSGi headers use a three-digit/two-dot notation version value in the form 0.0.0.

From this group of OSGi headers, only those appended with -Version require the explicit use of version values. Version assignments for the remaining headers are optional. OSGi headers requiring explicit version values use a syntax like <Header_Name>: 0.0.0, and those with optional version values use a syntax like <Header_Name>: <package/bundle/library>;version="0.0.0".

There is not much to say about headers requiring explicit version values, since a version value is the only piece of information assigned to this type of header. But the remaining headers rely on a special syntax to assign version values on a case-by-case basis.

In this last group of OSGi headers--those with optional version values--a comma (,) is used to delimit a list of values corresponding to packages, bundles, or libraries (e.g., Import-Package: com.apress.ch1,com.apress.ch2,com.apress.ch3). However, a semicolon (;) can be used to assign properties to each value in a comma-separated header list.

One of these properties is version, indicating the assignment of a version delimited by quotes (") (e.g., Import-Package: com.apress.ch1;version="1.0.0"). By using this version property, you are telling the OSGi environment at the moment a bundle is installed "Please wire the package com.apress.ch1 with version 1.0.0 to this bundle." If the package cannot be found, the OSGi environment will warn you in addition to leaving the bundle in an installed state instead of changing it to a resolved state. Chapter 1's Figure 1-2 illustrates a bundle's states and transitions.

Note

Wire is a widely used term in OSGi. It indicates a "link," or association, between installed packages and bundles in an OSGi environment. It's common to see phrasing like "Bundle A has a wire to package X" or "Bundle B has a wire to package Y." This generally means one bundle uses a package in another bundle, and there is said to be a wire between them.

The version property is optional. However, if it is omitted, OSGi always interprets the version property as version=[0.0.0,)". This last notation indicates a version range from 0 to infinity; in other words, it's interpreted by an OSGi environment as "Any version for the package/bundle/library declared prior to ; will do." Version ranges are not applicable for exports (you can't include them in the Export-Package header); you can only use version ranges to import packages or bundles.

This version range notation can also be used to indicate specific values, with the syntax following the mathematical conventions of brackets ([]) to indicate inclusive values or parentheses (()) for noninclusive values. For example, a range definition version="[1.0.0,2.0.0)" would indicate from version 1.0.0 inclusively (as indicated by the opening bracket, [) up to but not including (as indicated by the closing parenthesis, )) 2.0.0. A range version="(5.0.0,10.0.0)" would indicate a version higher than, but not including, version 5.0.0 (as indicated by the opening parenthesis, () up to, but not including, version 10.0.0.

Now that you are aware of OSGi's versioning syntax and the headers that support it, I will break down and illustrate the specific behaviors for each header based on its area of influence.

Each of the versioning behaviors presented next is taken from the context of the application presented in Chapter 3, unless otherwise specified. Therefore, this chapter will not require you to set up a new application, but rather only present a series of MANIFEST.MF and accompanying files that you can substitute from this earlier example.

OSGi Package Versioning Behaviors

Package headers are by far the most common type of header used in OSGi applications, comprising Export-Package, Import-Package, and DynamicImport-Package. These headers control what each bundle's class loader is able to access and expose to other bundles running in an OSGi environment.

Let's start things off by looking at the Export-Package package behavior. Let's assume the service layer for the application presented in Chapter 3 requires an upgrade to support new functionality. Listing 6-1 illustrates what the application's new service interface would look like.

Example 6-1. HelloWorldService Modified Service Interface

package com.apress.springosgi.ch3.service;

import com.apress.springosgi.ch3.model.HelloWorld;

public interface HelloWorldService {

    public HelloWorld find();


    public HelloWorld qualify(HelloWorld hw);

    public boolean validate(HelloWorld hw);

    public HelloWorld update(HelloWorld hw);

    public void save(HelloWorld hw);

    public void delete(HelloWorld hw);

}

This last interface adds two new service methods to the application's service layer. However, incorporating this new interface into the application does not mean you need to replace the earlier interface by the same name or even make modifications to other parts of the application that relied on the older version.

Two different interfaces—or classes for that matter--with the same name can perfectly co-inhabit the same JVM instance if an OSGi framework is used to manage them. In this case, the interface presented in Listing 6-1 would need to be packaged in its own bundle using a MANIFEST.MF file like the one illustrated Listing 6-2.

Example 6-2. MANIFEST.MF for the Modified Service Bundle

Bundle-Version: 2.0
Bundle-SymbolicName: com.apress.springosgi.ch3.service
Bundle-Name: HelloWorld Spring-OSGi Service
Bundle-Vendor: Pro Spring-OSGi
Import-Package: com.apress.springosgi.ch3.model;version="1.0.0"
Export-Package: com.apress.springosgi.ch3.service;version="2.0.0"
Bundle-ManifestVersion: 2

Note this last MANIFEST.MF file now uses version="2.0.0" to qualify the Export-Package header value, which corresponds to the service interface package. At this juncture, if you took these last two listings and packaged them as a bundle, the bundle could be deployed alongside the original bundle designed in Chapter 3 containing version 1.0.0 of the interface.

Let's assume you've now installed these two bundles with different versions of the HelloWorldService interface. How do you access them? The process consists of using either the Import-Package header or the DynamicImport-Package header in a consuming bundle. Listing 6-3 illustrates the header statement that you would need to add to the MANIFEST.MF file of a consuming bundle.

Example 6-3. Import-Package Statement for the Versioned Package

Import-Package: com.apress.springosgi.ch3.service;version="2.0.0"

Note this last statement qualifies the imported package into a bundle's class loader by appending the version="2.0.0" property. By doing so, resolution of the consuming bundle will only be successful if this version of the package is installed.

Still, it's important to mention what the behavior is for either Export-Package or Import-Package/DynamicImport-Package if one header uses the version property while the other does not.

If a package assigned to an Export-Package header does not have a version property, recall that OSGi automatically assigns the range version="0.0.0, which indicates "This is package version 0.0.0." In such cases, any Import-Package header—whether using the version property or not—would import such a package.

On the other hand, is an Import-Package header has no version property, and there are multiple exported package versions using the version property, the imported package will be the highest version available. (For example, if Listing 6-3 is reduced to a statement like Import-Package: com.apress.springosgi.ch3.service, OSGi would still import version 2.0.0 of the package, on account of its being the highest version exported by any installed bundle.) It's important to emphasize that even though one bundle can export version 1.0.0 of a package and another bundle export version 2.0.0 of the same package—thus having them coexist in the same OSGi environment—a bundle can never see different versions of the same package.

Therefore, if only certain classes of a bundle require a newer package version while others need to maintain backward compatibility, such classes would need to be partitioned into different bundles in order to use different package versions.

As you can see, using package versions is very straightforward in OSGi. In the next section you will learn about service versioning. See the sidebar "Package Versions or Service Versions?" for more background on why package versioning and service versioning are often closely related.

OSGi Service Versioning Behaviors

Service versioning is not based on the same semantics as OSGi's multiple headers that use the version property. Nevertheless, service versioning can be a very common practice once OSGi applications start to grow.

Take the case of Chapter 3's HelloWorld service, which is used to return a message to a web-based application. This last service's backing class— HelloWorldDAO—returns a message containing "Hello World" and "1.0." In order for the application to return a new message like "Braver New World" and "2.0" without losing the older version, it's not package versions that you need, but rather service versions.

The first step in creating a new service version is of course creating a modified version of the backing class. Listing 6-4 illustrates what this new service implementation class would look like.

Example 6-4. Modified HelloWorldDAO Service Implementation Class

package com.apress.springosgi.ch3.servicedao;

import com.apress.springosgi.ch3.service.HelloWorldService;

import java.util.Date;

import com.apress.springosgi.ch3.model.HelloWorld;

public class HelloWorldDAO implements HelloWorldService {

    public HelloWorld find() {
        // Model object will be create here
        HelloWorld hw = new HelloWorld("Braver New World",new Date(),2.0);
        return hw;
    }

    public HelloWorld update(HelloWorld hw) {
        hw.setCurrentTime(new Date());
        return hw;
    }

    public void save(HelloWorld hw) {
        throw new UnsupportedOperationException("Can't save anything, 
Modified HelloWorldDAO Service Implementation Class
no RDBMS back here"); } public void delete(HelloWorld hw) { throw new UnsupportedOperationException("Can't delete anything,
Modified HelloWorldDAO Service Implementation Class
no RDBMS back here"); } }

This last listing isn't very different from the one in Chapter 3, except for the hard-coded values, which now reflect a newer version. If you rebuild the service implementation bundle using this new service class and the same MANIFEST.MF file used in Chapter 3, and then attempt to install it alongside Chapter 3's service implementation bundle, the OSGi framework will generate an error.

The error message will indicate a bundle by the same name is already installed. In this case, OSGi is protecting your application from an inadvertent update. In order to bypass this error, you will need to either rename the bundle or assign the bundle a new version, steps that are done using OSGi manifest headers. Listing 6-5 illustrates the MANIFEST.MF file needed by the new service implementation bundle.

Example 6-5. Modified MANIFEST.MF for the Service Implementation Bundle

Bundle-Version: 1.5
Bundle-SymbolicName: com.apress.springosgi.ch3.servicedao
Bundle-Name: HelloWorld Spring-OSGi Service DAO
Bundle-Vendor: Pro Spring-OSGi
Import-Package: com.apress.springosgi.ch3.model;version="1.0.0",
 com.apress.springosgi.ch3.service;version="1.0.0",
 org.springframework.util;version="2.5.4",
 org.springframework.beans.factory.xml;version="2.5.4",
 org.springframework.aop;version="2.5.4",
 org.springframework.aop.framework;version="2.5.4",
 org.springframework.osgi.service.exporter.support;version="1.2.0"
Bundle-ManifestVersion: 2

This last MANIFEST.MF file modifies the Bundle-Version header to a value of 1.5 and leaves the bundle's name— Bundle-SymbolicName header—the same. If you repackage the service implementation bundle using this last MANIFEST.MF file, the installation of the bundle will now be successful.

Still, you may be left wondering why OSGi reported success. Two bundles are now publishing the same service name—which you haven't modified—yet both have different backing implementation classes. What is happening?

The initial errors for the new service implementation bundle are type-dependency checks, specifically on the bundle name and version, which are enforced through OSGi manifest headers. In this case, the Bundle-Version and Bundle-SymbolicName headers are inspected at installation, and OSGi notifies you if a bundle using the same Bundle-Version and Bundl e-SymbolicName values is already installed.

By changing the Bundle-Version header value, OSGi is satisfied with the type-dependency check. Equally, you could have used another Bundle-SymbolicName header value to successfully install this new bundle. In the next section, I will describe these last OSGi headers in more detail, but for now one important question remains: how will OSGi behave with two services published under the same name?

By activating both bundles, via each bundle's Spring-DM configuration file, a service using the HelloWorldService interface will be registered and made available to consuming bundles. So what service will consuming bundles have access to—the one returning "HelloWorld" and "1.0" or the one returning "Braver New World" and "2.0"? It will be the one with the lowest service ID, which generally belongs to the first installed service implementation bundle.

As you can probably imagine, having access to the first installed service implementation is not a good way to support different versions of an OSGi service. In order to support different service versions in a more robust fashion, there are two approaches that can be incorporated when a service is registered.

One is to use the ranking attribute and the other to use service properties. Both approaches were briefly described in Chapter 4 along with Spring-DM's <service> element, which registers OSGi services using Spring type configuration files.

I'll start by describing the use of the ranking attribute. This is an integer assigned to an OSGi service at the time it is registered, which serves as a precedence value for services using the same interface. This mechanism fits nicely with the two service implementations presented in this chapter, since both use the HelloWorldService interface.

Listing 6-6 illustrates the osgi-context.xml file needed by the new service implementation bundle to register a service via Spring-DM using a ranking attribute.

Example 6-6. Modified osgi-context.xml File Service Implementation Bundle Using the ranking Attribute

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:osgi="http://www.springframework.org/schema/osgi"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
Modified osgi-context.xml File Service Implementation Bundle Using the ranking Attribute
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi
Modified osgi-context.xml File Service Implementation Bundle Using the ranking Attribute
http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <!-- Create the helloWorldDAO bean --> <bean id="helloWorldDAO" class="com.apress.springosgi.ch3.servicedao.Hello
Modified osgi-context.xml File Service Implementation Bundle Using the ranking Attribute
WorldDAO"/> <!-- Export helloWorldDAO as a service to OSGi via its interface --> <osgi:service ref="helloWorldDAO" ranking="100" interface=
Modified osgi-context.xml File Service Implementation Bundle Using the ranking Attribute
"com.apress.springosgi.ch3.service.HelloWorldService"/> </beans>

Note the ranking="100" value accompanying the <service> element. Using this last file in the new service implementation bundle will guarantee that every lookup for a service with the HelloWorldService interface will always return this particular version, irrespective of the service ID, which often correlates with the installation order.

The ranking attribute has two advantages. It takes the guesswork out of what service will be returned to bundles looking up a service; if more than one service using the same interface is registered, OSGi will return whichever one was registered with the highest ranking value. And the second advantage is the ranking attribute does not require any changes to the lookup sequence of a service used in consuming bundles.

From an OSGi point of view, ranking is just a predefined and standard service property. This means service properties are the more general-purpose and robust approach to using multiple versions of the same service. However, unlike ranking, using service properties require incorporating not only a new approach to registering services, but also one for looking them up.

Listing 6-7 illustrates the osgi-context.xml file needed by the new service implementation bundle to register a service via Spring-DM using service properties.

Example 6-7. Modified osgi-context.xml File Using Service Properties for the Service Implementation Bundle

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:osgi="http://www.springframework.org/schema/osgi"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
Modified osgi-context.xml File Using Service Properties for the Service Implementation Bundle
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi
Modified osgi-context.xml File Using Service Properties for the Service Implementation Bundle
http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <!-- Create the helloWorldDAO bean --> <bean id="helloWorldDAO"
Modified osgi-context.xml File Using Service Properties for the Service Implementation Bundle
class="com.apress.springosgi.ch3.servicedao.HelloWorldDAO"/> <!-- Export helloWorldDAO as a service to OSGi via its interface --> <osgi:service ref="helloWorldDAO"
Modified osgi-context.xml File Using Service Properties for the Service Implementation Bundle
interface="com.apress.springosgi.ch3.service.HelloWorldService"> <osgi:service-properties> <entry key="version" value="2.0"/> <entry key="greeting" value="Braver World"/> </osgi:service-properties> <osgi:service> </beans>

Note this last listing uses Spring-DM's <service-properties> element nested inside the <service> element. By doing so, the OSGi service will be registered using key/value pairs that will serve as identifiers for bundles looking up the service. The values assigned through Spring-DM's <service-properties> element, which in themselves are contained in Spring <entry> elements, are pretty much open ended.

In this case, I opted to use a key named version with a string value of 2.0, as well as a key named greeting with a string value of Braver World. But any key/value combination is possible. It is also valid to use the value-ref attribute—instead of ref—to point to a Spring bean value. See Chapter 4 and its Listing 4-4 for more details on registering services using properties.

Registering a service using properties is only one part of the process; the other part consists of looking up a service with these properties. In order to look up a service using properties, you need to use the filter attribute in conjunction with Spring-DM's <reference> element, both of which were also discussed in Chapter 4.

Listing 6-8 illustrates how a Spring-DM <reference> statement using the filter attribute would resemble for looking up the previously registered service.

Example 6-8. Spring-DM < reference> Element Using the filter Attribute

<osgi:reference id="helloWorldService"
Spring-DM < reference> Element Using the filter Attribute
interface="com.apress.springosgi.ch3.service.HelloWorldService"
Spring-DM < reference> Element Using the filter Attribute
filter="(version=2.0)"/>

Note this last listing's filter attribute has a value of (version=2.0), which is exactly the key/value pair assigned when the service was registered with the interface HelloWorldService using Spring-DM's <service> and <service-properties> elements.

A statement like the one in Listing 6-8 would need to be placed in a service-consuming bundle's Spring-DM configuration file, such as Chapter 3's shared service WAR bundle, which makes use of this service.

When using the filter attribute to look up service versions using the same interface, keep in mind two things:

  • The filter matching pattern is based on string values. In this example, I opted to use the key/value property (version=2.0) as a convention on the topic of versions. However, using a filter value of (version=2.0) is not equal to one declared as (version=2) or (version=2.0.0).

  • There is no default service; either the filter is matched or no service is returned. If a filter value does not match the exact pattern for a service property, it doesn't matter whether there are other registered services using the same interface—no service will be returned.

This concludes our review of using multiple OSGi service versions. Next, I will return to discussing OSGi headers that make use of the version property. This time though, the focus will not be on individual package versioning but rather bundle versioning.

OSGi Bundle Versioning Behaviors

Bundle versioning offers a coarser-grained alternative to the package-based approach presented earlier. This does not mean, though, that package versioning can be totally supplanted by bundle versioning. In fact, most well-thought-out OSGi versioning approaches use a combination of the two.

Bundle versioning is supported through the headers Bundle-Version, Bundle-SymbolicName, and Require-Bundle, with the first two headers— Bundle-Version and Bundle-SymbolicName—always working in tandem.

By "tandem," I mean that Bundle-Version values are based on the value assigned to the Bundle- SymbolicName header. In the previous section, "OSGi Service Versioning Behaviors," you learned OSGi protects against installing bundles using the same Bundle-Version and Bundle-SymbolicName values.

But what happens when multiple versions of a bundle using the same Bundle-SymbolicName value are installed? Nothing much really; both bundles continue to operate with the same package and service semantics. Whatever potential clashes might exist between two different bundle versions are only of the package or service kind and are thus resolved according to each of these cases.

In essence, different versions of a bundle using the same Bundle-SymbolicName name are treated as if they were different bundle names altogether. So is the point of versioning bundles that simple? Not exactly—there is a more important reason for versioning bundles that is related to the Require-Bundle header and fragments, which are addressed later. So I will first discuss the purpose of the Require-Bundle header.

The Require-Bundle header is used as a shortcut for importing packages exported by other bundles. Let's first take a look at how the Require-Bundle header would look like in a bundle. Listing 6-9 shows a modified MANIFEST.MF file for the application web bundle (shared services WAR) used in Chapter 3 that leverages the Require-Bundle header.

Example 6-9. MANIFEST.MF Using Require-Bundle for a Shared WAR Bundle

Bundle-Version: 1.0
Bundle-SymbolicName: com.apress.springosgi.ch3.web
Bundle-Name: HelloWorld Spring-OSGi Web
Bundle-Vendor: Pro Spring-OSGi
Bundle-Classpath: WEB-INF/classes
Import-Package: com.apress.springosgi.ch3.model;version="1.0.0",
 com.apress.springosgi.ch3.service;version="1.0.0"
Require-Bundle: com.springsource.javax.servlet;version="2.5.0",
 com.springsource.javax.servlet.jsp;version="2.1.0",
 com.springsource.org.apache.taglibs.standard;version="1.1.2",
 org.springframework.aop;version="2.5.4",
 org.springframework.core;version="2.5.4",
 org.springframework.context;version="2.5.4",
 org.springframework.web;version="2.5.4",
 org.springframework.web.servlet;version="2.5.4",
 org.springframework.bundle.osgi.core;version="1.2.0",
 org.springframework.bundle.osgi.web;version="1.2.0"
Web-ContextPath: /
Bundle-ManifestVersion: 2

Though this last MANIFEST.MF file still makes use of the Import-Package header, notice how the listing is shorter than the one used in Chapter 3 (Listing 3-17) on account of the Require-Bundle header.

The values assigned to a Require-Bundle header are equivalent to Bundle-SymbolicName and Bundle-Version values. For example, the value com.springsource.javax.servlet;version="2.5.0" indicates a version range [2.5.0,), which specifies importing the packages exported by the bundle with a Bundle-SymbolicName value of com.springsource.javax.servlet and a Bundle-Version value of 2.5.0. or greater.

In the same manner as unsatisfied OSGi package header values, if OSGi cannot find a bundle by the name and version declared in the Require-Bundle header, it will warn you of a type-check error and leave the bundle in an installed state instead of changing the state to resolved.

To illustrate exactly what packages are imported using the Require-Bundle header, Listing 6-10 shows the MANIFEST.MF file used by the com.springsource.javax.servlet bundle, which contains packages related to Java servlets.

Example 6-10. MANIFEST.MF Using the com.springsource.javax.servlet Bundle

Manifest-Version: 1.0
Bundle-Name: Java Servlet API
Created-By: 1.5.0_06-b05 (Sun Microsystems Inc.)
Ant-Version: Apache Ant 1.6.5
Bundle-ManifestVersion: 2
Bundle-Vendor: SpringSource
Bundle-SymbolicName: com.springsource.javax.servlet
Export-Package: javax.servlet;version="2.5.0",
 javax.servlet.http;version="2.5.0";uses:="javax.servlet",
 javax.servlet.resources;version="2.5.0"
Bundle-Version: 2.5.0

All packages declared in this last listing's Export-Package header are made available to any bundle using a Require-Bundle header that matches the values for Bundle-SymbolicName and Bundle-Version. As you can see, the Require-Bundle header is an effective shortcut to using multiple Import-Package statements, but you need to be aware of some other issues with it.

One of the more obvious drawbacks to using the Require-Bundle header is that the consuming and providing bundles become dependent on one another—being coupled by a bundle's name. This is unlike the more granular Import-Package header, where you can pick and choose individual packages irrespective of a bundle.

The effects of this become more obvious once you start versioning. If a providing bundle contains numerous packages, and many consuming bundles rely on the Require-Bundle header to use it, each new package version will also require you to create an entirely new bundle version to guarantee backward compatibility, since the Require-Bundle header is an "all or nothing" affair.

This last process can quickly become unmanageable if enough packages in a providing bundle require versioning. Using the Import-Package header in consuming bundles won't require you to fork new bundle versions each time a package is changed—given this last header's granularity.

Still, if you don't see this previous Require-Bundle header behavior as a drawback, you will likely even see a greater benefit to using this header's optional visibility directive. By default, if a consuming bundle uses the Require-Bundle header, the imported packages will be made available to that bundle alone, but it's possible to extend this availability.

For example, if Bundle B uses a statement like Require-Bundle:A, Bundle A's exported packages will only be available to Bundle B. But if Bundle B uses a statement like Require-Bundle:A;visibility:=reexport, this allows any consuming bundle requiring Bundle B— Require-Bundle:B—to automatically see the same packages exported by Bundle A. By default, this behavior is not active since the default value for this directive is visibility:=private.

The topic of using Require-Bundle headers also leads inevitably to the subject of split packages, which can only occur if a bundle uses this particular header. Though not related to versioning, split packages are an interesting topic, so the next section will describe them in more detail. If you wish to stick to versioning issues exclusively, you can skip ahead to the section "OSGi Fragment Versioning Behaviors."

Split packages in OSGi occur when a Java package is contained in more than one bundle. For example, Bundle A contains part of Package P, and Bundle B another part of Package P. Even though OSGi's specification dictates that a package can come only from a single bundle, on certain occasions split packages are inevitable.

So what would be a real-life scenario for using split packages? One case could be related to internationalization (i18n), where the same package name (e.g., com.apress.springosgi.lang) is split out into various bundles, with each one supporting a different language (e.g., English, Spanish, French, etc.). Another case could be related to refactoring—the process of enhancing a code base—with one bundle providing the original package (e.g., com.apress.springosgi.util) while another bundle provides a series of new classes for the same package.

At first glance, you might think there is no problem installing two bundles using a header like Export-Package:com.apress.springosgi.lang, and you would actually be partially correct. An OSGi environment won't balk at installing two bundles exporting the same package; what it will have trouble with is importing the split packages into one bundle.

If a consuming bundle uses Import-Package:com.apress.springosgi.lang, and there are two bundles exporting this package, only one will get wired. So how do you support split packages? Easy—split packages can only work with the use of the Require-Bundle header.

Since the Require-Bundle header accesses all Export-Package values of a declared bundle, its semantics allows it to join split packages. This behavior by the Require-Bundle header accompanied by its visibility:=reexport directive can serve as facade for split packages.

Figure 6-1 illustrates this scenario of split packages and a facade bundle using the Require-Bundle header.

Split packages using a facade with Require-Bundle

Figure 6-1. Split packages using a facade with Require-Bundle

Figure 6-1 shows various bundles using the package com.apress.springosgi.lang, with each of these bundles having a Bundle-SymbolicName in the form ch6.english, ch6.spanish, and ch6.french. In order to bring these split packages together, a new facade bundle is created that leverages the Require-Bundle header. Listing 6-11 illustrates the MANIFEST.MF file for the facade bundle.

Example 6-11. MANIFEST.MF Headers for the Facade Bundle langfacade

Bundle-SymbolicName: ch6.langfaced;
Require-Bundle: ch6.english;visibility:=reexport,
 ch6.spanish;visibility:=reexport,
 ch6.french;visibility:=reexport

Note the values in this listing's Require-Bundle header point to each of the bundles containing a part of the com.apress.springosgi.lang package, while also using the visibility:=reexport directive. This means that any other bundle using this facade bundle will automatically have access to all the packages contained in bundles using visibility:=reexport.

Therefore, if an application bundle used a MANIFEST.MF file requiring this facade bundle, ch6.langfacade, it would automatically have access to all packages in bundles using the visibility:=reexport header, which in this case corresponds to the multiple split package com.apress.springosgi.lang. Listing 6-12 illustrates a MANIFEST.MF file using the langfacade bundle.

Example 6-12. MANIFEST.MF Header for a Bundle Using the Facade Bundle

Require-Bundle: ch6.langfacade

As you can observe, it's only due to the very specific semantics of the Require-Bundle header that a split package can be brought together. As outlined earlier, this is one of the other major differences between using Require-Bundle and Import-Package: the former supports split packages, while the latter does not.

But even though split packages can be used to support the scenarios outlined earlier, they are often avoided due to the following issues:[17]

  • Completeness: Split packages are open ended; there is no way to guarantee that all the intended pieces of a split packages have actually been included.

  • Ordering: If the same classes are present in more than one required bundle, the ordering of Require-Bundle is significant. A wrong ordering can cause hard-to-trace errors, as with the Java classpath model.

  • Performance: A class must be searched in all providers when packages are split. This increases the number of times that a ClassNotFoundException must be thrown, which can introduce significant overhead.

  • Confusing: It is easy to create a very contrived structure of dependencies if a package is spread out in multiple bundles.

  • Mutable exports: The feature of visibility:=reexport can unexpectedly change depending on the export signature of the required bundle.

  • Shadowing: The classes in the requiring bundle that are shadowed by those in a required bundle depend on the export signature of the required bundle and the classes the required bundle contains. (In contrast, Import-Package, except with resolution:=optional, shadows whole packages regardless of the exporter.)

  • Unexpected signature changes: The Require-Bundle directive visibility:=private (the default) may be unexpectedly overridden in some circumstances.

Now that you know the pros and cons of split packages and how they relate to the Require-Bundle and Import-Package headers, I will switch over to discussing versioning in relation to fragments.

OSGi Fragment Versioning Behaviors

As mentioned in Chapter 4, fragments are bundles that are attached to a host bundle and are treated as part of the host. Similar to the package and bundle versioning approaches explored earlier, fragment versioning is supported through the OSGi header Fragment-Host.

If you recall the discussion on fragments, a fragment bundle always possesses a Fragment-Host header value, which is compared to installed bundles' Bundle-SymbolicName header values. If a match occurs, the fragment is attached to the host with the matching header value; if not, the fragment fails to be installed.

Since a fragment itself is a bundle, it can have its own Bundle-Version header; however, this continues to be a bundle versioning header that has little to do with fragment dynamics. The versioning dynamics for fragments are assigned through the version property of the Fragment-Host header.

For example, if a fragment used a header in the form Fragment-Host:com.apress.spinrgosgi.ch6;version="2.0.0", this would indicate a version range [2.0.0,), which attempts to attach the fragment to a host bundle with a Bundle-SymbolicName value of com.apress.spinrgosgi.ch6 and a Bundle-Version of 2.0.0. or greater.

Similar to other OSGi version header behaviors, the lack of a version property on a Fragment-Host statement is automatically translated into version=[0.0.0,)"—in plain English this means "From 0 to infinity"—indicating this fragment should be attached to any version of a bundle with a matching Bundle-SymbolicName value.

So what happens to a fragment if there is more than one bundle using the same Bundle-SymbolicName value? The host for the fragment will become the bundle with the highest Bundle-Version value. And recall, since there can't be two bundles using the same Bundle-SymbolicName value (unless they have a different Bundle-Version value), there will always be a bundle that has a highest version value.

As far as fragments and their versioning behavior are concerned, there is nothing more to elaborate on. Next, I will finish off the subject and this chapter on versioning by discussing the dynamics behind Spring-DM's and SpringSource dm Server's proprietary OSGi headers.

OSGi Spring-DM and SpringSource dm Server Versioning Behaviors

As you've learned in previous chapters, not all the necessary functionality to support Spring and OSGi applications is provided by the OSGi standard. To fill this gap, both Spring-DM (discussed in Chapter 4) and the SpringSource dm Server (discussed in Chapter 5) make use of their own proprietary headers.

But proprietary as these headers are, when it comes to versioning and when it's applicable to any of these headers, they use the same syntax and semantics as the standard OSGi headers that support versioning.

The first proprietary header supporting versioning corresponds to Spring-DM's SpringExtender-Version header used in application bundles. Revisiting the topic of Spring-DM extenders presented in Chapter 4, recall that once Spring-DM extenders are installed in an OSGi environment, they will always inspect every subsequently installed bundle for the presence of trigger points to execute Spring-DM instructions.

By default, once Spring-DM extenders are installed, they will inspect each and every bundle. The SpringExtender-Version header allows an application bundle to override this behavior and indicate that it should only be inspected and processed if a certain version of Spring-DM extender is present.

For example, suppose you want an application to be processed only by Spring-DM's extender version 1.1.0. Listing 6-13 illustrates the header you would need to add to an application bundle.

Example 6-13. MANIFEST.MF Header for SpringExtender-Version

SpringExtender-Version: 1.1.0

Note that like every header appended with Version, this header requires an explicit three-digit/two-dot value. By using this last header in an application bundle, at the time any Spring-DM extender initiates its scanning process, this header will indicate not to continue or attempt to process any trigger points, unless the Spring-DM extender bundle version is 1.1.0.

The only reason you would use the SpringExtender-Version header is for cases when an application bundle requires functionality present only in certain Spring-DM extenders. Given the short lifespan of Spring-DM and its extender versions, for practical purposes this header can be considered esoteric. Nevertheless, once future versions of Spring-DM extenders become available, it can serve to enforce that only certain versions of Spring-DM extenders process application bundles.

Moving through the set of proprietary headers supporting versioning, we come to SpringSource dm Server's set, which includes Application-Version, Library-SymbolicName, Library-Version, Import-Bundle, and Import-Library.

The Application-Version header is only used in MANIFEST.MF files that accompany Platform Archives (PARs), which are the preferred format for deploying applications in SpringSource dm Server. Similar to the Bundle-Version header, the Application-Version header is always used in tandem with the Application-SymbolicName header, thus guaranteeing that no two PARs using the same symbolic name and version are deployed simultaneously.

Following the same conventions as other headers appended with Version, the Application-Version header requires an explicit three-digit/two-dot value. Listing 6-14 illustrates a sample MANIFEST.MF file added to a PAR.

Example 6-14. MANIFEST.MF Headers for Application-Version and Application-SymbolicName

Application-SymbolicName: com.apress.springosgi.ch6


Application-Version: 1.0.0

As you will note, the sole purpose of the Application-Version header is to avoid two PARs with the same Application-SymbolicName header being deployed simultaneously.

Currently, there are no further implications to using the Application-Version header. Which is to say it's not like the Bundle-Version header, with its influence on fragments and headers like Require-Bundle, though this could change with future versions of the SpringSource dm Server.

Next, we come to the Library-SymbolicName and Library-Version headers, which serve to declare libraries according to the SpringSource dm Server—see the "Libraries" section in Chapter 5 for more background on the use of libraries.

Applying versions to SpringSource dm Server libraries works in the same way as bundles and applications (PARs). It's necessary to use both a Library-SymbolicName and Library-Version to avoid two libraries using the same symbolic name and version being deployed inadvertently. Listing 6-15 illustrates a SpringSource dm Server library definition using these two headers.

Example 6-15. SpringSource dm Server Library Definition Using Import-Bundle

Library-SymbolicName: org.aspectj

Library-Version: 1.6.1

Library-Name: AspectJ

Import-Bundle:
com.springsource.org.aspectj.runtime;version="[1.6.1,1.6.1]",


 com.springsource.org.aspectj.weaver;version="[1.6.1, 1.6.1]"

Note the Library-Version header follows the same conventions as others appended with Version, requiring an explicit three-digit/two-dot value, whereas the Library-SymbolicName header is a simple text field just like other SymbolicName-appended headers.

But before I get to importing such a library, notice the Import-Bundle header in the library definition. The Import-Bundle header allows a library to import the whole set of packages exported by a particular bundle, in a very similar fashion to Require-Bundle, though they do have different semantics. (See Chapter 5's sidebar "SpringSource dm Server Import-Bundle vs. OSGi's Require-Bundle" for the differences.) For example, the statement com.springsource.org.aspectj.runtime;version="[1.6.1,1.6.1]" in Listing 6-15 indicates to wire the exported packages from the bundle with a Bundle-SymbolicName value of com.springsource.org.aspectj.runtime and an exact Bundle-Value of 1.6.1 into bundles that import this library.

The same versioning syntax and behaviors as Require-Bundle and Import-Package apply to the Import-Bundle header. If no version attribute is specified, it will automatically be interpreted as version=[0.0.0,)", or from 0 to infinity, meaning any version of a bundle matching Bundle-SymbolicName. Equally, noninclusive ranges or exact values can be used.

Next, we come to the Import-Library header used directly inside the MANIFEST.MF file of application bundles, which is used to grant a bundle access to the entire contents of a SpringSource dm library. Listing 6-16 illustrates a sample Import-Library header.

Example 6-16. SpringSource dm Server Import-Bundle

Import-Library: org.aspectj;version="1.6.1"

By using this last statement in an application bundle, you are indicating to the underlying SpringSource dm Server environment that it should load the library with a Library-SymbolicName value of org.aspectj and a Library-Version value of 1.6.1 or greater (e.g., [1.6.1,)) into this bundle's class loader.

Similarly, if the version property is omitted in this header, it's automatically interpreted as Import-Library;org.aspectj;version=[0.0.0,), meaning any version of such library should be imported. And following the same versioning semantics as other OSGi headers, if there are two libraries with the same Library-SymbolicName value, the one with the highest version will take precedence.

This concludes our review of the different proprietary headers used by Spring-DM and SpringSource dm Server in which versioning can be applied.

Summary

In this chapter you started by learning how OSGi compensates for Java's lack of runtime versioning, allowing you to easily deploy multiple versions of the same class in the same JVM instance, as well as enforce that only certain versions of JAR files (bundles) are used to run an application.

You then read about OSGi's various MANIFEST.MF file headers that support versioning, including the notation and conventions used to version packages, bundles, fragments, and more specific Spring-DM and SpringSource dm Server headers.

Next, you explored the dynamics of package versioning using the OSGi headers ExportPackage and Import-Package/DynamicImport-Package to rework the same application created in Chapter 3. Immediately after, you learned about OSGi service versioning, and how to apply service properties and ranking values to allow the deployment of multiple services using the same interface in the same OSGi environment.

Next, you found out about bundle versioning and how the Bundle-SymbolicName, Bundle-Version, and Require-Bundle headers are used to provide a coarser-grained approach to versioning than packages. You also learned about the Require-Bundle header and the split packages that are supported thanks to this header.

Finally, you discovered something about the versioning dynamics applied to OSGi fragments, as well as the proprietary headers used by both Spring-DM and SpringSource dm Server to support special behaviors outside of OSGi's scope.



[17] Note that the following list is taken from the OSGi Service Platform Release 4 Specification, Section 3.13.3, http://www.osgi.org/Specifications/HomePage.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.44.143