The Extension Mechanism

«« Previous
Next »»

The extension mechanism provides a standard, scalable way to make custom APIs available to all applications running on the Java platform. Java extensions are also referred to as optional packages. This trail may use both terms interchangeably.

Extensions are groups of packages and classes that augment the Java platform through the extension mechanism. 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. In that respect, extension classes are similar to the Java platform's core classes. That's also where extensions get their name -- they, in effect, extend the platform's core API.

Since this mechanism extends the platform's core API, its use should be judiciously applied. Most commonly it is used for well standarized interfaces such as those defined by the Java Community Process, although it may also be appropriate for site wide interfaces.

Oracle Java Tutorials and Materials, Oracle Java Guides, Oracle Java Certifications

As the diagram indicates, extensions act as "add-on" modules to the Java platform. Their classes and public APIs are automatically available to any applications running on the platform.

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

This trail has two lessons:

1. Creating and Using Extensions


Any set of packages or classes can easily be made to play the role of an extension. The first step in turning a set of classes into an extension is to bundle them in a JAR file. Once that's done, you can turn the software into an extension in two ways:

by placing the JAR file in a special location in the directory structure of the Java Runtime Environment, in which case it's called an installed extension.
by referencing the JAR file in a specified way from the manifest of the another JAR file, in which case it's called a download extension.
This lesson shows you how the extension mechanism works by using a simple "toy" extension as an example.

◉ 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.

The JRE is a strict subset of the JDK software. A subset of the JDK software directory tree looks like this:

Oracle Java Tutorials and Materials, Oracle Java Guides, Oracle Java Certifications

The JRE 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 lib/ext of the JRE directory is automatically treated by the runtime environment as an extension.

Since installed extensions extend the platform's core API, use them judiciously. They are rarely appropriate for interfaces used by a single, or small set of applications.

Furthermore, since the symbols defined by installed extensions will be visible in all Java processes, care should be taken to ensure that all visible symbols follow the appropriate "reverse domain name" and "class hierarchy" conventions. For example, com.mycompany.MyClass.

As of Java 6, extension JAR files may also be placed in a location that is independent of any particular JRE, so that extensions can be shared by all JREs that are installed on a system. Prior to Java 6, the value of java.ext.dirs referred to a single directory, but as of Java 6 it is a list of directories (like CLASSPATH) that specifies the locations in which extensions are searched for. The first element of the path is always the lib/ext directory of the JRE. The second element is a directory outside of the JRE. This other location allows extension JAR files to be installed once and used by several JREs installed on that system. The location varies depending on the operating system:

◈ Solaris™ Operating System: /usr/jdk/packages/lib/ext
◈ Linux: /usr/java/packages/lib/ext
◈ Microsoft Windows: %SystemRoot%\Sun\Java\lib\ext

Note that an installed extension placed in one of the above directories extends the platform of each of the JREs (Java 6 or later) on that system.

A Simple Example

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

