52 URL Handlers Service Specification

52.1 Introduction

This specification standardizes the mechanism to extend the Java run-time with new URL schemes and content handlers through bundles. Dynamically extending the URL schemes that are supported in an OSGi framework is a powerful concept.

This specification is necessary because the standard Java mechanisms for extending the URL class with new schemes and different content types is not compatible with the dynamic aspects of an OSGi framework. The registration of a new scheme or content type is a one time only action in Java, and once registered, a scheme or content type can never be revoked. This singleton approach to registration makes the provided mechanism impossible to use by different, independent bundles. Therefore, it is necessary for OSGi Framework implementations to hide this mechanism and provide an alternative mechanism that can be used.

52.1.1 Essentials

  • Multiple Access - Multiple bundles should be allowed to register ContentHandler objects and URLStreamHandler objects.

  • Existing Schemes Availability - Existing schemes in an OSGi framework should not be overridden.

  • life cycle Monitored - The life cycle of bundles must be supported. Scheme handlers and content type handlers must become unavailable when the registering bundle is stopped.

  • Simplicity - Minimal effort should be required for a bundle to provide a new URL scheme or content type handler.

52.1.2 Entities

  • Scheme - An identifier for a specific protocol. For example, "http" is a scheme for the Hyper Text Transfer Protocol. A scheme is implemented in a java.net.URLStreamHandler sub-class.

  • Content Type - An identifier for the type of the content. Content types are usually referred to as MIME types. A content type handler is implemented as a java.net.ContentHandler sub-class.

  • Uniform Resource Locator (URL) - An instance of the java.net.URL class that holds the name of a scheme with enough parameters to identify a resource for that scheme.

  • Factory - An object that creates other objects. The purpose is to hide the implementation types (that may vary) from the caller. The created objects are a subclass/implementation of a specific type.

  • Proxy - The object that is registered with Java and that forwards all calls to the real implementation that is registered with the service registry.

  • java.net.URLStreamHandler - An instance of the java.net.URLStreamHandler class that can create URLConnection objects that represent a connection for a specific protocol.

  • Singleton Operation - An operation that can only be executed once.

  • URLStreamHandlerService - An OSGi service interface that contains the methods of the URLStreamHandler class with public visibility so they can be called from the Framework.

  • AbstractURLStreamHandlerService - An implementation of the URLStreamHandlerService interface that implements the interface's methods by calling the implementation of the super class (java.net.url.URLStreamHandler). This class also handles the setting of the java.net.URL object via the java.net.URLStreamHandlerSetter interface.

  • URLStreamHandlerSetter - An interface needed to abstract the setting of the java.net.URL object. This interface is related to the use of a proxy and security checking.

  • java.net.URLStreamHandlerFactory - A factory, registered with the java.net.URL class, that is used to find java.net.URLStreamHandler objects implementing schemes that are not implemented by the Java environment. Only one java.net.URLStreamHandlerFactory object can be registered with Java.

  • java.net.URLConnection - A connection for a specific, scheme-based protocol. A java.net.URLConnection object is created by a java.net.URLStreamHandler object when the java.net.URL.openConnection method is invoked.

  • java.net.ContentHandler - An object that can convert a stream of bytes to a Java object. The class of this Java object depends on the MIME type of the byte stream.

  • java.net.ContentHandlerFactory - A factory that can extend the set of java.net.ContentHandler objects provided by the java.net.URLConnection class, by creating new ones on demand. Only one java.net.ContentHandlerFactory object can be registered with the java.net.URLConnection class.

  • MIME Type - A namespace for byte stream formats. See [2] MIME Multipurpose Internet Mail Extension.

The following class diagram is surprisingly complex due to the complicated strategy that Java uses to implement extendable stream handlers and content handlers.

Figure 52.1 Class Diagram, java.net (URL and associated classes)

Class Diagram, java.net (URL and associated classes)

52.1.3 Operation

A bundle that can implement a new URL scheme should register a service object under the URLStreamHandlerService interface with the OSGi Framework. This interface contains public versions of the java.net.URLStreamHandler class methods, so that these methods can be called by the proxy (the object that is actually registered with the Java run-time).

