Introduction to Services Framework

In this chapter:

1.      You will be introduced to the Application Service Framework and why is it required.

2.      You will see the underlying Gof patterns in the Services Framework.

3.      You will clearly understand the component discovery mechanism used by the Services Framework.

4.      We will show you the generic template for coding any Application Service by adhering to the component discovery mechanism.

5.      We also present an example: Unique id generation as a sample service and develop an implementation

6.      Finally we cover the disadvantages of having a adapter framework like services on top of actual implementations

 

5.1  Why another Framework?

J2EE is a mature and successful platform for building and deploying enterprise applications. Imagine what would have been the server side Java development state of affairs if J2EE were non-existent. Server side Java development would not be what we see today. Instead there would be a myriad vendors with their proprietary APIs making it extremely difficult for Java developers to learn and program in each of these API sets. Consequently the entire Java developer community would be fragmented into islands isolated and stunted thus making it next to impossible to build serious enterprise applications in Java.

Part of the success of J2EE can be attributed to the simplicity and standardization of its APIs thus reducing the learning curve for server side Java developers. In reality J2EE is mostly a set of interfaces. The J2EE APIs define abstractions on top of several essential features for building applications like transaction control (JTS, Container Managed Transaction), database access (Entity EJBs), web access through various protocols like HTTP (Servlets and JSPs) and Directory Services (JNDI) to name a few. When building your application you actually code to the J2EE API and the vendor application server classes run the show for you at runtime. This is what makes porting of J2EE applications from one application server to another easier if not painless. By coding your application to that interface you prevent vendor locking and protect the coding investment. Another advantage is that it lessens the learning curve and creates a large developer community making serious server side Java development and deployment a reality.

J2EE provides the abstractions at the platform level. We need abstractions at the application level too. Enterprise applications, not just J2EE ones, have several things in common. They access configuration and application data from external sources – be they properties or XML files. They log messages and errors to external source. They validate business data. They need to generate ids to uniquely identify different entities. The list goes on. These are typical examples of Application Services. Simply put, Application Services are abstractions for repeatedly needed facilities in an enterprise development and deployment environment so that developers can code to the defined API rather than locking to a particular implementation. They also go a long way in protecting your coding investment much like their J2EE counterparts.

To further illustrate what we have been saying so far, let us look at the runtime view of a J2EE application. Figure 5.1 shows the runtime view of the API stack for a J2EE application. Let us analyze it from bottom to top. At the bottom of the stack there is the standard Java 2 Standard Edition (J2SE), the base sandbox for any Java 2 application. Vendors build their proprietary implementations of application servers on top of the J2SE platform. Then they wrap it with J2EE APIs by providing Adapters and Bridges (Gof 1) and hide the internal implementation from the external world.  The J2EE API spectrum includes various specifications ranging from Servlet and JSP specification, JTA specification to EJB and JMS specification. As you can see, the J2EE API acts as a layer on top of the vendor implementation hiding the low-level details at the system level. Similarly, Application Services Framework acts as an abstraction on top of myriad implementations of recurring infrastructure tasks at the application level.

 

1Gof refers to the Gang of Four book on patterns and is OO programmer’s bible.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Figure 5.1 Runtime view of API stack in an EAR

 

You as a developer build the J2EE application by adhering to the J2EE APIs. The top grey section in Figure 5.1 is the Enterprise Archive (EAR), the file format for deploying J2EE applications. It consists of two web applications (WAR) and a single EJB application (EJB JAR). In the web applications and the EJB application you would want to log variety of messages. You can perform these tasks by directly invoking methods in the individual implementations. For example you can directly call Log4J methods to log appropriate messages. However this introduces a coupling between your application code and the third party library. If you think this is not too bad, consider what happens when the third party API changes without being backward compatible. You have to go through each of the places in your code where the third party APIs are invoked and change them and also probably test each of these changes. With Log4J, it is definitely a trivial change and a no-brainer. But it may not as trivial as “search and replace” as the Log4J scenario when the coupling is tight and the method invocations are complicated.

