The Extension Mechanism

 


 

Overview

The extension mechanism enables the runtime environment to find and load extension classes without the extension classes having to be named on the class path, thereby extending the platform's core API.

The extension mechanism also provides a means for extension classes to be downloaded from remote locations for use by applets.

Extensions are bundled as Java Archive (JAR) files, and this trail assumes that you are familiar with the JAR file format. You can find further information about extensions in the The Java Extensions Mechanism section of the JDK 1.2 documentation.

 


 

Installed Extensions

Installed extensions are JAR files in the lib/ext directory of the Java Runtime Environment (JRE) software. As its name implies, the JRE is the runtime portion of the Java Development Kit containing the platform's core API but without development tools such as compilers and debuggers. The JRE is available either by itself or as part of the Java Development Kit.

In the 1.2 platform, the JRE is a strict subset of the JDK software. The JDK 1.2 software directory tree looks like this:

JRE 1.2 consists of those directories within the highlighted box in the diagram. Whether your JRE is stand-alone or part of the JDK software, any JAR file in the JRE's lib/ext directory is automatically treated by the runtime environment as an extension.

 

 

A Simple Example

Let's create a simple installed extension. Our extension consists of one class, RectangleArea, that computes the areas of rectangles:

{
    public static int area(java.awt.Rectangle r) 
    {
        return r.width * r.height;
    }
}

This class has a single method, area, that takes an instance of java.awt.Rectangle and returns the rectangle's area.

Suppose that you want to test RectangleArea with an application called AreaApp:

import java.awt.*;

public class AreaApp 
{
    public static void main(String[] args) 
    {
        int width = 10;
        int height = 5;

        Rectangle r = new Rectangle(width, height);
        System.out.println("The rectangle's area is " 
                           + RectangleArea.area(r));
    }
}

This application instantiates a 10 x 5 rectangle, and then prints out the rectangle's area using the RectangleArea.area method.

 

 

Running AreaApp Without the Extension Mechanism

Let's first review how you would run the AreaApp application without using the extension mechanism. We'll assume that the RectangleArea class is bundled in a JAR file named area.jar.

The RectangleArea class is not part of the Java platform, of course, so you would need to place the area.jar file on the class path in order to run AreaApp without getting a runtime exception. If area.jar was in the directory /home/user, for example, you could use this command:

java -classpath .:/home/user/area.jar AreaApp 

The class path specified in this command contains both the current directory, containing AreaApp.class, and the path to the JAR file containing the RectangleArea package. You would get the desired output by running this command:

The rectangle's area is 50

 

 

Running AreaApp by Using the Extension Mechanism

Now let's look at how you would run AreaApp by using the RectangleArea class as an extension.

To make the RectangleArea class into an extension, you place the file area.jar in the lib/ext directory of the JRE. Doing so automatically gives the RectangleArea the status of being an installed extension.

With area.jar installed as an extension, you can run AreaApp without needing to specify the class path:

java AreaApp 

Because you're using area.jar as an installed extension, the runtime environment will be able to find and to load the RectangleArea class even though you haven't specified it on the class path. Similarly, any applet or application being run by any user on your system would be able to find and use the RectangleArea class.

 


 

Download Extensions

Download extensions are classes, including classes in JAR files, that are specified in the Class-Path headers in the manifests of other JAR files. Assume for example that a.jar and b.jar are two JAR files in the same directory, and that the manifest of a.jar contains this header:

Class-Path: b.jar

Then the classes in b.jar serve as extension classes for purposes of the classes in a.jar. The classes in a.jar can invoke classes in b.jar without b.jar's classes having to be named on the class path. a.jar may or may not itself be an extension. If b.jar weren't in the same directory as a.jar, then the value of the Class-Path header should be set to the relative pathname of b.jar.

There's nothing special about the classes that are playing the role of a download extension. They are treated as extensions solely because they're referenced by the manifest of some other JAR file.

To get a better understanding of how download extensions work, let's create one and put it to use.

 

 

An Example

Suppose you want to create an applet that makes use of the RectangleArea class of the previous section:

{  
    public static int area(java.awt.Rectangle r) 
    {
        return r.width * r.height;
    }
}

In the previous section, you made the RectangleArea class into an installed extension by placing the JAR file containing it into the lib/ext directory of the JRE. By making it an installed extension, you enabled any application to use the RectangleArea class as if it were part of the Java platform.