The OSGi Framework implementation must make this service object available to the underlying java.net implementation. This must be supported by the OSGi Framework implementation because the java.net.URL.setStreamHandlerFactory method can only be called once, making it impossible to use by bundles that come and go.

Bundles that can convert a content-typed stream should register a service object under the name java.net.ContentHandler. These objects should be made available by the OSGi Framework to the java.net.URLConnection class.

52.2 Factories in java.net

Java provides the java.net.URL class which is used by the OSGi Framework and many of the bundles that run on the OSGi framework. A key benefit of using the URL class is the ease with which a URL string is translated into a request for a resource.

The extensibility of the java.net.URL class allows new schemes (protocols) and content types to be added dynamically using java.net.URLStreamHandlerFactory objects. These new handlers allow existing applications to use new schemes and content types in the same way as the handlers provided by the Java run-time environment. This mechanism is described in the Javadoc for the URLStreamHandler and ContentHandler class.

For example, the URL http://www.osgi.org/sample.txt addresses a file on the OSGi web server that is obtained with the HTTP scheme (usually a scheme provided by the Java run-time). A URL such as rsh://www.acme.com/agent.zip is addressing a ZIP file that can be obtained with the non-built-in RSH scheme. A java.net.URLStreamHandlerFactory object must be registered with the java.net.URL class prior to the successful use of an RSH scheme.

There are several problems with using only the existing Java facilities for extending the handlers used by the java.net.URL class:

  • Factories Are Singleton Operations - One java.net.URLStreamHandlerFactory object can be registered once with the java.net.URL class. Similarly, one java.net.ContentHandlerFactory object can be registered once with the java.net.URLConnection class. It is impossible to undo the registration of a factory or register a replacement factory.

  • Caching Of Schemes - When a previously unused scheme is first used by the java.net.URL class, the java.net.URL class requests a java.net.URLStreamHandler object for that specific scheme from the currently registered java.net.URLStreamHandlerFactory object. A returned java.net.URLStreamHandler object is cached and subsequent requests for that scheme use the same java.net.URLStreamHandler object. This means that once a handler has been constructed for a specific scheme, this handler can no longer be removed, nor replaced, by a new handler for that scheme. This caching is likewise done for java.net.ContentHandler objects.

Both problems impact the OSGi operating model, which allows a bundle to go through different life cycle stages that involve exposing services, removing services, updating code, replacing services provided by one bundle with services from another, etc. The existing Java mechanisms are not compatible when used by bundles.

52.3 Framework Procedures

The OSGi Framework must register a java.net.URLStreamHandlerFactory object and a java.net.ContentHandlerFactory object with the java.net.URL.setURLStreamHandlerFactory and java.net.URLConnection.setContentHandlerFactory methods, respectively.

When these two factories are registered, the OSGi Framework service registry must be tracked for the registration of URLStreamHandlerService services and java.net.ContentHandler services.

A URL Stream Handler Service must be associated with a service registration property named URL_HANDLER_PROTOCOL. The value of this url.handler.protocol property must be an array of scheme names (String[] or String).

A Content Handler service must be associated with a service registration property named URL_CONTENT_MIMETYPE. The value of the URL_CONTENT_MIMETYPE property must be an array of MIME types names (String[] or String) in the form type/subtype. See [2] MIME Multipurpose Internet Mail Extension.

52.3.1 Constructing a Proxy and Handler

When a URL is used with a previously unused scheme, it must query the registered java.net.URLStreamHandlerFactory object (that should have been registered by the OSGi Framework). The OSGi Framework must then search the service registry for services that are registered under URLStreamHandlerService and that match the requested scheme.

If one or more service objects are found, a proxy object must be constructed. A proxy object is necessary because the service object that provides the implementation of the java.net.URLStreamHandler object can become unregistered and Java does not provide a mechanism to withdraw a java.net.URLStreamHandler object once it is returned from a java.net.URLStreamHandlerFactory object.

Once the proxy is created, it must track the service registry for registrations and unregistrations of services matching its associated scheme. The proxy must be associated with the service that matches the scheme and has the highest ranking (first in ranking order) at any moment in time. If a proxy is associated with a URL Stream Handler Service, it must change the associated handler to a newly registered service when that service has a higher ranking.