A similar situation arises when you migrate from one vendor to another. For instance, J2SE comes with its own logging from JDK 1.4 onwards. If you want to move from Log4J to JDK1.4 Logging, then the changes need to be performed in a lot of places in your code. Again, the changes are easy with Log4J to JDK 1.4 Logging migration. You will have nightmares with anything non-trivial.

A better option is to view tasks such as Logging as a service. The service is defined by an interface. The appropriate implementation component for the service is configured at runtime by the Deployer or Application Assembler2 and is hidden from the application developer. The Application Services Framework shown in dark in Figure 5.1 provides the decoupling between the interface and the actual implementation. It also provides the runtime discovery of the service implementation component.

 

2These are the same roles defined by the J2EE specification.

5.2  A Pattern based approach to Services

There are a lot of open source and commercial implementations available for most of the common infrastructure tasks like XML based data access, logging etc. However their abundance itself poses a problem. Which one do you choose? If you go with choice A today, will it result in vendor locking tomorrow? If you want to switch to choice B in the future what is the implication on your application?  Is it feasible at all because of the tight coupling between the application code and the vendor framework? We will address these questions in this chapter and develop a vendor independent way of accessing application services.

Consider how you might convert XML into Java Objects in your application. You can use DOM, SAX, Castor and JAXB to name a few. Let’s say you chose SAX. Very soon you will find the application code interspersed with SAX callback methods like startElement, endElement and trying to make sense of SAXException. Another developer in your team might very well choose Castor (Castor is an open source framework for converting XML to Java objects). In that case, he will import Castor specific packages in the application code. Even if you standardize across the board on using one approach, you still have the application code that is tied to a specific implementation. In other words you have a rigid system that cannot evolve.

How do you develop a system that can evolve? The answer is simple. By decoupling the abstraction from the implementation. If you can keep the interface to the client program constant and change the implementation class you will never have to change the client program. This very much sounds like an Adapter or Bridge Pattern (Gof). We will use the adapter approach along with a component discovery mechanism as a wrapper to develop the application services in what has become the preferred way of developing services in Java.

JDBC is a good example of service–service provider relationship. The JDBC driver is an instance of Adapter pattern, providing the interface the client expects using the services of class with different interface. The overall application that uses the JDBC driver is an instance of Bridge. By changing the JDBC driver class, we can connect to different databases. The database vendor implements java.sql interfaces and adapts the java.sql interfaces to their proprietary data access implementation. Let us take a look at a small bit of code that acquires a database connection and executes a simple SQL.

 

 

01      DriverManager.registerDriver (“com.foo.JDBCDriver”);

02      Connection con = DriverManager.getConnection (url, user,

03                                                       password);

04      Statement stmt = con.createStatement ();

05      ResultSet rs = stmt.executeQuery (“SELECT * FROM X”);

 

In Line 1 we have registered a driver with the DriverManager class by calling registerDriver method on DriverManager passing the fully qualified class name of the JDBC driver as the parameter. The rest of the lines refer to the interfaces without worrying about the underlying implementation. This code is very portable because it has decoupled the interface from the implementation. However it has a flaw in that the vendor driver is hard coded in the client code. If the adapter represents a service, then we would need to have as many adapters as the service providers. Further, by implementing in this fashion, the client code will have to explicitly refer to the adapter class, which introduces a coupling between the application logic and the actual implementation class by referring to the implementation class by name and is not a good practice.

5.3 Introduction to Component Discovery Mechanism

In the previous section, the database driver was loaded by registering it with the DriverManager as follows:

     DriverManager.registerDriver (“com.foo.JDBCDriver”);

The implementer class name com.foo.JDBCDriver is hard coded in this method invocation. In order to overcome this hard coding, you can specify the fully qualified driver class name in a properties file as DriverClass=com.foo.JDBCDriver and read it in the Java program as follows.

 

