Manage your software with the Java Product Versioning Specification

An introduction to component versioning with Java

Many of us are familiar with how to find version information in Windows-like applications. At startup, we're often greeted with a splash screen that identifies the vendor, the product name, and usually its version. Once an application has launched, similar information can generally be found under the About dialog in the Help menu.

Although not quite as ubiquitous, server-side applications tend to employ similar approaches. When server-side applications launch, product and version information usually appear on the system console or are written into a log file. Once these applications are up and running, the same kind of information can usually be found in the assortment of README, license, and contact information files that accompany server-side products.

Over time, most users seem to have become familiar with and accepted these approaches. We could define them as defacto standards.

But what about component systems, such as those falling under the widening umbrella of specifications in the Java 2 Platform, Enterprise Edition (J2EE)? In these systems, applications are often made up of components supplied by several vendors. The components themselves may be distributed across a network or a cluster of servers. How do we find the product and vendor information of such distributed components? It is not always immediately obvious where to find such information just by looking at the installation of a running, distributed component system.

Even for simple tasks, such as submitting a bug report or contacting the support group, the ability to identify the vendor of a product or component, its name, and its version is absolutely mandatory. When upgrading a component, the user needs to feel reasonably sure that introducing a new version will not adversely affect other parts of the system, or more formally, that the contract a component exposes to other parts of the system remains the same.

To address these kinds of problems, Sun Microsystems released the Java Product Versioning Specification. Using the mechanisms of packaging, combined with a little support from a selection of core classes within the Java platform, Sun has achieved a versioning model that turns out to be quite well suited to the needs of developers and users of component systems.

Note: You can download this article's source code from Resources.

The Java Product Versioning Specification

Published in 1998, the Java Product Versioning Specification describes the problems of evolution in distributed systems. It outlines various distinguishing factors of such systems, including component distribution, multivendor sourcing, and intracomponent dependencies. It also identifies the needs of various individuals involved with the production, support, administration, and use of these kinds of systems.

One of the key definitions of the Java Product Versioning Specification is the replaceable unit. To paraphrase the specification, the replaceable unit is the smallest unit in a product or system that can be identified with a specification and a supplier, be distributed and redistributed, and replaced if necessary. In Java, the specification defines the replaceable unit as the Java package.

While it is possible to perform these procedures on a class level, over time the process can become a bit of an administrative nightmare. In a large system, the problems presented in the introduction are often particularly evident.

In Java, the next higher level of granularity from the class is the package. Following a general definition of a component as a service abstraction provided by a collection of cooperating classes, the Java package fits quite nicely. Packages tend to be made up of logically related classes and interfaces, such as the core APIs java.io, java.math, java.net, and so on. And from an administrative point, a system assembled from packages is significantly more manageable than one made up of independent classes.

Contracts and identification

Going back to the problems identified in the introduction, Sun's specification points out that packages also conform to the notion of a contract. They expose a public part, which forms the contract, and they have a private part, the implementation. An implementation can consist of private, protected, and "default access" classes, methods, and attributes that are hidden from the client. Other than behavioral variations, changes made to an implementation won't affect the contract exposed by a package.

So, while the Java package can be used to specify the contract, how do we associate it with the component information, such as the vendor's name, the component name, and its version?

One commonly employed approach is Javadoc. There is a standard template for documenting Java packages, including sections on what specifications the package implements and so on. Unfortunately, Javadoc isn't always installed. If you've deployed a production J2EE system, did you install Javadoc on all of the hosts in the cluster? Or if you added a new host to an existing cluster, did you install the Javadoc on that one as well? While useful during development, Javadoc depends on a user's ability to make the association between a physical package and its documentation. Is this something we should rely on in a system with many components distributed across a network of hosts?

Another approach is to hardcode this type of information into each class, maybe even providing public methods to retrieve it. Most of us realize the drawbacks of duplicating information and logic and the problems that result from doing so. Alternatively, we can code the information into a class implementing the Singleton pattern. This works fine, but not all packages have a singleton object, or are suited to have one.

One could argue for and against each of these approaches in different scenarios. A situation we should ideally avoid is having different vendors document their component information in different ways. This is bound to confuse and irritate users.

The jar file

The Java Product Versioning Specification approach for documenting this type of information is based on the jar file. Stored within every jar file is a plain text file called Manifest.mf. By default, the jar tool automatically generates this file and places it in a directory called meta-inf. In its default form, the manifest file created by the jar tool contains two fields, a mandatory Manifest-version attribute and another called Created-by that the jar tool automatically generates. It usually looks something like:

Manifest-version: 1.0
Created-by: 1.3.1 (Sun Microsystems Inc.)

