The Service Discovery Package


The Service Discovery Package

Within the Commons library is a library called Service Discovery , or Discovery for short. The purpose of the Service Discovery package is defined as follows (source: Discovery Proposal): The Discovery package is a very formalized approach to instantiating an object. In Java, you can use the Service Discovery technique built into the JDK. The Discovery package enhances the basic technique into a full-blown configurable service provider interface, and users of this package should keep this in mind. It is not easier to instantiate objects using the Discovery package than using the factory. However, the Discovery package makes it easier for a developer to develop an application that is configured by an administrator.

Loading a Plug-in

Imagine building a dynamic Web Server, meaning that it will support the dynamic loading of components to serve requests. To serve requests , you would use a configuration file that would load the mappings. Listing 3.19 shows a sample mapping.

Listing 3.19
start example
 <mappings> <request uri="/home" class="com.devspace.jseng.create.ObjectToBeShared" /> <request uri="/user" class="com.devspace.jseng.create.AnotherObjectToBeCreated" /> </mappings> 
end example
 
start sidebar
Rationale

The Discovery Component is about discovering, or finding, implementations for pluggable interfaces. It provides facilities instantiating classes in general, and for lifecycle management of singleton (factory) classes.

Fundamentally, Discovery locates classes that implement a given Java interface. The discovery pattern, though not necessarily this package, is used in many projects including JAXP (SaxParserFactory and others) and commons-logging (LogFactory). By extracting this pattern, other projects can (re)use it and take advantage of improvements to the pattern as Discovery evolves.

Discovery improves over previous implementations by establishing facilities for working within managed environments. These allow configuration and property overrides without appealing to the global System properties (which are scoped across an entire JVM)."

end sidebar
 

In Listing 3.19, there are two XML tags that contain a mapping of a request to a Java class name . When the request /home is received, the class com.devspace.jseng.create. ObjectToBeShared will be instantiated and called. The process of instantiating the class is what the Discovery package does. The details of loading and parsing this configuration will be discussed in Chapter 8. In the actual implementation, the Web Server will not use the class directly, but rather an interface that both classes in Listing 3.19 implement.

In simplest terms, you would think that the Discovery package is absolute overkill because doing a dynamic class load in Java is a relatively simple operation. However, there is another situation to consider. Imagine that the Web server loads both classes defined in the mapping of Listing 3.19. The classes loaded by the request /home load version 1.0 of the class libraries package xyz . However, the classes loaded by the request /request load version 1.2 of the class libraries package xyz . We have a dilemma because there are two versions of the same library. The class loader at this point becomes very complicated. This is where the Discovery package helps you. The Discovery package does not create the different class loaders per se, but it helps you set up the class loaders and then will automatically manage and load the right class.

Let's finish off Listing 3.19; the way to load and instantiate the class is shown in Listing 3.20.

Listing 3.20
start example
 import  org.apache.commons.discovery.tools.DiscoverClass; DiscoverClass discoverClass = new DiscoverClass(); InterfaceToBeShared interf = (InterfaceToBeShared)discoverClass.newInstance( InterfaceToBeShared.class, "com.devspace.jseng.create.ObjectToBeCreated"); interf.availability(); 
end example
 

In Listing 3.20, the utility class DiscoverClass makes it simpler to use the Discovery package. The method newInstance is used to instantiate a class. The type is determined by the first parameter InterfaceToBeShared.class . However, since the type is an inter- face, it cannot be instantiated directly. The second parameter, " com.devspace.jseng. create.ObjectToBeCreated ", is the default class that will be instantiated if another implementation cannot be found. Since Listing 3.20 is simple, the default class will be instantiated. Once the class has been instantiated, the returned object is type cast to the interface InterfaceToBeShared . Finally, the interface method interf.availability can be called just like another interface instance.

Technical Details for the Discovery Package

Tables 3.3 and 3.4 contain an abbreviated details necessary to use the Discovery package.

Table 3.3: Repository details for the Discovery package.

Item

Details

CVS repository

jakarta-commons

Directory within repository

discovery

Main packages used

org.apache.commons.discovery, org.apache.commons.discovery.resource, org.apache.commons.discovery.tools; org.apache.commons.discovery.jdk (optional)

Table 3.4: Package and class details (legend: [discovery] = org.apache.commons.discovery).

Class/Interface

Details

[discovery].tools.DiscoverClass

A utility class used to find a specific service that implements a specific interface.

[discovery].tools.Singleton

A utility class used to find a specific service that implements a specific interface, except that the returned instance will be a singleton service.

[discovery].tools.ClassUtils

A utility class used to help find out specific attributes regarding a class, such as package name.

[discovery].tools.Properties