01      String propertiesFileName = “driver.properties”;

02      Properties p = new Properties();

03      InputStream is = new FileInputStream(propertiesFileName);

04      p.load(is);

05

06      String driverIdentifierKey = “DriverClass”;

07      String driverClassName = p.get(driverIdentifierKey);

08      DriverManager.registerDriver(driverClassName);

 

In the above code, an InputStream is created from a file named driver.properties residing in the same directory where the JVM is started (Lines 1–3). The file is loaded into java.util.Properties and the key value pairs from that file are available for lookup (Line 4).  Then a lookup is performed for the value of a key named DriverClass (Lines 06-07). The value of this key is the fully qualified class name of the JDBC driver to use. There you go!

A properties file lookup for the driver class name is a slightly better approach than hard coding the driver class name, but it is still addressing the problem at the developer’s level i.e. the development time relationships between the objects.  Some questions still remain unanswered:

§  Every environment is different. The build process might have failed to copy the application properties file to the deployment environment. What will happen in that case?

§  Let us assume that the development environment was Windows and the deployment environment is UNIX. What if the deployer wants to select the JDBC driver using another mechanism? (For instance looking up in a LDAP – although it is far fetched)

      The above two questions point out the fundamental problems encountered when the developer decides the implementation mechanism and the corresponding component. When the implementation component is hard coded, the deployer cannot do much. The solution to this problem is Component Based Development (CBD). CBD is a step ahead from object-oriented development enabling applications to be assembled from components, rather than have a monolithic system. Coming back to our problem – it is clear that we need to have a standard component discovery mechanism to be able to assemble the services from different implementations in a plug and play fashion.

The application code need not (and should not) select the implementation for an interface by either hard coding or from a properties file. If your application is reading a properties file, most likely it is reading application data. Selecting an appropriate component is not the responsibility of the application logic. It belongs to the deployer/application assembler’s domain.

 

The application code should use factory classes to get the implementation component. The factory class should be capable of looking up appropriate implementation for any interface using a standard component discovery mechanism.

 

The above statement summarizes the Component Discovery Mechanism in a nutshell. By following this discipline at the development time, it is possible to build systems that can be assembled from components decoupled from application logic. There is an initial ramp due to the change in the thought process in getting to this mode of programming. Simply put,

 

Application Services Framework is a standard mechanism to develop wrappers (adapters) on top of actual implementations and expose them as service interfaces to the application code.

 

Do not get overboard by applying this mechanism everywhere. Recall that a component is a unit in deployment that consists of collaborating classes behaving cohesively. It does not make sense to apply this mechanism in a cohesive piece of code where interfaces and implementations are all internal to the component. It makes sense to go this route in this scenario since we are dealing with service definition and service implementations. Service Implementations are components to be used by your application code at runtime with no knowledge of it whatsoever at development time.

Before we proceed to the component discovery mechanism details in the next section, let us look at how a developer would use the services. For this purpose we have selected Commons Logging. Although not using similar terminology, it essentially is a working example of how the service provider is hidden from the actual service. Commons Logging is a very simple example of Logging Service from Apache. The actual service provider (implementation) could be Log4J, JDK 1.4 Logging or any other Logging mechanism you choose. However the developer is unaware (and need not know) of the implementation. The appropriate implementation is picked up at the runtime by the magic of component discovery. Commons Logging has its own mechanism of component discovery. To use Commons Logging, the developer codes the following lines

 

01        Log log1 = LogFactory.getLog(“com.foo.bar”);

02        log1.info(“This is FYI”);

03       

04        Log log2 = LogFactory.getLog(“com.foo.error”);

05        log2.error(“This is Error message”);

 

