3.6 Package Reflection
Most of the Reflection API deals with class-level metadata. However, version information is another form of metadata, and it plays a critical role as code evolves; it makes sure that client code accesses only compatible versions of needed classes. Version metadata is also useful when you need to know the exact version of software that caused a problem. In Java, version information is typically tracked at the level of a package, and it is stored at the level of the JAR file. This is reasonable because the next smaller unit, the class file, is typically too small to be the standard unit of versioning or deployment. Version information is embedded in JAR files, added to the virtual machine by a class loader, and accessed programmatically via the java.lang.Package class.
3.6.1 Setting Package Metadata
In order to use the version metadata currently provided by Java, you must deploy your code as a jar file instead of as separate class files. Adding version information to a jar file is a simple matter of adding name/value pairs to the manifest file. For example, Listing 324 specifies all the possible version fields for a hypothetical com.develop.hello package.
Listing 324 Manifest Entries for Package Versioning
Manifest-version: 1.0 Name: com/develop/hello/ Specification-Title: Hello World Specification-Version: 1.0.0 Specification-Vendor: DevelopMentor Implementation-Title: com.develop.hello Implementation-Version: build1 Implementation-Vendor: DevelopMentor
The manifest information is added to the JAR file with the m switch. Assuming the file above is named hellov1.mf, you might create a JAR file with the command
jar -cmf hellov1.mf hellov1.jar com/develop/hello/Main.class
When a URLClassLoader loads classes from a JAR file that includes version information, it registers the version information with the virtual machine by calling the definePackage method defined by the ClassLoader class, shown in Listing 325. This method takes the name of the package, the six well-known version information strings, and the sealBase (more on sealing in 3.6.3).
Listing 325 The definePackage Method
package java.lang; public class ClassLoader { protected Package definePackage( String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase); //other methods omitted for clarity }
3.6.2 Accessing Package Metadata
The virtual machine makes package metadata available to code at runtime through the Package class. Included in the Package class are accessor methods for the six version info strings, plus two methods for looking up the packages known to the virtual machine, as shown in Listing 326.
Listing 326 Querying Package Metadata
package java.lang; public class Package { public String getSpecificationTitle(); public String getSpecificationVersion(); public String getSpecificationVendor(); public String getImplementationTitle(); public String getImplementationVersion(); public String getImplementationVendor(); public static Package getPackage(String packageName); public static Package[] getPackages(); public Boolean isCompatibleWith(String desired); //additional methods omitted for clarity }
The version strings provide a minimal infrastructure you can use as a starting point when you are developing versioning semantics for your applications. All six well-known version strings can be set to any arbitrary String value; none has any semantics that are enforced by the current version of the SDK.
One of the six version strings provides a documented semantic. If you set the specVersion value to a dotted string, such as 1.0.2, you can use the isCompatibleWith API to see if a package is compatible with a particular version number. The specification version should be a dotted number such as 1.0.5, and the compatibility check uses a simple definition of compatibility, in which higher-numbered versions are always compatible with lower numbered ones. Although this usage of specVersion is documented, it is not enforced by the platform. For the other version strings, no semantics are even documented, and you can define any semantic that is convenient for you.
3.6.3 Sealing Packages
JAR files also support the process of sealing a package. When you seal a package, you guarantee that all the code from that package must come from the same location. This is valuable for versioning and security because it allows you to guarantee that your packages are not polluted by invalid or malicious versions of any classes. To seal a package, you add a Sealed: true pair to the metadata, somewhere after a package's Name field and before the next blank line, like so:
Name: com/develop/hello/ Sealed: true
You can also seal all packages in a JAR file by adding the Sealed entry to the main section of the manifest. For package sealing to take effect, you must use a class loader that honors the metadata in the JAR file, such as your good friend URLClassLoader.
Sealing all of your packages is a very good idea. Consider the following, all-too-common scenario. Version 2.0 of an application modifies several classes from version 1.0 while it also adds some new classes. Unfortunately, JAR files for both version 1.0 and version 2.0 are on the classpath. As a result, the virtual machine loads a mix of version 1.0 and version 2.0 classes, a sure recipe for trouble and confusion. If either version had sealed its packages, this configuration problem would have triggered an easily diagnosed error. Unless you specifically want to load package code from more than one JAR, always seal all application packages.
3.6.4 Weaknesses of the Versioning Mechanism
There are several problems with the versioning mechanism as it exists today. First, though the class loaders provided with the core API correctly propagate version information as described above, they do not automatically reject incompatible versions of a package, nor do they seek out compatible ones. Second, the URLClassLoader and subclasses do not load package information until after the first class in a particular package is loaded, so you have to load at least one class in a package before you can find out whether the package version actually meets your needs. Third, the text format for the manifest is not adequately validated; so for example, a spelling error in the name of a version field silently obliterates that field's information. The 1.4 version of Java is slated to have a built-in XML parser, which would permit a more structured manifest format. For the other problems, you will have to wait a while longer or address them in your own code. (5.5.1 presents a more robust approach to versioning based on custom class loaders.)