A class used by the Discovery library to store certain service definitions and their respective implementation.

[discovery].tools.ManagedProperties

A class used by the Discovery library to store certain service definitions and their respective implementation except that the class is a static global class.

[discovery].resource.ClassLoaders

A class used to store and reference a specific set of class loaders that can be created at runtime. The combination of class loaders managed by the ClassLoaders class can be reused for later class instantiations .

[discovery].ResourceClass

A class that contains a reference to the service class found by the Discovery framework.

[discovery].ResourceClassIterator

A class used to iterate the ResourceClasses that are found.

[discovery].resource.DiscoverResources

A class used to find a specific set of resources that is helpful for finding service descriptors.

[discovery].Resource

A class used to encapsulate a found resource.

[discovery].ResourceIterator

A class used to iterate the Resources found.

[discovery].jdk.JDKHooks

A very useful utility class that encapsulates the class loading functionality of the JDK.

Using Service Descriptors

Listing 3.20 showed how to instantiate and find a service based on a default object. Listing 3.21 shows you how to use service descriptors to find and load a class.

Listing 3.21
start example
 org.apache.commons.discovery.log.SimpleLog.setLevel(2); DiscoverClass discover = new DiscoverClass(); Class implClass = discover.find(InterfaceToBeShared.class); 
end example
 

As in Listing 3.20, the DiscoverClass in Listing 3.21 is instantiated. This time, though, instead of instantiating a new instance of the interface InterfaceToBeShared , the class descriptor is located. The method find returns an object of type Class , which can be used to instantiate a class instance. In this case, the class Class serves the same purpose as the interface Factory we saw in the Lang Factory section of this chapter. Listing 3.21 assumes that certain configuration items are defined, which will be explained in later sections.

If you run Listing 3.21 as is, an error will occur. You can find the cause of the error in the output that is generated by the method call SimpleLog.setLevel . The debug code that is generated is shown in Listing 3.22.

Listing 3.22
start example
 [DEBUG] DiscoveryLogFactory - -Class meets requirements: org.apache.commons.discovery.tools.ClassUtils [DEBUG] DiscoveryLogFactory - -Class meets requirements:  org.apache.commons.discovery.resource.names.DiscoverNamesInFile [DEBUG] DiscoveryLogFactory - -Class meets requirements:  org.apache.commons.discovery.resource.DiscoverResources [DEBUG] DiscoverNamesInFile - -find: fileName='META- INF/services/com.devspace.jseng.create.InterfaceToBeShared' [DEBUG] DiscoverResources - -find:  resourceName='com.devspace.jseng.create.InterfaceToBeShared' [DEBUG] DiscoveryLogFactory - -Class meets requirements: org.apache.commons.discovery.resource.classes.DiscoverClasses [DEBUG] DiscoverResources - -getNextResources: search using ClassLoader 'sun.misc.Launcher$AppClassLoader@4b222f' [DEBUG] DiscoverResources - -getNextResources: search using ClassLoader 'sun.misc.Launcher$AppClassLoader@4b222f' [DEBUG] DiscoverResources - -getNextResources: search using ClassLoader 'sun.misc.Launcher$AppClassLoader@4b222f' [DEBUG] DiscoverResources - -getNextResources: search using ClassLoader 'sun.misc.Launcher$AppClassLoader@4b222f' 
end example
 

In Listing 3.22, find the section DiscoverNamesInFile and notice the filename, which is META-INF/services/com.devspace.jseng.create.InterfaceToBeShared . This is a rather odd name for a class that needs to be loaded. At first, it would seem that the compiled class file for the interface InterfaceToBeShared needs to be stored underneath the META-INF/services directory. However, that impression is incorrect because the class name is in fact a real filename.

Earlier in this chapter we mentioned the idea of a configuration stored in XML. At that time, we didn't worry about that file. However, in this specific case, we need to concern ourselves with it because the file that is to be searched for is a file that contains a reference to a class that implements the interface InterfaceToBeShared . In this case, the name of the file is com.devspace.jseng.create.InterfaceToBeShared, the dots should not be replaced with subdirectories. Listing 3.23 shows a sample implementation of the file.

Listing 3.23
start example
 # comment com.devspace.jseng.create.ObjectToBeCreated 
end example
 

In Listing 3.23, a single line identifies the class that implements the interface InterfaceToBeShared . This time when Listing 3.21 is executed, the class ObjectToBeCreated is instantiated and loaded into the class loader. It is possible to put multiple service implementations into the file in Listing 3.23. In a multiple service implementation scenario, only the first line is used to load a class.

Using Properties