If you want to be able to use the RectangleArea class from an applet, the situation is a little different. Suppose, for example, that you have an applet, AreaApplet, that makes use of class RectangleArea:

import java.applet.Applet;
import java.awt.*;

public class AreaApplet extends Applet 
{
    Rectangle r;

    public void init() 
    {    
        int width = 10;
        int height = 5;

        r = new Rectangle(width, height);
    }

    public void paint Graphics(g) 
    {
        g.drawString("The rectangle's area is " 
                      + RectangleArea.area(r), 10, 10);
    }
}

This applet instantiates a 10 x 5 rectangle and then displays the rectangle's area by using the RectangleArea.area method.

However, you can't assume that everyone who downloads and uses your applet is going to have the RectangleArea class available on their system, as an installed extension or otherwise. One way around that problem is to make the RectangleArea class available from the server side, and you can do that by using it as a download extension.

To see how that's done, let's assume that you've bundled AreaApplet in a JAR file called AreaApplet.jar and that the class RectangleArea is bundled in RectangleArea.jar. In order for RectangleArea.jar to be treated as a download extension, RectangleArea.jar must be listed in the Class-Path header in AreaApplet.jar's manifest. AreaApplet.jar's manifest might look like this, for example:

Manifest-Version: 1.0
Class-Path: RectangleArea.jar

The value of the Class-Path header in this manifest is RectangleArea.jar with no path specified, indicating that RectangleArea.jar is located in the same directory as the applet's JAR file.

 

 

More about the Class-Path Header

If an applet or application uses more than one extension, you can list multiple URLs in a manifest. For example, the following is a valid header:

Class-Path: area.jar servlet.jar images/

In the Class-Path header any URLs listed that don't end with '/' are assumed to be JAR files. URLs ending in '/' indicate directories. In the preceding example, images/ might be a directory containing resources needed by the applet or the application.

You can also specify multiple extension URLs by using more than one Class-Path header in the manifest. For example:

Class-Path: area.jar
Class-Path: servlet.jar

Download extensions can be "daisy chained", meaning that the manifest of one download extension can have a Class-Path header that refers to a second extension, which can refer to a third extension, and so on.

 


 

Extension Class Loading

The extension framework makes use of the new class-loading mechanism in the Java 1.2 platform. When the runtime environment needs to load a new class for an application, it looks for the class in the following locations, in order:

  1. Bootstrap classes: the runtime classes in rt.jar and internationalization classes in i18n.jar.

  2. Installed extensions: classes in JAR files in the lib/ext directory of the JRE.

  3. The class path: classes, including classes in JAR files, on paths specified by the system property java.class.path. If a JAR file on the class path has a manifest with the Class-Path attribute, JAR files specified by the Class-Path attribute will be searched also. By default, the java.class.path property's value is ., the current directory. You can change the value by setting the CLASSPATH environment variable or by using the -classpath or -cp command-line options. These command-line options override the setting of the CLASSPATH environment variable. Note that in the Java 1.2 software, java.class.path no longer includes the bootstrap classes in rt.jar and i18n.jar.

The precedence list tells you, for example, that the class path is searched only if a class to be loaded hasn't been found among the classes in rt.jar, i18n.jar or the installed extensions.

Unless your software instantiates its own class loaders for special purposes, you don't really need to know much more than to keep this precedence list in mind. In particular, you should be aware of any class name conflicts that might be present. For example, if you list a class on the class path, you'll get unexpected results if the runtime environment instead loads another class of the same name that it found in an installed extension.

 

 

The 1.2 Class Loading Mechanism

The Java 1.2 platform uses a new delegation model for loading classes. The basic idea is that every class loader has a "parent" class loader. When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself.

Here are some highlights of the class-loading API:

  • Constructors in java.lang.ClassLoader and its subclasses allow you to specify a parent when you instantiate a new class loader. If you don't explicitly specify a parent, the virtual machine's system class loader will be assigned as the default parent.

  • The loadClass method in ClassLoader performs these tasks, in order, when called to load a class:

    1. If a class has already been loaded, it returns it.

    2. Otherwise, it delegates the search for the new class to the parent class loader.

    3. If the parent class loader doesn't find the class, loadClass calls the method findClass to find and load the class.

  • The findClass method of ClassLoader searches for the class in the current class loader if the class wasn't found by the parent class loader. You will probably want to override this method when you instantiate a class loader subclass in your application.

  • The class java.net.URLClassLoader has been added to the core API. This class serves as the basic class loader for extensions and other JAR files, overriding the findClass method of java.lang.ClassLoader to search one or more specified URLs for classes and resources.