Log is an interface defined in Commons Logging. The developer requests the factory to provide a implementation for this interface. The LogFactory is the factory defined in Commons Logging. It returns an implementation component (service provider) for the Log interface. Using the Log interface, the developer can log any messages. In Lines 1 and 2, a Log named com.foo.bar is obtained to log a message of level INFO. In Lines 4 and 5, a Log named com.foo.error is obtained to log a message of level ERROR.

5.4 Component Discovery Mechanism Details

In the last section, you saw the rational behind the component discovery mechanism and a high level summary of how it works. In this section let us get down to details and cover the actual component discovery mechanism used by the Services. Our component discovery mechanism is adopted from Commons Logging and slightly modified for J2EE. Here is the mechanism in the order of discovery.

 

1.      Look for a predefined system property.  In Commons Logging, the Log implementation in the can be decided by having a system property org.apache.commons.logging.LogFactory. This property can be set at the command line when the JVM or the application server starts up as follows

         -Dorg.apache.commons.logging.LogFactory=

          org.apache.commons.logging.impl.Log4JFactory

In a J2EE environment this option has undesirable effects. Why? Because setting a system property affects all the web applications in that JVM. If you think this is not a problem consider this: In a typical enterprise, an application server might host J2EE applications from multiple departments. If one department takes the liberty of setting the system property to declare Log4J as its Logging system, then it is implicitly imposing the condition that other applications deployed on the same application server use Log4J whether they like it or not (if other applications are also using the same component discovery mechanism).

 

2.      Use the services discovery mechanism from J2SE (Available since JDK 1.3). Here the class loader looks for a file with the name of the service interface under META-INF/services directory within all the jars available for the class loader. If that file is available, the class loader will try to load the class specified in the first line of that file as the implementation of the service interface. We will explain it by using the familiar JAXP API and the Xerces XML Parser library.

For instance, consider the SAXParserFactory interface in Java API for Xml Parsing (JAXP). The fully qualified class name for this interface is javax.xml.parsers.SAXParserFactory. Xerces xml framework from Apache implements JAXP and also provides an implementation for the SAXParserFactory interface. The fully qualified class name of the implementation class is org.apache.xerces.jaxp.SAXParserFactoryImpl. Now recall that the JAXP classes are supplied by Sun and the Xerces is from Apache. How does the Sun’s JAXP implementation discover the implementation class for its own interface? The secret is the JDK1.3 standard services discovery mechanism. The artifact provided by Xerces for its XML implementation is called xerces.jar. As you already know, jar is the standard archive file for distributing libraries in Java. This jar file contains a directory called META-INF. Underneath the META-INF directory there is a subdirectory called services. The services directory contains a file whose name is javax.xml.parsers.SAXParserFactory. The file contains a single line in it with the words org.apache.xerces.jaxp.SAXParserFactoryImpl.  The JAXP implementation always looks for the file with the interface name under META-INF/services and reads the first line it and instantiates that class.

 

3.      Look for a properties file with a predefined name to be available in the classpath. For instance, if the above mechanism to discover an implementation fail, then Commons Logging looks for file named commons-logging.properties in the classpath and then looks for a property to be defined in that file with the name org.apache.commons.logging.LogFactory.

Exercising this option with the properties file in the system classpath is also not a good choice. Reading a property file from the system classpath violates the encapsulation of the EAR/WAR and should be avoided as much as possible.  There are always work-arounds. For example, the properties file can be placed under WEB-INF and accessed through a servlet. There is a better alternative. The property file can be placed under WEB-INF/classes folder in a WAR and it can be accessed as a resource. With this alternative, neither the underlying J2EE principle is violated nor are you stepping on others toes.

NOTE: This approach is slightly different from the properties file access described in Section 5.3. In Section 5.3, the application code directly accesses the driver.properties file to instantiate the driver. Here, the Factory responsible for instantiation of the Service is accessing the properties file. Hence, any change in Factory’s internal implementation does not affect the application code.

 