public final class RectangleArea {
    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.

If there are multiple JREs (Java 6 or later) installed on a system and want the RectangleArea class to be available as an extension to all of them, instead of installing it in the lib/ext directory of a particular JRE, install it in the system-wide location. For example, on system running Linux, install area.jar in the directory /usr/java/packages/lib/ext. Then AreaApp can run using different JREs that are installed on that system, for example if different browsers are configured to use different JREs.

◉ Download Extensions


Download extensions are sets of classes (and related resources) in JAR files. A JAR file's manifest can contain headers that refer to one or more download extensions. The extensions can be referenced in one of two ways:

◈ by a Class-Path header
◈ by an Extension-List header

Note that at most one of each is allowed in a manifest. Download extensions indicated by a Class-Path header are downloaded only for the lifetime of the application that downloads them, such as a web browser. Their advantage is that nothing is installed on the client; their disadvantage is that they are downloaded each time they are needed. Download extensions that are downloaded by an Extension-List header are installed into the /lib/ext directory of the JRE that downloads them. Their advantage is that they are downloaded the first time they're needed; subsequently they can be used without downloading. But, as shown later in this tutorial, they are more complex to deploy.

Since download extensions that use the Class-Path headers are simpler, let's consider them first. 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 final class RectangleArea {  
    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.

Note that only one Class-Path header is allowed in a manifest file, and that each line in a manifest must be no more than 72 characters long. If you need to specify more class path entries than will fit on one line, you can extend them onto subsequent continuation lines. Begin each continuation line with two spaces. For example:

Class-Path: area.jar servlet.jar monitor.jar datasource.jar
  provider.jar gui.jar

A future release may remove the limitation of having only one instance of each header, and of limiting lines to only 72 characters.

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.

Installing Download Extensions

In the above example, the extension downloaded by the applet is available only while the browser which loaded the applet is still running. However, applets can trigger installation of extensions, if additional information is included in the manifests of both the applet and the extension.

Since this mechanism extends the platform's core API, its use should be judiciously applied. It is rarely appropriate for interfaces used by a single, or small set of applications. All visible symbols should follow reverse domain name and class hierarchy conventions.

The basic requirements are that both the applet and the extensions it uses provide version information in their manifests, and that they be signed. The version information allows Java Plug-in to ensure that the extension code has the version expected by the applet. For example, the AreaApplet could specify an areatest extension in its manifest:

Manifest-Version: 1.0
Extension-List: areatest
areatest-Extension-Name: area
areatest-Specification-Version: 1.1
areatest-Implementation-Version: 1.1.2
areatest-Implementation-Vendor-Id: com.example
areatest-Implementation-URL: http://www.example.com/test/area.jar

The manifest in area.jar would provide corresponding information:

Manifest-Version: 1.0
Extension-Name: area
Specification-Vendor: Example Tech, Inc
Specification-Version: 1.1
Implementation-Vendor-Id: com.example
Implementation-Vendor: Example Tech, Inc
Implementation-Version: 1.1.2

Both the applet and the extension must be signed, by the same signer. Signing the jar files will modify them in-place, providing more information in their manifest files. Signing helps ensure that only trusted code gets installed. A simple way to sign jar files is to first create a keystore, and then use that to hold certificates for the applet and extension. For example:

keytool -genkey -dname "cn=Fred" -alias test  -validity 180

You will be prompted for the keystore and key passwords. After generating a key, the jar files can be signed:

jarsigner AreaApplet.jar test
jarsigner area.jar test

You will be prompted for the keystore and key passwords. More information on keytool, jarsigner, and other security tools is at the Summary of Tools for the Java 2 Platform Security.

Here is AreaDemo.html, which loads the applet and causes the extension code to be downloaded and installed:

<html>
<body>
  <applet code="AreaApplet.class" archive="AreaApplet.jar"/>
</body>
</html>

When the page is loaded for the first time, the user is told that the applet requires installation of the extension. A subsequent dialog informs the user about the signed applet. Accepting both installs the extension in the lib/ext folder of the JRE and runs the applet.

After restarting the web browser and load the same web page, only the dialog about the applet's signer is presented, because area.jar is already installed. This is also true if AreaDemo.html is opened in a different web browser (assuming both browsers use the same JRE).

◈ Understanding Extension Class Loading


The extension framework makes use of the class-loading delegation mechanism. 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, internationalization classes in i18n.jar, and others.
2. Installed extensions: classes in JAR files in the lib/ext directory of the JRE, and in the system-wide, platform-specific extension directory (such as /usr/jdk/packages/lib/ext on the Solaris™ Operating System, but note that use of this directory applies only to Java™ 6 and later).
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 using the -classpath or -cp command-line options, or setting the CLASSPATH environment variable. The command-line options override the setting of the CLASSPATH environment variable.

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 Java Class Loading Mechanism

The Java platform uses a 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 does not 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 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.

Class Loading and the java Command

The Java platform's class-loading mechanism is reflected in the java command.

◈ In the java tool, the -classpath option is a shorthand way to set the java.class.path property.
◈ The -cp and -classpath options are equivalent.
◈ The -jar option runs applications that are packaged in JAR files.

◉ Creating Extensible Applications


The following topics are covered:
  • Introduction
An extensible application is one that you can extend without modifying its original code base. You can enhance its functionality with new plug-ins or modules. Developers, software vendors, and customers can add new functionality or application programming interfaces (APIs) by adding a new Java Archive (JAR) file onto the application class path or into an application-specific extension directory.

This section describes how to create applications with extensible services, which enable you or others to provide service implementations that require no modifications to the original application. By designing an extensible application, you provide a way to upgrade or enhance specific parts of a product without changing the core application.

One example of an extensible application is a word processor that allows the end user to add a new dictionary or spelling checker. In this example, the word processor provides a dictionary or spelling feature that other developers, or even customers, can extend by providing their own implementation of the feature.

The following are terms and definitions important to understand extensible applications:

Service

A set of programming interfaces and classes that provide access to some specific application functionality or feature. The service can define the interfaces for the functionality and a way to retrieve an implementation. In the word-processor example, a dictionary service can define a way to retrieve a dictionary and the definition of a word, but it does not implement the underlying feature set. Instead, it relies on a service provider to implement that functionality.

Service provider interface (SPI)

The set of public interfaces and abstract classes that a service defines. The SPI defines the classes and methods available to your application.

Service Provider

Implements the SPI. An application with extensible services enable you, vendors, and customers to add service providers without modifying the original application.
  • Dictionary Service Example
Consider how you might design a dictionary service in a word processor or editor. One way is to define a service represented by a class named DictionaryService and a service provider interface named Dictionary. The DictionaryService provides a singleton DictionaryService object. (See the section The Singleton Design Pattern for more information.) This object retrieves definitions of words from Dictionary providers. Dictionary service clients — your application code — retrieve an instance of this service, and the service will search, instantiate, and use Dictionary service providers.

Although the word-processor developer would most likely provide a basic, general dictionary with the original product, the customer might require a specialized dictionary, perhaps containing legal or technical terms. Ideally, the customer is able to create or purchase new dictionaries and add them to the existing application.

The DictionaryServiceDemo sample shows you how to implement a Dictionary service, create Dictionary service providers that add additional dictionaries, and create a simple Dictionary service client that tests the service. This sample, which is packaged in the zip file DictionaryServiceDemo.zip, consists of the following files:
  • build.xml
  • DictionaryDemo
    • build.xml
    • build
    • dist
      • DictionaryDemo.jar
    • src
      • dictionary
        • DictionaryDemo.java
  • DictionaryServiceProvider
    • build.xml
    • build
    • dist
      • DictionaryServiceProvider.jar
    • src
      • dictionary
        • DictionaryService.java
        • spi
          • Dictionary.java
  • ExtendedDictionary
    • build.xml
    • build
    • dist
      • ExtendedDictionary.jar
    • src
      • dictionary
        • ExtendedDictionary.java
      • META-INF
        • services
          • dictionary.spi.Dictionary
  • GeneralDictionary
    • build.xml
    • build
    • dist
      • GeneralDictionary.jar
    • src
      • dictionary
        • GeneralDictionary.java
      • META-INF
        • services
          • dictionary.spi.Dictionary
Note: The build directories contain the compiled class files of the Java source files contained in the src directory in the same level.
  • Running the DictionaryServiceDemo Sample
Because the zip file DictionaryServiceDemo.zip contains compiled class files, you can unzip this file to your computer and run the sample without compiling it by following these steps:

1. Download and unzip the sample code: Download and unzip the file DictionaryServiceDemo.zip to your computer. It is These steps assume that you unzipped the contents of this file into the directory C:\DictionaryServiceDemo.

2. Change the current directory to C:\DictionaryServiceDemo\DictionaryDemo and follow the step Run the Client.
  • Compiling and Running the DictionaryServiceDemo Sample
The DictionaryServiceDemo sample includes Apache Ant build files, which are all named build.xml. The following steps show you how to use Apache Ant to compile, build, and run the DictionaryServiceDemo sample:

1. Install Apache Ant: Go to the following link to download and install Apache Ant:

http://ant.apache.org/

Ensure that the directory that contains the Apache Ant executable file is in your PATH environment variable so that you can run it from any directory. In addition, ensure that your JDK's bin directory, which contains the java and javac executables (java.exe and javac.exe for Microsoft Windows). is in your PATH environment variable.

2. Download and unzip the sample code: Download and unzip the file DictionaryServiceDemo.zip to your computer. These steps assume that you unzipped the contents of this file into the directory C:\DictionaryServiceDemo.

3. Compile the code: Change the current directory to C:\DictionaryServiceDemo and run the following command:

ant compile-all

This command compiles the source code in the src directories contained in the directories DictionaryDemo, DictionaryServiceProvider, ExtendedDictionary, and GeneralDictionary, and puts the generated class files in the corresponding build directories.

4. Package the compiled Java files into JAR files: Ensure the current directory is C:\DictionaryServiceDemo and run the following command:

ant jar

This command creates the following JAR files:

◈ DictionaryDemo/dist/DictionaryDemo.jar
◈ DictionaryServiceProvider/dist/DictionaryServiceProvider.jar
◈ GeneralDictionary/dist/GeneralDictionary.jar
◈ ExtendedDictionary/dist/ExtendedDictionary.jar

5. Run the sample: Ensure that the directory that contains the java executable is in your PATH environment variable. See PATH and CLASSPATH for more information.

Change the current directory to C:\DictionaryServiceDemo\DictionaryDemo and run the following command:

ant run

The sample prints the following:

book: a set of written or printed pages, usually bound with a protective cover
editor: a person who edits
xml: a document standard often used in web services, among other things
REST: an architecture style for creating, reading, updating, and deleting data that attempts to use the common vocabulary of the HTTP protocol; Representational State Transfer
  • Understanding the DictionaryServiceDemo Sample
The following steps show you how to re-create the contents of the file DictionaryServiceDemo.zip. These steps show you how the sample works and how to run it.
    • Define the Service Provider Interface
The DictionaryServiceDemo sample defines one SPI, the Dictionary.java interface. It contains only one method:

package dictionary.spi;

public interface Dictionary {
    public String getDefinition(String word);
}

The sample stores the compiled class file in the directory DictionaryServiceProvider/build.
    • Define the Service That Retrieves the Service Provider Implementations
The DictionaryService.java class loads and accesses available Dictionary service providers on behalf of dictionary service clients:


package dictionary;

import dictionary.spi.Dictionary;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;

public class DictionaryService {