To see a sample application that uses some of the new API as it relates to JAR files, see the JAR File Format trail in this tutorial.

 

 

Changes to the java Command

The 1.2 platform's class-loading mechanism is reflected in some changes to the java command.

  • The 1.2 JRE now includes the same Java interpreter, invoked with the java command, as is in the JDK 1.2 software. This tool replaces the old jre tool in JRE 1.1.

  • In the 1.2 java tool, the -classpath option is a shorthand way to set the java.class.path property. Formerly the -classpath option was used to override the search path for system classes.

  • The -cp option, formerly part of the jre utility, has been added as an option to the java command. The -cp and -classpath options are equivalent.

  • The -jar option has been added for running applications that are packaged in JAR files. For a description and examples of this new option, see the JAR File Format trail in this tutorial.

 


 

Setting Privileges for Extensions

If a security manager is in force, the following conditions must be met to enable any software, including extension software, to perform security-sensitive operations:

  • The security-sensitive code in the extension must be wrapped in a PrivilegedAction object.

  • The security policy implemented by the security manager must grant the appropriate permission to the extension. By default, installed extensions are granted all security permissions as if they were part of the core platform API. The permissions granted by the security policy apply only to code wrapped in the PrivilegedAction instance.

Let's look at each of these conditions in a little more detail, with some examples.

 

 

Using the PrivilegedAction Class

Suppose that you want to modify the RectangleArea class in the extension example of the previous lesson to write rectangle areas to a file rather than to stdout. Writing to a file, however, is a security-sensitive operation, so if your software is going to be running under a security manager, you'll need to mark your code as being privileged. There are two steps you need to take to do so:

  1. You need to place code that performs security-sensitive operations within the run method of an object of type java.security.PrivilegedAction.

  2. You must use that PrivilegedAction object as the argument in a call to the doPrivileged method of java.security.AccessController.

If we apply those guidelines to the RectangleArea class, our class definition would look something like this:

import java.io.*;
import java.security.*;

{
    {
        AccessController.doPrivileged(new PrivilegedAction() 
        {
	    public Object run() 
            {
	        try 
                { 
		    int area = r.width * r.height;
		    FileWriter fw = new FileWriter("/tmp/AreaOutput");
		    fw.write("The rectangle's area is " + area);
		    fw.flush();
		    fw.close();
	        } 
                catch IOException(ioe) 
                {
		    System.err.println(ioe);
	        }
	        return null;
	    }
        });
    }
}

The single method in this class, writeArea, computes the area of a rectangle, and writes the area to a file called AreaOutput in the /tmp directory.

The security-sensitive statements dealing with the output file are placed within the run method of a new instance of PrivilegedAction. (Note that run requires that an Object instance be returned. The returned object can be null.) The new PrivilegedAction instance is then passed as an argument in a call to AccessController.doPrivileged.

For more information about using doPrivileged, see New API for Privileged Blocks in the JDK documentation.

Wrapping security-sensitive code in a PrivilegedAction object in this manner is the first requirement for enabling an extension to perform security-sensitive operations. The second requirement is: getting the security manager to grant the privileged code the appropriate permissions.

 

 

Specifying Permissions with the Security Policy

The security policy in force at runtime is specified by a policy file. The default policy is set by the file lib/security/java.policy in the JRE software.

The policy file assigns security privileges to software by using grant entries. The policy file can contain any number of grant entries. The default policy file has this grant entry for installed extensions:

grant codeBase "file:${java.home}/lib/ext/" 
{
    permission java.security.AllPermission;
};
This entry specifies that files at the location file:${java.home}/lib/ext/ are to be granted the permission called java.security.AllPermission. (Note that in the Java 1.2 Java platform, java.home refers to the jre directory.) It's not too hard to guess that java.security.AllPermission grants installed extensions all the security privileges that it's possible to grant.

By default, then, installed extensions have no security restrictions. Extension software can perform security-sensitive operations as if there were no security manager installed, provided that security-sensitive code is contained in an instance of PrivilegedAction passed as an argument in a doPrivileged call.