The proxy object must forward all method requests to the associated URL Stream Handler Service until this service object becomes unregistered.

Once a proxy is created, it cannot be withdrawn because it is cached by the Java run-time. However, service objects can be withdrawn and it is possible for a proxy to exist without an associated URLStreamHandlerService/java.net.ContentHandler object.

In this case, the proxy must handle subsequent requests until another appropriate service is registered. When this happens, the proxy class must handle the error.

In the case of a URL Stream Handler proxy, it must throw a java.net.MalformedURLException exception if the signature of a method allows throwing this exception. Otherwise, a java.lang.IllegalStateException exception is thrown.

In the case of a Content Handler proxy, it must return InputStream to the data.

Bundles must ensure that their URLStreamHandlerService or java.net.ContentHandler service objects throw these exceptions also when they have become unregistered.

Proxies for Content Handler services operate slightly differently from URL Stream Handler Service proxies. In the case that null is returned from the registered ContentHandlerFactory object, the factory will not get another chance to provide a ContentHandler object for that content-type. Thus, if there is no built-in handler, nor a registered handler for this content-type, a ContentHandler proxy must be constructed that returns the InputStream object from the URLConnection object as the content object until a handler is registered.

52.3.2 Built-in Handlers

Implementations of Java provide a number of sub-classes of java.net.URLStreamHandler classes that can handle protocols like HTTP, FTP, NEWS etc. Most Java implementations provide a mechanism to add new handlers that can be found on the class path through class name construction.

If a registered java.net.URLStreamHandlerFactory object returns null for a built-in handler (or one that is available through the class name construction mechanism), it will never be called again for that specific scheme because the Java implementation will use its built-in handler or uses the class name construction.

As a result, even though it is not forbidden for URL Handlers Service implementations to override built-in handlers, it is not possible to guarantee that a registered URLStreamHandlerService object will be used when it is overriding a built-in handler. For consistency reasons, it is therefore recommended to never override built-in handlers.

The Content Handler Factory is implemented using a similar technique and has therefore the same problems.

To facilitate the discovery of built-in handlers that are available through the name construction, the method described in the next section must be used by the Framework before any handlers are searched for in the service registry.

52.3.3 Finding Built-in Handlers