4.    Look for a JNDI entry with a predefined name in the context of the web application. In J2EE environments, each web application is guaranteed to have its own JNDI context relative to java:comp/env context. This context can be obtained within the web application as follows.

        Context initCtx = new InitialContext();

        Context envCtx = initCtx.lookup(“java:comp/env”);

 

The envCtx can be used to lookup JNDI values local to the web application. The web application can configure the JNDI values by setting the env-entry in the web.xml. The env-entry declared in web.xml is accessible only in the web application where it is declared. A sample env-entry in web.xml is as follows.

     

    <web-app>

       ...

       ...

       <env-entry>

          <description>Sample Description</description>

          <env-entry-name>SampleName</env-entry-name>

          <env-entry-value>SampleValue</env-entry-value>

          <env-entry-type>java.lang.String</env-entry-type>

       </env-entry>

       ...

    </web-app>

 

The env-entry block has an optional description, a name and value for the environment entry and finally, the type of the environment entry. In the above block, the name of the environment entry is SampleName. The value of the SampleName environment entry is SampleValue and it is of type Java String. The value of the SampleName env-entry can be obtained as follows.

 

        Context initCtx = new InitialContext();

        Context envCtx = (Context) initCtx.lookup(“java:comp/env”);

        String value = (String) envCtx.lookup(“SampleName”);

 

The value returned from the lookup is of type Object and has to be cast to the appropriate type. In another web application, the SampleName env-entry can be configured to have another value. The benefit of this is immediately obvious.  The env-entry is providing us with a system properties-like arrangement without affecting other web applications in the process. For instance, if a deployer wants to choose Log4J as the Logging system for the web application he or she is deploying, the following env-entry can be added in the web.xml.

 

         <env-entry>

            <description>

               Fully qualified LogFactory Class Name

            </description>

            <env-entry-name>

               org.apache.commons.logging.LogFactory

            </env-entry-name>

            <env-entry-value>

               org.apache.commons.logging.impl.Log4jFactory

            </env-entry-value>

            <env-entry-type>

               java.lang.String

            </env-entry-type>

         </env-entry>

 

The LogFactory can then use the component discovery mechanism as described earlier and choose Log4J as the implementation component. There is only one glitch! The LogFactory in Commons Logging as it exists today is geared more towards command line systems and has the first three options of component discovery. It is incapable of handling JNDI entries as shown above. In the next chapter, we will develop a “web aware” logging factory, which will lookup the JNDI tree for the service provider in addition to the usual discovery mechanism.

 

     Enough of theory! If you are itching to look at some code, look no further. The next section provides the template and the pseudo code for any service.

5.5 Generic Template for Services

In this section we cover the template used for all the services. Figure 5.2 shows the template class diagram. When we say template, we mean that there is no such concrete class as shown in the Figure 5.2. Each of those classes shown is only a concept and represents the generic pattern that is visible in each and every service defined henceforth.

The GenericService represents a Service interfaces such as Logging or Configuration. The two methods defined on GenericService represent the methods available on the service. For e.g. a Logging Service might have service methods like debug(), info(), warn() and error(). When the service methods fail, they throw a GenericServiceException.

The GenericService is instantiated by the GenericServiceFactory. The Factory contains a HashMap to cache the instantiated services for later use. The services are cached per class loader. All methods on the GenericServiceFactory are static. There is only public method called getService. In an actual service like Logging this method manifests as a method with name getLog. (Remember… we dealt with LogFactory.getLog(..) method earlier in Section 5.3). The rest of the methods defined on the Factory are protected. The pseudo code for the getService method is shown in Listing 5.1.