To limit the privileges granted to extensions, you need to modify the policy file. To deny all privileges to all extensions, you could simply remove the above grant entry.

Not all permissions are as comprehensive as the java.security.AllPermissions granted by default. After deleting the default grant entry, you can enter a new grant entry for one or more of the following limited permissions:

The Policy Permissions file in the JDK documentation provides details about each of these permissions. Let's look at just one, java.io.FilePermission, as an example.

The only permission that the RectangleArea.writeArea method really needs is the permission to write to a file. Assuming that the RectangleArea class is bundled in the file area.jar, you could grant write privileges by adding this entry to the policy file:

grant codeBase "file:${java.home}/lib/ext/area.jar" 
{
    permission java.io.FilePermission "/tmp/*", "write";  
};

The codeBase "file:${java.home}/lib/ext/area.jar" part of this entry guarantees that any permissions specified by this entry will apply only to your JAR file. The java.io.FilePermission permits access to files. The first string argument, "/tmp/*", indicates that area.jar is being granted permission to access all files in the /tmp directory. The second argument indicates that the file access being granted is only for writing. (Other choices for the second argument are "read", "delete", and "execute".)

 

 

Signing Extensions

You can use the policy file to place additional restrictions on the permissions granted to extensions by requiring them to be signed by a trusted entity. (For a review of signing and verifying JAR files, see the JAR File Format trail in this tutorial.)

To allow signature verification of extensions or other software in conjunction with granting permissions, the policy file must contain a keystore entry. The keystore entry specifies which keystore is to be used in the verification. Keystore entries have the form

keystore "keystore_url";

The URL keystore_url is either an absolute or relative. If it's relative, the URL is relative to the location of the policy file.

To indicate that an extension must be signed in order to be granted security privileges, you use the signedBy field. For example, the following entry indicates that the extension area.jar is to be granted write privileges only if it is signed by the users identified in the keystore by the aliases Robert and Rita:

grant signedBy "Robert,Rita", codeBase "file:${java.home}/lib/ext/area.jar" 
{
        permission java.io.FilePermission "*", "write";  
};

If the codeBase field is omitted, as in the following "grant", the permissions are granted to any software, including installed or download extensions, that are signed by "Robert" or "Rita":

grant signedBy "Robert,Rita" 
{
    permission java.io.FilePermission "*", "write";  
};

For further details about the policy file format, see section 3.3.1 of the Security Architecture Specification in the JDK documentation.

 


 

Sealing Packages in Extensions

You can optionally seal packages in extension JAR files as an additional security measure. If a package is sealed, all classes defined in that package must originate from a single JAR file.

Without sealing, a "hostile" program could create a class and define it to be a member of one of your extension packages. The hostile software would then have free access to package-protected members of your extension package.

Sealing packages in extensions is no different than sealing any JAR-packaged classes. To seal your extension packages, you must add the Sealed header to the manifest of the JAR file containing your extension. You can seal individual packages by associating a Sealed header with the packages' Name headers. A Sealed header not associated with an individual package in the archive signals that all packages are sealed. Such a "global" Sealed header is overridden by any Sealed headers associated with individual packages. The value associated with the Sealed header is either true or false.

 

 

Examples

Let's look at a few sample manifest files. For these examples suppose that the JAR file contains these packages:

com/myCompany/package_1/
com/myCompany/package_2/
com/myCompany/package_3/
com/myCompany/package_4/

Suppose that you want to seal all the packages. You could do so by simply adding an archive-level Sealed header to the manifest like this:

Manifest-Version: 1.0
Sealed: true

All packages in any JAR file having this manifest will be sealed.

If you wanted to seal only com.myCompany.package_3, you could do so with this manifest:

Manifest-Version: 1.0

Name: com/myCompany/package_3/
Sealed: true

In this example the only Sealed header is that associated with the Name header of package com.myCompany.package_3, so only that package is sealed. (The Sealed header is associated with the Name header because there are no blank lines between them.)

Manifest-Version: 1.0
Sealed: true

Name: com/myCompany/package_2/
Sealed: false

In this example the archive-level Sealed: true header indicates that all of the packages in the JAR file are to be sealed. However, the manifest also has a Sealed: false header associated with package com.myCompany.package_2, and that header overrides the archive-level sealing for that package. Therefore this manifest will cause all packages to be sealed except for com.myCompany.package_2.

 

Home