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.
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 | |
| Indicates manifest version. NOTE: This value is always set to |
| Indicates bundle-manifest version. Note that this value is always set to |
OSGi Bundle Headers | |
| Assigns a bundle's version. |
| Serves as an identifier along with |
| Imports the exported packages of a Java bundle into another bundle. |
OSGi Package Headers | |
| Exports a bundle's Java packages for the benefit of other bundles. |
| Imports Java packages from another bundle. |
| Dynamically imports Java packages from another bundle. |
OSGi Fragment Header | |
| Assigns a fragment to a host bundle. |
OSGi Spring-DM Header | |
| Assigns a target version of a Spring extender to a host bundle. |
OSGi SpringSource dm Server Header | |
| Assigns a library's version. |
| Serves as an identifier along with |
| Imports a bundle using SpringSource dm Server semantics. |
| Imports a library using SpringSource dm Server semantics. |
| Assigns an application's version. |
| Serves as an identifier along with |
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.
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.
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.
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,
no RDBMS back here");
}
public void delete(HelloWorld hw) {
throw new UnsupportedOperationException("Can't delete anything,
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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <!-- Create the helloWorldDAO bean --> <bean id="helloWorldDAO" class="com.apress.springosgi.ch3.servicedao.Hello WorldDAO"/> <!-- Export helloWorldDAO as a service to OSGi via its interface --> <osgi:service ref="helloWorldDAO" ranking="100" interface= "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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <!-- Create the helloWorldDAO bean --> <bean id="helloWorldDAO" class="com.apress.springosgi.ch3.servicedao.HelloWorldDAO"/> <!-- Export helloWorldDAO as a service to OSGi via its interface --> <osgi:service ref="helloWorldDAO" 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"
interface="com.apress.springosgi.ch3.service.HelloWorldService"
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.
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.
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.
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.
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.
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.
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.
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.
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
.
52.15.129.90