    private static DictionaryService service;
    private ServiceLoader<Dictionary> loader;

    private DictionaryService() {
        loader = ServiceLoader.load(Dictionary.class);
    }

    public static synchronized DictionaryService getInstance() {
        if (service == null) {
            service = new DictionaryService();
        }
        return service;
    }


    public String getDefinition(String word) {
        String definition = null;

        try {
            Iterator<Dictionary> dictionaries = loader.iterator();
            while (definition == null && dictionaries.hasNext()) {
                Dictionary d = dictionaries.next();
                definition = d.getDefinition(word);
            }
        } catch (ServiceConfigurationError serviceError) {
            definition = null;
            serviceError.printStackTrace();

        }
        return definition;
    }
}

The sample stores the compiled class file in the directory DictionaryServiceProvider/build.

The DictionaryService class implements the singleton design pattern. This means that only a single instance of the DictionaryService class is ever created. See the section The Singleton Design Pattern for more information.

The DictionaryService class is the dictionary service client's entry point to using any installed Dictionary service provider. Use the ServiceLoader.load method to retrieve the private static member DictionaryService.service, the singleton service entry point. Then the application can call the getDefinition method, which iterates through available Dictionary providers until it finds the targeted word. The getDefinition method returns null if no Dictionary instance contains the specified definition of the word.

The dictionary service uses the ServiceLoader.load method to find the target class. The SPI is defined by the interface dictionary.spi.Dictionary, so the example uses this class as the load method's argument. The default load method searches the application class path with the default class loader.

However, an overloaded version of this method enables you to specify custom class loaders if you wish. That enables you to do more sophisticated class searches. A particularly enthusiastic programmer might, for example, create a ClassLoader instance that can search in an application-specific subdirectory that contains provider JARs added during runtime. The result is an application that does not require a restart to access new provider classes.

After a loader for this class exists, you can use its iterator method to access and use each provider that it finds. The getDefinition method uses a Dictionary iterator to go through the providers until it finds a definition for the specified word. The iterator method caches Dictionary instances, so successive calls require little additional processing time. If new providers have been placed into service since the last invocation, the iterator method adds them to the list.

The DictionaryDemo.java class uses this service. To use the service, the application obtains a DictionaryService instance and calls the getDefinition method. If a definition is available, the application prints it. If a definition is not available, the application prints a message stating that no available dictionary carries the word.
      • The Singleton Design Pattern
A design pattern is a general solution to a common problem in software design. The idea is that the solution gets translated into code, and that code can be applied in different situations where the problem occurs. The singleton pattern describes a technique to ensure that only a single instance of a class is ever created. In essence, the technique takes the following approach: Do not let anyone outside the class create instances of the object.

For example, the DictionaryService class implements the singleton pattern as follows:

◈ Declares the DictionaryService constructor as private, which prevents all other classes, except DictionaryService, from creating instances of it.
◈ Defines the DictionaryService member variable service as static, which ensures only one instance of DictionaryService exists.
◈ Defines the method getInstance, which enables other classes controlled access to the DictionaryService member variable service.
    • Implement the Service Provider
To provide this service, you must create a Dictionary.java implementation. To keep things simple, create a general dictionary that defines just a few words. You can implement the dictionary with a database, a set of property files, or any other technology. The easiest way to demonstrate the provider pattern is to include all the words and definitions within a single file.

The following code shows an implementation of the Dictionary SPI, the GeneralDictionary.java class. Notice that it provides a no-argument constructor and implements the getDefinition method defined by the SPI.

package dictionary;

import dictionary.spi.Dictionary;
import java.util.SortedMap;
import java.util.TreeMap;

public class GeneralDictionary implements Dictionary {