As stated earlier, developers get the reference to the Service by calling the getService method on the Factory. The getService method works as follows (Refer to Listing 5.1). First, it looks if a cached service is available for the requesting thread’s class loader (Line 9). If it finds one, then it doesn’t look any further and returns the cached service (Lines 11-14). If it cannot find a cached service, then it has to instantiate a new one. The information for new service instantiation is obtained through the component discovery mechanism. Lines 16-33 deal with getting the fully qualified class name about implementation component using the mechanism described in previous section (Section 5.4) and instantiating a new implementation for the context class loader. If no implementation is available, then the factory resorts to a fallback mechanism of instantiating a default implementation component (Lines 36-39). After that the implementation component is cached in the Factory with the context class loader name as the key for future use (Lines 42-45). If at this point no service is available still, it indicates a serious problem in the configuration or something along those lines. A GenericServiceException is thrown to indicate the exceptional condition (Lines 47-50). If everything went well, the caller of the getService method would get an implementation component for the service. Now, that is the gist of what’s going on in the GenericServiceFactory. You can look at downloadable code for the unique id generator service illustrated in the next section to understand what other methods in Listing 5.1 are doing.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Figure 5.2 Generic Template for Services

 

Listing 5.1 getService() method from GenericServiceFactory.

01  public static GenericService getService() throws GenericServiceException

02  {

03     ClassLoader contextClassLoader =

04                   Thread.currentThread().getContextClassLoader();

05

06     /* Check if there is a cached service already

07      * Services are keyed on ClassLoader in the cache

08      */

09     GenericService s = getCachedService(contextClassLoader);

10

11    if (s != null)

12     {

13        return s;

14     }

15

16     //Check if there is a system property for GenericService

17     s = getServiceFromSystemProperty(contextClassLoader);

18

19     if (s == null) //Use JDK1.3 Services Discovery mechanism

20     {

21        s = getServiceByJDK13Discovery(contextClassLoader);

22     }

23

24     //Look for the property in properties file

25     if (s == null)

26     {         

27        s = getServiceFromPropertyFile(contextClassLoader);

28     }

29

30     if (s == null) //Use JNDI Lookup mechanism

31     {

32        s = getServiceFromJNDILookup(contextClassLoader);

33     }

34

35     //Final resort -- Fallback to default implementation

36     if (s == null)  

37     {

38          s = newService(SERVICE_DEFAULT_IMPL, contextClassLoader);

39     }

40   

41     //Cache the implementation class, key on the class loader

42     if (s != null)  

43     {

44        cacheService(contextClassLoader, s);

45     }

46         

47     if (s == null)

48     {

49         throw new GenericServiceException("No Implementation available");

50     }

51

52    return s;

53  }

5.6 Example Service – Unique ID Generator Service

No explanation is complete without an example. In fact valuable insights are gained through examples – beyond what can be explained and understood. In this section you will see a working example of unique id generator as a service - A simplistic service but nonetheless captures the essence of a service.

Figure 5.3 shows the classes involved in Unique ID Generator Service. As you can see, the class diagram is very similar to Figure 5.2, except that the classes here are concrete ones and not just a concept.

Listing 5.2 shows the getUniqueIDGenerator method. Compare this with the generic getService in Listing 5.1. The UniqueIDGenerator represents the Service interface. The getUniqueID is the service method. When the getUniqueID method fails, it throws a UniqueIDGeneratorException. The UniqueIDGenerator is instantiated by the UniqueIDGeneratorFactory. The Factory contains a HashMap to cache the instantiated generators for later use. The generators are cached per class loader. All methods on UniqueIDGeneratorFactory are static. There is only public method called getUniqueIDGenerator. The rest of the methods defined on the Factory are protected.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Figure 5.3 Participating classes in the UniqueIDGenerator Service.

 

Now that we seen the getUniqueIDGenerator method, let us cover other methods used by the getUniqueIDGenerator. The first method to look at is the getCachedGenerator. This method looks for a cached copy of the IUniqueIDGenerator. Every time a new UniqueIDGenerator is instantiated, it is added to a HashMap with the current thread’s ClassLoader as the key. In a J2EE EAR, different class loaders load WARs and EJB-JARs and each of the jars and wars can have their own unique id generators. If such a behavior is not desired, then the implementation can be changed to traverse up the class loader hierarchy to get a unique id generator from one of its parent class loaders.