If the system properties java.protocol.handler.pkgs or java.content.handler.pkgs are defined, they must be used to locate built-in handlers. Each property must be defined as a list of package names that are separated by a vertical line ('|' \u007C) and that are searched in the left-to-right order (the names must not end in a full stop ('.' \u002E). For example:

org.osgi.impl.handlers | com.acme.url

The package names are the prefixes that are put in front of a scheme or content type to form a class name that can handle the scheme or content-type.

A URL Stream Handler name for a scheme is formed by appending the string ".Handler" to the scheme name. Using the packages in the previous example, the rsh scheme handler class is searched by the following names:

org.osgi.impl.handlers.rsh.Handler
com.acme.url.rsh.Handler

MIME type names contain the solidus ('/' \u002F) character and can contain other characters that must not be part of a Java class name. A MIME type name must be processed as follows before it can be converted to a class name:

  1. First, all solidi in the MIME name must be converted to a full stop ('.' \u002E). All other characters that are not allowed in a Java class name must be converted to an underscore ('_' \u005F).

    application/zip   application.zip
    text/uri-list     text.uri_list
    image/vnd.dwg     image.vnd_dwg
  2. After this conversion, the name is appended to the list of packages specified in java.content.handler.pkgs. For example, if the content type is application/zip, and the packages are defined as in the previous example, then the following classes are searched:

    org.osgi.impl.handlers.application.zip
    com.acme.url.application.zip

The Java run-time specific packages should be listed in the appropriate properties so that implementations of the URL Stream Handler Factory and Content Handler Factory can be made aware of these packages.

52.3.4 Protected Methods and Proxy

Implementations of java.net.URLStreamHandler class cannot be registered in the service registry for use by the proxy because the methods of the URLStreamHandler class are protected and thus not available to the proxy implementation. Also, the URLStreamHandler class checks that only the URLStreamHandler object that was returned from the URLStreamHandlerFactory object can invoke the setURL method. This means that URLStreamHandler objects in the service registry would be unable to invoke the setURL method. Invoking this method is necessary when implementing the parseURL method.

Therefore, the URLStreamHandlerService and URLStreamHandlerSetter interfaces were created. The URLStreamHandlerService interface provides public versions of the URLStreamHandler methods, except that the setURL method is missing and the parseURL method has a new first argument of type URLStreamHandlerSetter. In general, sub-classes of the URLStreamHandler class can be converted to URLStreamHandlerService classes with minimal code changes. Apart from making the relevant methods public, the parseURL method needs to be changed to invoke the setURL method on the URLStreamHandlerSetter object that the URLStreamHandlerService object was passed, rather then the setURL method of URLStreamHandler class.

Figure 52.2 Proxy Issues

Proxy Issues

To aid in the conversion of URLStreamHandler implementation classes, the AbstractURLStreamHandlerService has been provided. Apart from making the relevant methods public, the AbstractURLStreamHandlerService stores the URLStreamHandlerSetter object in a private variable. To make the setURL method work properly, it overrides the setURL method to invoke the setURL method on the saved URLStreamHandlerSetter object rather then the URLStreamHandler.setURL method. This means that a subclass of URLStreamHandler should be changed to become a sub-class of the AbstractURLStreamHandlerService class and be recompiled.

Normally, the parseURL method will have the following form:

class URLStreamHandlerImpl {
    ...
    protected URLStreamHandlerSetter realHandler;
    ... 
    public void parseURL(
        URLStreamHandlerSetter realHandler,
            URL u, String spec, int start, int limit) {
            this.realHandler = realHandler;
            parseURL(u, spec, start, limit);
    }
    protected void setURL(URL u,
        String protocol, String host,
        int port, String authority,
        String userInfo, String path,
        String query,String ref) {
            realHandler.setURL(u, protocol, host, 
                port, authority, userInfo, path,
                query, ref);
    }
    ...
}

The URLStreamHandler.parseURL method will call the setURL method which must be invoked on the proxy rather than this. That is why the setURL method is overridden to delegate to the URLStreamHandlerSetter object in realHandler as opposed to super.

52.3.5 Stream Handlers that use java.net.Proxy

Java 1.5 introduced a new method on the URLStreamHandler class: URLConnection openConnection(URL,Proxy). Adding this method to the URL Stream Handler service poses the following problems:

  • It would have broken all existing implementations.

  • The references to the java.net.Proxy class would make the API dependent on Java 1.5

Therefore, scheme providers can optionally implement the openConnection(URL,Proxy) method as a public method. If the scheme provider implements this method, then the framework must call it (using reflection). If this method is not implemented in the URL Stream Handler service an Unsupported Operation Exception must be thrown.

Framework implementations should be careful not to create unwanted dependencies on Java 1.5. This will require two different implementation classes for the URLStreamHandler class that is used to proxy the URL Stream Handler services.

52.4 Providing a New Scheme

The following example provides a scheme that returns the path part of the URL. The first class that is implemented is the URLStreamHandlerService. When it is started, it registers itself with the OSGi Framework. The OSGi Framework calls the openConnection method when a new java.net.URLConnection must be created. In this example, a DataConnection object is returned.

public class DataProtocol 
    extends AbstractURLStreamHandlerService         
    implements BundleActivator {
    public void start( BundleContext context ) {
        Hashtable               properties = new Hashtable();
        properties.put( URLConstants.URL_HANDLER_PROTOCOL, 
            new String[] { "data" } );
        context.registerService(
            URLStreamHandlerService.class.getName(), 
            this, properties );
    }
    public void stop( BundleContext context ) {}

    public URLConnection openConnection( URL url ) {
        return new DataConnection(url); 
    }
}

The following example DataConnection class extends java.net.URLConnection and overrides the constructor so that it can provide the URL object to the super class, the connect method, and the getInputStream method. This last method returns the path part of the URL as an java.io.InputStream object.

class DataConnection extends java.net.URLConnection{
    DataConnection( URL url ) {super(url);}
    public void connect() {}
    
    public InputStream getInputStream() throws IOException {
        String s = getURL().getPath();
        byte [] buf = s.getBytes();
        return new ByteArrayInputStream(buf,1,buf.length-1);
    }
    public String getContentType() {
        return "text/plain";
    }   
}

52.5 Providing a Content Handler

A Content Handler should extend the java.net.ContentHandler class and implement the getContent method. This method must get the InputStream object from the java.net.URLConnection parameter object and convert the bytes from this stream to the applicable type. In this example, the MIME type is text/plain and the return object is a String object.

public class TextPlainHandler extends ContentHandler 
    implements BundleActivator {

    public void start( BundleContext context ) {
        Hashtable   properties = new Hashtable();
        properties.put( URLConstants.URL_CONTENT_MIMETYPE, 
            new String[] { "text/plain" } );
        context.registerService(
            ContentHandler.class.getName(), 
            this, properties );
    }
    public void stop( BundleContext context ) {}

    public Object getContent( URLConnection conn ) 
            throws IOException {
        InputStream in = conn.getInputStream();
        InputStreamReader r = new InputStreamReader( in );      
        StringBuffer sb = new StringBuffer();
        int c;
        while ( (c=r.read()) >= 0 )
            sb.append( (char) c );
        r.close(); in.close();
        return sb.toString(); 
    }   
}

52.6 Security Considerations

The ability to specify a protocol and add content handlers makes it possible to directly affect the behavior of a core Java VM class. The java.net.URL class is widely used by network applications and can be used by the OSGi Framework itself.

Therefore, care must be taken when providing the ability to register handlers. The two types of supported handlers are URLStreamHandlerService and java.net.ContentHandler. Only trusted bundles should be allowed to register these services and have ServicePermission[URLStreamHandlerService|ContentHandler, REGISTER] for these classes. Since these services are made available to other bundles through the java.net.URL class and java.net.URLConnection class, it is advisable to deny the use of these services (ServicePermission[<name>, GET]) to all, so that only the Framework can get them. This prevents the circumvention of the permission checks done by the java.net.URL class by using the URLStreamHandlerServices service objects directly.

52.7 org.osgi.service.url

Version 1.0

URL Stream and Content Handlers Package Version 1.0.

Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest.

Example import for consumers using the API in this package:

Import-Package: org.osgi.service.url; version="[1.0,2.0)"

52.7.1 Summary

52.7.2 public abstract class AbstractURLStreamHandlerService
extends URLStreamHandler
implements URLStreamHandlerService

Abstract implementation of the URLStreamHandlerService interface. All the methods simply invoke the corresponding methods on java.net.URLStreamHandler except for parseURL and setURL, which use the URLStreamHandlerSetter parameter. Subclasses of this abstract class should not need to override the setURL and parseURL(URLStreamHandlerSetter,...) methods.

Thread-safe

52.7.2.1 protected volatile URLStreamHandlerSetter realHandler

The URLStreamHandlerSetter object passed to the parseURL method.

52.7.2.2 public AbstractURLStreamHandlerService()

52.7.2.3 public boolean equals(URL u1, URL u2)

This method calls super.equals(URL,URL).

java.net.URLStreamHandler.equals(URL,URL)

52.7.2.4 public int getDefaultPort()

This method calls super.getDefaultPort.

java.net.URLStreamHandler.getDefaultPort

52.7.2.5 public InetAddress getHostAddress(URL u)

This method calls super.getHostAddress.

java.net.URLStreamHandler.getHostAddress

52.7.2.6 public int hashCode(URL u)

This method calls super.hashCode(URL).

java.net.URLStreamHandler.hashCode(URL)

52.7.2.7 public boolean hostsEqual(URL u1, URL u2)

This method calls super.hostsEqual.

java.net.URLStreamHandler.hostsEqual

52.7.2.8 public abstract URLConnection openConnection(URL u) throws IOException

java.net.URLStreamHandler.openConnection

52.7.2.9 public void parseURL(URLStreamHandlerSetter realHandler, URL u, String spec, int start, int limit)

The object on which the setURL method must be invoked for the specified URL.

Parse a URL using the URLStreamHandlerSetter object. This method sets the realHandler field with the specified URLStreamHandlerSetter object and then calls parseURL(URL,String,int,int).

java.net.URLStreamHandler.parseURL

52.7.2.10 public boolean sameFile(URL u1, URL u2)

This method calls super.sameFile.

java.net.URLStreamHandler.sameFile

52.7.2.11 protected void setURL(URL u, String proto, String host, int port, String file, String ref)

This method calls realHandler.setURL(URL,String,String,int,String,String).

java.net.URLStreamHandler.setURL(URL,String,String,int,String,String)

This method is only for compatibility with handlers written for JDK 1.1.

52.7.2.12 protected void setURL(URL u, String proto, String host, int port, String auth, String user, String path, String query, String ref)

This method calls realHandler.setURL(URL,String,String,int,String,String,String,String) .

java.net.URLStreamHandler.setURL(URL,String,String,int,String,String,String,String)

52.7.2.13 public String toExternalForm(URL u)

This method calls super.toExternalForm.

java.net.URLStreamHandler.toExternalForm

52.7.3 public interface URLConstants

Defines standard names for property keys associated with URLStreamHandlerService and java.net.ContentHandler services.

The values associated with these keys are of type java.lang.String[] or java.lang.String, unless otherwise indicated.

Consumers of this API must not implement this type

52.7.3.1 public static final String URL_CONTENT_MIMETYPE = "url.content.mimetype"

Service property naming the MIME types serviced by a java.net.ContentHandler. The property's value is a MIME type or an array of MIME types.

52.7.3.2 public static final String URL_HANDLER_PROTOCOL = "url.handler.protocol"

Service property naming the protocols serviced by a URLStreamHandlerService. The property's value is a protocol name or an array of protocol names.

52.7.4 public interface URLStreamHandlerService

Service interface with public versions of the protected java.net.URLStreamHandler methods.

The important differences between this interface and the URLStreamHandler class are that the setURL method is absent and the parseURL method takes a URLStreamHandlerSetter object as the first argument. Classes implementing this interface must call the setURL method on the URLStreamHandlerSetter object received in the parseURL method instead of URLStreamHandler.setURL to avoid a SecurityException.

AbstractURLStreamHandlerService

Thread-safe

52.7.4.1 public boolean equals(URL u1, URL u2)

java.net.URLStreamHandler.equals(URL, URL)

52.7.4.2 public int getDefaultPort()

java.net.URLStreamHandler.getDefaultPort

52.7.4.3 public InetAddress getHostAddress(URL u)

java.net.URLStreamHandler.getHostAddress

52.7.4.4 public int hashCode(URL u)

java.net.URLStreamHandler.hashCode(URL)

52.7.4.5 public boolean hostsEqual(URL u1, URL u2)

java.net.URLStreamHandler.hostsEqual

52.7.4.6 public URLConnection openConnection(URL u) throws IOException

java.net.URLStreamHandler.openConnection

52.7.4.7 public void parseURL(URLStreamHandlerSetter realHandler, URL u, String spec, int start, int limit)

The object on which setURL must be invoked for this URL.

Parse a URL. This method is called by the URLStreamHandler proxy, instead of java.net.URLStreamHandler.parseURL, passing a URLStreamHandlerSetter object.

java.net.URLStreamHandler.parseURL

52.7.4.8 public boolean sameFile(URL u1, URL u2)

java.net.URLStreamHandler.sameFile

52.7.4.9 public String toExternalForm(URL u)

java.net.URLStreamHandler.toExternalForm

52.7.5 public interface URLStreamHandlerSetter

Interface used by URLStreamHandlerService objects to call the setURL method on the proxy URLStreamHandler object.

Objects of this type are passed to the URLStreamHandlerService.parseURL(URLStreamHandlerSetter, URL, String, int, int) method. Invoking the setURL method on the URLStreamHandlerSetter object will invoke the setURL method on the proxy URLStreamHandler object that is actually registered with java.net.URL for the protocol.

Thread-safe

52.7.5.1 public void setURL(URL u, String protocol, String host, int port, String file, String ref)

java.net.URLStreamHandler.setURL(URL,String,String,int,String,String)

This method is only for compatibility with handlers written for JDK 1.1.

52.7.5.2 public void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref)

java.net.URLStreamHandler.setURL(URL,String,String,int,String,String,String,String)

52.8 References