    private SortedMap<String, String> map;
    
    public GeneralDictionary() {
        map = new TreeMap<String, String>();
        map.put(
            "book",
            "a set of written or printed pages, usually bound with " +
                "a protective cover");
        map.put(
            "editor",
            "a person who edits");
    }

    @Override
    public String getDefinition(String word) {
        return map.get(word);
    }

}

The sample stores the compiled class file in the directory GeneralDictionary/build. Note: You must compile the classes dictionary.DictionaryService and dictionary.spi.Dictionary before the class GeneralDictionary.

The GeneralDictionary provider for this example defines just two words: book and editor. Obviously, a more usable dictionary would provide a more substantial list of generally used vocabulary.

To demonstrate how multiple providers can implement the same SPI, the following code shows yet another possible provider. The ExtendedDictionary.java service provider is an extended dictionary containing technical terms familiar to most software developers.

package dictionary;

import dictionary.spi.Dictionary;
import java.util.SortedMap;
import java.util.TreeMap;

public class ExtendedDictionary implements Dictionary {

        private SortedMap<String, String> map;

    public ExtendedDictionary() {
        map = new TreeMap<String, String>();
        map.put(
            "xml",
            "a document standard often used in web services, among other " +
                "things");
        map.put(
            "REST",
            "an architecture style for creating, reading, updating, " +
                "and deleting data that attempts to use the common " +
                "vocabulary of the HTTP protocol; Representational State " +
                "Transfer");
    }