The methods getGeneratorFromSystemProperty and getGeneratorFromPropertyFile are fairly straightforward and need no explanation. We will cover the getGeneratorByJDK13Discovery instead. Listing 5.3 shows this method in its entirety. This method tries to load a file named abcbank.service.uid.IUniqueIDGenerator from META-INF/services directory (within the jar file if needed) using the class loader for the current thread. If that fails, it tries to load the file using the static getSystemResouceAsStream method in ClassLoader. All of these occur in Lines 8-18. Assuming that one of these approaches succeeds, it opens the file and reads the first line. Since the first line is the fully qualified class name of the implementation component of the interface to be instantiated, it invokes the newGenerator method to do so and then returns back the UniqueIDGenerator implementation (Lines 20-40).

Listing 5.4 shows the newGenerator method. As the name states, this method instantiates a new UniqueIDGenerator. However it takes a two-step approach. First, it tries to instantiate the new class using the class loader of the current thread. If the implementation class is not available to the context class loader due to class loader hierarchy issues, then it uses the bootstrap class loader to achieve the same. For an in-depth understanding of class loader hierarchy in J2EE systems refer to Chapter 1.

 

Listing 5.2 getUniqueIDGenerator() method from UniqueIDGeneratorFactory.

01  public static IUniqueIDGenerator getUniqueIDGenerator() throws

                                        UniqueIDGeneratorException

02  {

03     ClassLoader contextClassLoader =

04                   Thread.currentThread().getContextClassLoader();

05

06     /* Check if there is a cached UniqueIDGenerator already

07      * UniqueIDGenerators are keyed on ClassLoader in the cache

08      */

09     IUniqueIDGenerator s = getCachedGenerator(contextClassLoader);

10

11    if (s != null)

12     {

13        return s;

14     }

15

16     //Check if there is a system property for UniqueIDGenerator

17     s = getGeneratorFromSystemProperty(contextClassLoader);

18

19     if (s == null) //Use JDK1.3 Services Discovery mechanism

20     {

21        s = getGeneratorByJDK13Discovery(contextClassLoader);

22     }

23

24     //Look for the property in properties file

25     if (s == null)

26     {         

27        s = getGeneratorFromPropertyFile(contextClassLoader);

28     }

29

30     if (s == null) //Use JNDI Lookup mechanism

31     {

32        s = getGeneratorFromJNDILookup(contextClassLoader);

33     }

34

35     //Final resort -- Fallback to default implementation

36     if (s == null)  

37     {

38          s = newGenerator(SERVICE_DEFAULT_IMPL, contextClassLoader);

39     }

40   

41     //Cache the implementation class, key on the class loader

42     if (s != null)  

43     {

44        cacheGenerator(contextClassLoader, s);

45     }

46         

47     if (s == null)

48     {

49         throw new UniqueIDGeneratorException("No Impl available");

50     }

51    return s;

52  }

 

Listing 5.3 getGeneratorByJDK13Discovery() method from UniqueIDGeneratorFactory.

01 protected static IUniqueIDGenerator getGeneratorByJDK13Discovery

02                                (ClassLoader contextClassLoader)

03                               throws UniqueIDGeneratorException

04 {

05    IUniqueIDGenerator idgen = null;

06    String service_id =

07            “META-INF/services/abcbank.service.uid.IUniqueIDGenerator”;

08    try

09    {

10       InputStream is = null;

11       if (contextClassLoader != null)

12       {

13          is =  classLoader.getResourceAsStream(name);

14       }

15       else

16       {

17          is = ClassLoader.getSystemResourceAsStream(name);

18       }

19

20       if( is != null )

21       {

22          BufferedReader rd = null;

23          try

24          {

25             rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));

26          }

27          catch (UnsupportedEncodingException uee)

28          {

29             rd = new BufferedReader(new InputStreamReader(is));

30          }

31