Governed by a separate specification (see Resources), the manifest file can be used to store all kinds of information. This includes a set of version-specific attributes defined by the versioning specification. Rather conveniently, these attributes can be accessed in a running system using the java.lang.Package class, as shown in a later example.

Specification and implementation

The six fields defined by the Java Product Versioning Specification allow the developer to define the title, version, and vendor of a component's specification and implementation. Separating specification and implementation allows the two to evolve independently. Many specifications are implemented by competing developers and vendors. Equally, many of these vendors make multiple releases implementing the same specification, such as bug fixes and performance enhancements in minor releases and upgrades.

The java.lang.Package class provides a method called isCompatibleWith(String version), allowing the specification version of a component to be checked programmatically. Why is that useful? It allows a system's dependent parts to verify that a new or upgraded component conforms to the specification or contract that it expects it to.

Drawing on the example within Sun's specification, the following manifest file describes an imaginary Tourist specification and the API implementation that accompanies this article in the downloadable source code (see Resources):

Manifest-Version: 1.0
Name: tourist/util
Specification-Title: Java Tourist Classes 
Specification-Vendor: Tourist Inc.
Specification-Version: 1.0
Implementation-Title: tourist.util
Implementation-Vendor: Travel Systems.
Implementation-Version: Build 1.0.1-b01

Note the absence of the Created-by field. This attribute is generated by the jar tool when the archive is created, and as such does not need to be specified in the input manifest.

Referring to the Javadoc for the java.lang.Package class (see Resources), you'll discover that for each field defined in the example manifest—with the exception of Manifest-version and Created-by attributes—there is a similarly entitled method. Calling any of these methods returns a java.lang.String object with the value of the attribute as defined in the manifest. The following example shows a small program that does this:

package tourist.demo;
import tourist.util.*;
public class Ex1 {
    public static void main(String[] args)
    {
        Package pkg = Package.getPackage("tourist.util");
        System.out.println("Package name:\t" + pkg.getName());
        System.out.println("Spec title:\t" + pkg.getSpecificationTitle());
        System.out.println("Spec vendor:\t" + pkg.getSpecificationVendor());
        System.out.println("Spec version:\t" + pkg.getSpecificationVersion());
        System.out.println("Impl title:\t" + pkg.getImplementationTitle());
        System.out.println("Impl vendor:\t" + pkg.getImplementationVendor());
        System.out.println("Impl version:\t" + pkg.getImplementationVersion());
    }
}

The code itself is self explanatory, but for those that download the code accompanying the article, there are two minor points to remember.

First, ensure the -m option is used when you create the jar file. This option replaces the default manifest generated by the jar tool with one you specify. The example manifest detailed previously in this article can be found in the downloadable source code. Second, don't forget to include the jar file in the CLASSPATH. For Windows users, I've included a script that does this for you (apologies to those on Unix and Mac systems).

An exception?

In many runtime environments, executing this example will result in a java.lang.NullPointerException being thrown. The reason behind this has to do with the implementation of the classloaders. (See sidebar, "A Note on Classloaders and Packages.")

Classloaders often do not load external jar files into memory until the contained classes are actually required. In the above example, the classloaders are unable to find the required package in memory as none of the classes within the API have been instantiated or invoked. As a result, the call Package.getPackage("tourist/util") simply returns null. To get around this classloader optimization, instantiate one or more classes belonging in the jar file before making the call to access the manifest information. Instantiating a class forces the responsible classloader to read the jar file from the disk, and as a result, brings the manifest information up with it.

The following example, Ex2, also included in the downloadable source code, should produce a better result:

package tourist.demo;
import tourist.util.*;
public class Ex2 {
    public static void main(String[] args)
    {
        Tourist tony = new Tourist();
        Package pkg = Package.getPackage("tourist.util");
        System.out.println("Package name:\t" + pkg.getName())
          . . . .
    }
}

One approach, multiple uses

One of the benefits of component versioning using the technique described in this article is that every jar file contains a manifest file. Whether you create Java servlets, JavaServer Pages, Enterprise JavaBeans, Java connectors in the form of resource adapters, or even just plain old APIs, the same approach can be used for specifying vendor and version information. With built-in support for accessing this type of data in the Java platform libraries, perhaps one day its use within Java component systems will be as pervasive as the About dialog is in Windows applications.

Raised in the foothills of Australia's Great Dividing Ranges, Erik Eide escaped the greed and capitalism of his monastery education for the spiritual enlightenment of the telecommunications industry. Having developed IT systems that occupy the perimeter of intelligent networks and mobile systems in Europe, Australia, and the Americas, he dreams of one day building a really nice barbeque in his backyard.

Learn more about this topic

Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more