    @Override
    public String getDefinition(String word) {
        return map.get(word);
    }

}

The sample stores the compiled class file in the directory ExtendedDictionary/build. Note: You must compile the classes dictionary.DictionaryService and dictionary.spi.Dictionary before the class ExtendedDictionary.

It is easy to imagine customers using a complete set of Dictionary providers for their own special needs. The service loader API enables them to add new dictionaries to their application as their needs or preferences change. Because the underlying word-processor application is extensible, no additional coding is required for customers to use the new providers.
    • Register Service Providers
To register your service provider, you create a provider configuration file, which is stored in the META-INF/services directory of the service provider's JAR file. The name of the configuration file is the fully qualified class name of the service provider, in which each component of the name is separated by a period (.), and nested classes are separated by a dollar sign ($).

The provider configuration file contains the fully qualified class names of your service providers, one name per line. The file must be UTF-8 encoded. Additionally, you can include comments in the file by beginning the comment line with the number sign (#).

For example, to register the service provider GeneralDictionary create a text file named dictionary.spi.Dictionary . This file contains one line:

dictionary.GeneralDictionary

Similarly, to register the service provider ExtendedDictionary create a text file named dictionary.spi.Dictionary . This file contains one line:

dictionary.ExtendedDictionary
    • Create a Client That Uses the Service and Service Providers
Because developing a full word-processor application is a significant undertaking, this tutorial provides a simpler application that uses the DictionaryService and Dictionary SPI. The DictionaryDemo sample searches for the words book, editor, xml, and REST words from any Dictionary providers on the class path and retrieves their definitions.

The following is the DictionaryDemo sample. It requests a definition of the target word from the DictionaryService instance, which passes the request to its known Dictionary providers.

package dictionary;

import dictionary.DictionaryService;

public class DictionaryDemo {

  public static void main(String[] args) {

    DictionaryService dictionary = DictionaryService.getInstance();
    System.out.println(DictionaryDemo.lookup(dictionary, "book"));
    System.out.println(DictionaryDemo.lookup(dictionary, "editor"));
    System.out.println(DictionaryDemo.lookup(dictionary, "xml"));
    System.out.println(DictionaryDemo.lookup(dictionary, "REST"));
  }

  public static String lookup(DictionaryService dictionary, String word) {
    String outputString = word + ": ";
    String definition = dictionary.getDefinition(word);
    if (definition == null) {
      return outputString + "Cannot find definition for this word.";
    } else {
      return outputString + definition;
    }
  }
}

The sample stores the compiled class file in the directory DictionaryDemo/build. Note: You must compile the classes dictionary.DictionaryService and dictionary.spi.Dictionary before the class DictionaryDemo.
    • Package the Service Providers, the Service, and the Service Client in JAR Files
The Java™ Archive (JAR) file format enables you to bundle multiple files into a single archive file. Typically a JAR file contains the class files and auxiliary resources associated with applets and applications.

The JAR file format provides many benefits:

◈ Security: You can digitally sign the contents of a JAR file. Users who recognize your signature can then optionally grant your software security privileges it wouldn't otherwise have.
◈ Decreased download time: If your applet is bundled in a JAR file, the applet's class files and associated resources can be downloaded to a browser in a single HTTP transaction without the need for opening a new connection for each file.
◈ Compression: The JAR format allows you to compress your files for efficient storage.
◈ Packaging for extensions: The extensions framework provides a means by which you can add functionality to the Java core platform, and the JAR file format defines the packaging for extensions. By using the JAR file format, you can turn your software into extensions as well.
◈ Package Sealing: Packages stored in JAR files can be optionally sealed so that the package can enforce version consistency. Sealing a package within a JAR file means that all classes defined in that package must be found in the same JAR file.
◈ Package Versioning: A JAR file can hold data about the files it contains, such as vendor and version information.
◈ Portability: The mechanism for handling JAR files is a standard part of the Java platform's core API.
      • Packaging Service Providers in JAR Files
To package the GeneralDictionary service provider, create a JAR file named GeneralDictionary/dist/GeneralDictionary.jar that contains the compiled class file of this service provider and the configuration file in the following directory structure:
  • META-INF
    • services
      • dictionary.spi.Dictionary
  • dictionary
    • GeneralDictionary.class
Similarly, to package the ExtendedDictionary service provider, create a JAR file named ExtendedDictionary/dist/ExtendedDictionary.jar that contains the compiled class file of this service provider and the configuration file in the following directory structure:
  • META-INF
    • services
      • dictionary.spi.Dictionary
  • dictionary
      • ExtendedDictionary.class
Note that the provider configuration file must be in the directory META-INF/services in the JAR file.
      • Packaging the Dictionary SPI and Dictionary Service in a JAR File
Create a JAR file named DictionaryServiceProvider/dist/DictionaryServiceProvider.jar that contains the following files:
  • dictionary
    • DictionaryService.class
    • spi
      • Dictionary.class
      • Packaging the Client in a JAR File
Create a JAR file named DictionaryDemo/dist/DictionaryDemo.jar that contains the following file:
  • dictionary
    • DictionaryDemo.class
    • Run the Client
The following command runs the DictionaryDemo sample with the GeneralDictionary service provider:

Linux and Solaris:

java -Djava.ext.dirs=../DictionaryServiceProvider/dist:../GeneralDictionary/dist -cp dist/DictionaryDemo.jar dictionary.DictionaryDemo

Windows:

java -Djava.ext.dirs=..\DictionaryServiceProvider\dist;..\GeneralDictionary\dist -cp dist\DictionaryDemo.jar dictionary.DictionaryDemo

When using this command, the following is assumed:
  • The current directory is DictionaryDemo.
  • The following JAR files exist:
    • DictionaryDemo/dist/DictionaryDemo.jar: Contains the DictionaryDemo class
    • DictionaryServiceProvider/dist/DictionaryServiceProvider.jar: Contains the Dictionary SPI and the DictionaryService class
    • GeneralDictionary/dist/GeneralDictionary.jar: Contains the GeneralDictionary service provider and configuration file
The command prints the following:

book: a set of written or printed pages, usually bound with a protective cover
editor: a person who edits
xml: Cannot find definition for this word.
REST: Cannot find definition for this word.
Suppose you run the following command and ExtendedDictionary/dist/ExtendedDictionary.jar exists:

Linux and Solaris:

java -Djava.ext.dirs=../DictionaryServiceProvider/dist:../ExtendedDictionary/dist -cp dist/DictionaryDemo.jar dictionary.DictionaryDemo

Windows:

java -Djava.ext.dirs=..\DictionaryServiceProvider\dist;..\ExtendedDictionary\dist -cp dist\DictionaryDemo.jar dictionary.DictionaryDemo

The command prints the following:

book: Cannot find definition for this word.
editor: Cannot find definition for this word.
xml: a document standard often used in web services, among other things
REST: an architecture style for creating, reading, updating, and deleting data that attempts to use the common vocabulary of the HTTP protocol; Representational State Transfer
  • The ServiceLoader Class
The java.util.ServiceLoader class helps you find, load, and use service providers. It searches for service providers on your application's class path or in your runtime environment's extensions directory. It loads them and enables your application to use the provider's APIs. If you add new providers to the class path or runtime extension directory, the ServiceLoader class finds them. If your application knows the provider interface, it can find and use different implementations of that interface. You can use the first loadable instance of the interface or iterate through all the available interfaces.

The ServiceLoader class is final, which means that you cannot make it a subclass or override its loading algorithms. You cannot, for example, change its algorithm to search for services from a different location.

From the perspective of the ServiceLoader class, all services have a single type, which is usually a single interface or abstract class. The provider itself contains one or more concrete classes that extend the service type with an implementation specific to its purpose. The ServiceLoader class requires that the single exposed provider type has a default constructor, which requires no arguments. This enables the ServiceLoader class to easily instantiate the service providers that it finds.

Providers are located and instantiated on demand. A service loader maintains a cache of the providers that were loaded. Each invocation of the loader's iterator method returns an iterator that first yields all of the elements of the cache, in instantiation order. The service loader then locates and instantiates any new providers, adding each one to the cache in turn. You can clear the provider cache with the reload method.

To create a loader for a specific class, provide the class itself to the load or loadInstalled method. You can use default class loaders or provide your own ClassLoader subclass.

The loadInstalled method searches the runtime environment's extension directory of installed runtime providers. The default extension location is your runtime environment's jre/lib/ext directory. You should use the extension location only for well-known, trusted providers because this location becomes part of the class path for all applications. In this article, providers do not use the extension directory but will instead depend on an application-specific class path.
  • Limitations of the ServiceLoader API
The ServiceLoader API is useful, but it has limitations. For example, it is impossible to derive a class from the ServiceLoader class, so you cannot modify its behavior. You can use custom ClassLoader subclasses to change how classes are found, but ServiceLoader itself cannot be extended. Also, the current ServiceLoader class cannot tell your application when new providers are available at runtime. Additionally, you cannot add change-listeners to the loader to find out whether a new provider was placed into an application-specific extension directory.

The public ServiceLoader API is available in Java SE 6. Although the loader service existed as early as JDK 1.3, the API was private and only available to internal Java runtime code.

2. Making Extensions Secure


Now that you have seen how to use extensions, you may be wondering what security privileges extensions have. If you are developing an extension that does file I/O, for example, you will need to know how your extension is granted the appropriate permissions for reading and writing files. Conversely, if you are thinking about using an extension developed by someone else, you will want to understand clearly what security privileges the extension has and how to change those privileges should you desire to do so.

This lesson shows you how the Java™ platform's security architecture treats extensions. You will see how to tell what privileges are granted to extension software, and you will learn how to modify extension privileges by following some simple steps. In addition, you will learn how to seal packages within your extensions to restrict access to specified parts of your code.

This lesson has two sections:

◈ Setting Privileges for Extensions


If a The 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.*;

public final class RectangleArea {
    public static void
    writeArea(final java.awt.Rectangle r) {
        AccessController.
          doPrivileged(new PrivilegedAction() {
            public Object run() {
                try { 
                    int area = r.width * r.height;
                    String userHome = System.getProperty("user.home");
                    FileWriter fw = new FileWriter( userHome + File.separator
                        + "test" + File.separator + "area.txt");
                    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 area.txt in the test directory under the user's home 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 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.ext.dirs}}/*" {
    permission java.security.AllPermission;
};

This entry specifies that files in the directories specified by file:${{java.ext.dirs}}/* are to be granted the permission called java.security.AllPermission. (Note that as of Java 6, java.ext.dirs refers to a classpath-like path of directories, each of which can hold installed extensions.) 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.AllPermission granted by default. After deleting the default grant entry, you can enter a new grant entry for particular permissions, including:

◈ java.awt.AWTPermission
◈ java.io.FilePermission
◈ java.net.NetPermission
◈ java.util.PropertyPermission
◈ java.lang.reflect.ReflectPermission
◈ java.lang.RuntimePermission
◈ java.security.SecurityPermission
◈ java.io.SerializablePermission
◈ java.net.SocketPermission

The Permissions in the JDK documentation provides details about each of these permissions. Let's look at those needed to use RectangleArea as an extension.

The RectangleArea.writeArea method needs two permissions: one to determine the path to the user's home directory, and the other 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.PropertyPermission "user.home",
        "read";
    permission java.io.FilePermission
        "${user.home}${/}test${/}*", "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 the area.jar. The java.io.PropertyPermission permits access to properties. The first argument, "user.home", names the property, and the second argument, "read", indicates that the property can be read. (The other choice is "write".)

The java.io.FilePermission permits access to files. The first argument, "${user.home}${/}test${/}*", indicates that area.jar is being granted permission to access all files in the test directory that is in the user's home directory. (Note that ${/} is a platform-independent file separator.) 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 Signing JAR Files lesson 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. For example, to use the default keystore used by keytool, add this entry to java.policy

keystore "file://${user.home}/.keystore";

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 the listed 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.PropertyPermission
            "user.home", "read";
        permission java.io.FilePermission
            "${user.home}${/}test${/}*", "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";  
};

◈ 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.)

For a final example, suppose that you wanted to seal all packages except for com.myCompany.package_2. You could accomplish that with a manifest like this:

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.

«« Previous
Next »»