The service can be stored as settings in a configuration file. However, when the data is in memory, knowing where to store the data can be more complicated. In the Discovery package, the individual interfaces and their service providers could be stored as a property. There are two property storage techniques. The local storage mechanism is to the use the Java common library class Properties . The global storage mechanism is to use the Discovery package class ManagedProperties . An example of using the class Properties is shown in Listing 3.24.

Listing 3.24
start example
 DiscoverClass discover = new DiscoverClass(); Properties props = new Properties(); props.setProperty( InterfaceToBeShared.class.getName(), ObjectToBeCreated.class.getName()); String found[] = discover.discoverClassNames( new SPInterface(InterfaceToBeShared.class), props); if( found != null) { for( int c1 = 0; c1 < found.length; c1 ++) { System.out.println( "Found " + found[ c1]); } } 
end example
 

In Listing 3.24, the class Properties is used to store the name of the interface and the name of the class that provides the service for the interface. Both the interface and class identifier are stored as strings. Therefore, if you introduce a typo in either the interface or class identifier, the Discovery package will not work as intended.

Once the class Properties has been populated using the method setProperty , a search is performed. The search is the method discoverClassNames , which searches for the name of the services that implement a specific interface. For the method discoverClassNames, the first parameter is the class SPInterface , which references the class identifier of the interface to be found. The class SPInterface is a placeholder class for the service implementation class descriptor. In the documentation, the class SPInterface is short for " Service Provider Interface." Part of the purpose of this class is to store the descriptors about the constructor and its parameters. The second parameter of the method discoverClassNames is the instance of the class Properties . The second parameter does not need to be an actual value and could be substituted with a null value. The method discoverClassNames returns a list of registered service providers in string form.

This then raises another question, because the class Properties is based on the class Hashtable , meaning that there can be only one key value combination. In the case of Listing 3.24, the key value combination is the interface and the service that implements the interface. This means that if a second service implements the same interface, the second service identifier will overwrite the first service identifier. While this behavior is not unintended , it's questionable that it's possible to return an array of service identifiers for a specific interface, as in the case of discoverClassNames . The truth lies in the implementation of discoverClassNames , which searches multiple places for service providers. An example of another place to search is the global properties represented by the class ManagedProperties . Listing 3.25 is an example of setting a key value combination in the class ManagedProperties .

Listing 3.25
start example
 DiscoverClass discover = new DiscoverClass(); Properties props = new Properties(); props.setProperty( InterfaceToBeShared.class.getName(), ObjectToBeCreated.class.getName()); ManagedProperties.setProperty(  InterfaceToBeShared.class.getName(),  AnotherObjectToBeCreated.class.getName()); String found[] = discover.discoverClassNames( new SPInterface(InterfaceToBeShared.class), props); if( found != null) { for( int c1 = 0; c1 < found.length; c1 ++) { System.out.println( "Found " + found[ c1]); } } 
end example
 

The code in Listing 3.25 is very similar to that in Listing 3.24, except the additional class method call ManagedProperties.setProperty . The class ManagedProperties is essentially a class containing only static methods and data members . In Listing 3.25, you set the key value combination using the same technique as in the class Properties example (see Listing 3.24). However, if the code in Listing 3.25 were now executed, the method discoverClassNames would return two services that implement the same interface. It would then be up to the programmer to decide which service to use. It is important to realize that the class ManagedProperties manages global properties that are based on the thread context class loader. This means that a class loader for one thread may or may not be the same class loader for another thread. It depends on the application's runtime configuration.

Low-Level Resource Discovery

Thus far, all of the examples have been used to discover specific services that implement specific interfaces. However, within the Discovery package are several low-level classes that allow you to discover the specific files manually. Imagine that a Web Server is serving requests to multiple clients on multiple domains. Typically, different Web site administrators will manage the multiple domains. There will also be an administrator managing both domains. The problem will be when the Web site administrators want to update their respective Web sites. Do the administrators need to be informed of every change and add the items in a configuration file? This approach would require a constant update of the Java execution environment and Java classpath. The administrators would want a facility to find a specific resource within the Java classpath that can be used to carry out some other action. The resource to find could be a service descriptor file or Java class file. Once the service descriptor file has been found, further actions, such as expanding the jar file, may be carried out. Listing 3.26 shows an example of this type of resource location.

Listing 3.26
start example
 ClassLoaders loaders = new ClassLoaders(); ClassLoader cl = getClass().getClassLoader(); if( cl != null) loaders.put(getClass().getClassLoader(), true); else loaders.put( JDKHooks.getJDKHooks().getSystemClassLoader(), true); String name = "build.xml"; DiscoverResources discovery = new  DiscoverResources(loaders); ResourceIterator iter = discovery.findResources(name); while( iter.hasNext()) { Resource resource = iter.nextResource(); URL url = resource.getResource(); if ( url != null ) { System.out.println("URL = " + url.toString()); } } 
end example
 

In Listing 3.26, there are two very important parts to identify. The first part is the how the various class loaders are referenced and added to the class loader collection. The second part is the iteration of the discovered resources. In the first part, the class loaders are referenced using two different techniques. The first technique is to use the Java-defined method getClass().getClassLoader() . The second technique is to use the class JDKHooks to retrieve the system class loader using the method getSystemClassLoader . The class JDKHooks is a global JDK helper class that you can use to retrieve the different class loaders. It is a useful helper class because the messiness of using the JDK class loader methods is avoided.

Once the individual class loaders have been referenced and added to the list managed by the class ClassLoaders, you can q uery for a resource. You can use the class DiscoverResources to query for the resources. The constructor of the class DiscoverResources accepts as a parameter a collection of class loaders, as we saw in Listing 3.26. When the method findResources is called, the list of class loaders is searched. The method findResources has a single parameter that represents the resource to be found. The resources that are found are iterators that use the class ResourceIterator . We'll discuss iterators in more detail in Chapter 7.

Once a resource has been found, the description of the resource is stored in the class Resource . Various pieces of information can be retrieved from the class Resource . Listing 3.26 shows the Uniform Resource Locator (URL) or location of the resource. In addition, you can discover the name of the resource or load the resource as an InputStream by using the method getResourceAsStream . It's useful to convert the resource to a stream if the resource is a configuration file that needs additional parsing.

If the resource to be found is a class file, then a variation of the query is to query for an individual class. The entire process of setting up the query by defining the class loaders and then querying is virtually identical in both cases. The difference is that you use the class DiscoverClasses instead of DiscoverResources . Listing 3.27 shows an example of this.

Listing 3.27
start example
 ClassLoaders loaders = ClassLoaders.getAppLoaders( InterfaceToBeShared.class, getClass(), false); String name = "com.devspace.jseng.create.InterfaceToBeShared"; DiscoverClasses discovery = new  DiscoverClasses(loaders); ResourceClassIterator iter=  discovery.findResourceClasses(name); while (iter.hasNext()) { ResourceClass resource = iter.nextResourceClass(); try { Class implClass = resource.loadClass(); if ( implClass != null ) { System.out.println( "Resource is " + implClass.getName()); return;  } } catch (Exception e) { throw new RuntimeException(  "Could not load service: " + resource ); } } 
end example
 

The difference between Listings 3.26 and 3.27 is that Listing 3.27 uses the class DiscoverClasses instead of DiscoverResources. Notice, though, in Listing 3.27 how the class name is specified as a Java complete name. In contrast, the class DiscoverResources does not use a class name, but a filename. Listing 3.27 when executed will generate output similar to Listing 3.28, which contains the names of the classes that were searched for.

Listing 3.28
start example
 [java] [DEBUG] DiscoverClasses - -find:  className='com.devspace.jseng.create.InterfaceToBeShared' [java] [DEBUG] DiscoverClasses - -getNextClass: next  URL='jar:file:/D:/mydocs/book/bookappliedpatterns/src/jseng.jar!/com/devspace/jseng/create/InterfaceToBeShared.class' 
end example
 

Notice that the found file has the class extension to indicate that the found item is a class, which was not specified when we assigned the variable name in Listing 3.27. Of course, this may seem logical since in Listing 3.26 the resource to be found is a file and in Listing 3.27 the resource to be found is a class. However, if a developer chose to find the class file using the class DiscoverResources, then he would have to use the query string "InterfaceToBeShared.class".

When you are querying for a class name, you need to remember several specifics. Using the class DiscoverResources to query for a class name requires that the developer add the package details. For example, the package com.devspace.jseng.create would have to be converted to com/devspace/jseng/create . Trying to search for a class name without a package identifier will not work, although you can find a regular file without a directory specifier . The directories for a class file have a special purpose. When you're attempting to find a class using the DiscoverClasses class, it is important to use the package name; otherwise , you will not be able to find the class.

Using the Class JDKHooks

The class JDKHooks is part of the Discovery package because it is needed to make some things in the Java environment simpler. It is not the most interesting class, but knowing what it can do is useful in the odd situation. The essence of the class JDKHooks is to make it simpler to get the thread context and system context class loaders. This is very useful when you are manually loading classes based on specific class paths. The class JDKHooks is a static class within the JDKHooks class definition. To retrieve the instance, you use the static method JDKHooks.getJDKHooks . You need an accessor because the class JDKHooks is an abstract class that has two private implementations. This type of architecture is identical to that of the Commons Bridge, defined in Chapter 2. The two private implementations are for JDK 1.1 and JDK 1.2.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net