Connect the enterprise with the JCA, Part 2

In "Connect the Enterprise with the JCA, Part 1," I introduced JCA (J2EE Connector Architecture), explained how it compares to EAI (enterprise application integration) products, and offered details on JCA interfaces.

JCA, an important new Java standard addressing enterprise integration, provides a framework, much like JDBC (Java Database Connectivity), to read and write data to different enterprise systems such as SAP, PeopleSoft, or Siebel. Reflecting its importance, the latest releases of most J2EE (Java 2 Platform, Enterprise Edition) application servers support JCA.

Read the whole series on the JCA:



In this article, the second of two, I demonstrate how to implement a JCA adapter -- a set of classes with which a J2EE application server targets a particular enterprise system. A JCA adapter functions similarly to how a JDBC driver connects to databases. However, because developing a full-featured JCA adapter is a complex task, I can only scratch the surface in this article. Nevertheless, by the end, you will understand a basic JCA adapter's construction, and grasp the effort required to build your own.

To accomplish those goals, I first describe the sample adapter's capabilities, as well as how to deploy and run it. I then introduce the implementation classes, followed by what occurs when the adapter executes in the container. Finally, I discuss the lessons learned from creating the sample adapter.

Note: To download the myjca.rar source code that accompanies this article, see Resources.

What the sample JCA adapter implementation does (and doesn't) contain

First, it's important to frame the sample-adapter discussion by describing its functionality. This article's sample adapter doesn't actually hook up to an enterprise system; it merely implements the interfaces required to deploy the adapter and look up a connection.

Moreover, the sample adapter implements only those classes required for the JCA specification's connection management section. Further, most of the adapter's method implementations contain print statements that let you see the method calls' order, without hooking up a debugger. The sample adapter does not, however, address transaction and security contacts.

This article's sample adapter does not use the CCI (Common Client Interface) interfaces. It strictly demonstrates the classes necessary to connect to an enterprise system.

What you need to deploy the sample adapter

To use the adapter (or any JCA adapter for that matter), you need a J2EE application server with JCA 1.0 specification support. I used BEA's WebLogic 6.1 server to test the sample adapter; however, other application servers should work.

To deploy the sample adapter with BEA's WebLogic 6.1, you must:

  1. Navigate to the connector tree in the Administration Console
  2. Click on the "Install new connector" link
  3. Navigate to the myjca.rar file, then upload it  http://www.javaworld.com/javaworld/jw-02-2002/jca/jw-0201-jca2.zip

In a later section, you'll find instructions on how to compile and build myjca.rar.

The sample class files

Next, let's delve into the Java classes required to implement the sample JCA adapter. The adapter includes two class categories:

  • Managed classes: The application server calls managed classes to perform the connection management. They're needed only if the application server is managing the connection (via a connection pool), a likely situation.
  • Physical connection classes: These required classes, which the aforementioned managed classes may call, establish the connection to the EIS (enterprise information systems).



MyManagedConnectionFactory

With the MyManagedConnectionFactory class, which implements the ManagedConnectionFacytory interface, you create the MyConnectionFactory and MyManagedConnection classes. The MyManagedConnectionFactory class acts as the main entry point for the application server to call into the adapter:

 

 
package myjca;
import java.io.PrintWriter;
import java.io.Serializable;
import java.sql.DriverManager;
import java.util.Iterator;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.spi.*;
import javax.security.auth.Subject;
public class MyManagedConnectionFactory
    
implements ManagedConnectionFactory, Serializable
{
    
public MyManagedConnectionFactory() {
        System.out.println(
"In MyManagedConnectionFactory.constructor");
    }

    
public Object createConnectionFactory(ConnectionManager cxManager) throws ResourceException {
        System.out.println(
"In MyManagedConnectionFactory.createConnectionFactory,1");
        
return new MyDataSource(this, cxManager);
    }

    
public Object createConnectionFactory() throws ResourceException {
              System.out.println(
"In MyManagedConnectionFactory.createManagedFactory,2");
        
return new MyDataSource(thisnull);
    }

    
public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo info) {
        System.out.println(
"In MyManagedConnectionFactory.createManagedConnection");
        
return new MyManagedConnection(this"test");
    }

    
public ManagedConnection matchManagedConnections(Set connectionSet, Subject subject, ConnectionRequestInfo info)
        
throws ResourceException
    
{
        System.out.println(
"In MyManagedConnectionFactory.matchManagedConnections");
        
return null;
    }

    
public void setLogWriter(PrintWriter out) throws ResourceException {
        System.out.println(
"In MyManagedConnectionFactory.setLogWriter");
    }

    
public PrintWriter getLogWriter() throws ResourceException {
        System.out.println(
"In MyManagedConnectionFactory.getLogWriter");
        
return DriverManager.getLogWriter();
    }

    
public boolean equals(Object obj) {
        
if(obj == null)
            
return false;
        
if(obj instanceof MyManagedConnectionFactory)
        
{
            
int hash1 = ((MyManagedConnectionFactory)obj).hashCode();
            
int hash2 = hashCode();
            
return hash1 == hash2;
        }

        
else
        
{
            
return false;
        }

    }

    
public int hashCode()
    
{
              
return 1;
    }

}

MyManagedConnection

The MyManagedConnection class implements the ManagedConnection interface. MyManagedConnection encapsulates the adapter's physical connection, in this case the MyConnection class:

package myjca;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.spi.*;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
public class MyManagedConnection
  
implements ManagedConnection
{
     
private MyConnectionEventListener myListener;
     
private String user;
     
private ManagedConnectionFactory mcf;
     
private PrintWriter logWriter;
     
private boolean destroyed;
     
private Set connectionSet;
  MyManagedConnection(ManagedConnectionFactory mcf, String user)
  
{
    System.out.println(
"In MyManagedConnection");
    
this.mcf = mcf;
    
this.user = user;
    connectionSet 
= new HashSet();
    myListener 
= new MyConnectionEventListener(this);
  }

  
private void throwResourceException(SQLException ex)
    
throws ResourceException
  
{
    ResourceException re 
= new ResourceException("SQLException: " +
ex.getMessage());
    re.setLinkedException(ex);
    
throw re;
  }

  
public Object getConnection(Subject subject, ConnectionRequestInfo
connectionRequestInfo)
    
throws ResourceException
  
{
    System.out.println(
"In MyManagedConnection.getConnection");
    MyConnection myCon 
= new MyConnection(this);
    addMyConnection(myCon);
    
return myCon;
  }

  
public void destroy()
  
{
        System.out.println(
"In MyManagedConnection.destroy");
        destroyed 
= true;
  }

  
public void cleanup()
   
{
        System.out.println(
"In MyManagedConnection.cleanup");
  }

  
public void associateConnection(Object connection)
   
{
        System.out.println(
"In MyManagedConnection.associateConnection");
  }

  
public void addConnectionEventListener(ConnectionEventListener listener)
  
{
        System.out.println(
"In MyManagedConnection.addConnectionEventListener");
    myListener.addConnectorListener(listener);
  }

  
public void removeConnectionEventListener(ConnectionEventListener listener)
  
{
        System.out.println(
"In MyManagedConnection.removeConnectionEventListener");
    myListener.removeConnectorListener(listener);
  }

  
public XAResource getXAResource()
    
throws ResourceException
  
{
        System.out.println(
"In MyManagedConnection.getXAResource");
    
return null;
  }

  
public LocalTransaction getLocalTransaction()
  
{
           System.out.println(
"In MyManagedConnection.getLocalTransaction");
      
return null;
  }

  
public ManagedConnectionMetaData getMetaData()
    
throws ResourceException
  
{
    System.out.println(
"In MyManagedConnection.getMetaData");
    
return new MyConnectionMetaData(this);
  }

  
public void setLogWriter(PrintWriter out)
    
throws ResourceException
  
{
        System.out.println(
"In MyManagedConnection.setLogWriter");
    logWriter 
= out;
  }

  
public PrintWriter getLogWriter()
    
throws ResourceException
  
{
        System.out.println(
"In MyManagedConnection.getLogWriter");
    
return logWriter;
  }

  Connection getMyConnection()
    
throws ResourceException
  
{
        System.out.println(
"In MyManagedConnection.getMyConnection");
    
return null;
  }

  
boolean isDestroyed()
  
{
        System.out.println(
"In MyManagedConnection.isDestroyed");
    
return destroyed;
  }

  String getUserName()
  
{
        System.out.println(
"In MyManagedConnection.getUserName");
    
return user;
  }

  
void sendEvent(int eventType, Exception ex)
  
{
        System.out.println(
"In MyManagedConnection.sendEvent,1");
    myListener.sendEvent(eventType, ex, 
null);
  }

  
void sendEvent(int eventType, Exception ex, Object connectionHandle)
  
{
        System.out.println(
"In MyManagedConnection.sendEvent,2 ");
    myListener.sendEvent(eventType, ex, connectionHandle);
  }

  
void removeMyConnection(MyConnection myCon)
  
{
        System.out.println(
"In MyManagedConnection.removeMyConnection");
    connectionSet.remove(myCon);
  }

  
void addMyConnection(MyConnection myCon)
  
{
        System.out.println(
"In MyManagedConnection.addMyConnection");
    connectionSet.add(myCon);
  }

  ManagedConnectionFactory getManagedConnectionFactory()
  
{
        System.out.println(
"In MyManagedConnection.getManagedConnectionFactory");
    
return mcf;
  }

}

MyConnectionEventListener

For its part, the MyConnectionEventListener class allows the application server to register callbacks for the adapter. The application server can then perform operations, connection-pool maintenance, for example, based on the connection state:

 

package myjca;
import java.util.Vector;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ManagedConnection;
public class MyConnectionEventListener
    
implements javax.sql.ConnectionEventListener
{
    
private Vector listeners;
    
private ManagedConnection mcon;
    
public MyConnectionEventListener(ManagedConnection mcon)
    
{
        System.out.println(
"In MyConnectionEventListener");
        
this.mcon = mcon;
    }

    
public void sendEvent(int eventType, Exception ex, Object connectionHandle)
    
{
        System.out.println(
"In MyConnectionEventListener.sendEvent");
    }

    
public void addConnectorListener(ConnectionEventListener l)
    
{
        System.out.println(
"In MyConnectionEventListener.addConnectorListener");
    }

    
public void removeConnectorListener(ConnectionEventListener l)
    
{
        System.out.println(
"In MyConnectionEventListener.removeConnectorListener");
    }

    
public void connectionClosed(javax.sql.ConnectionEvent connectionevent)
    
{
        System.out.println(
"In MyConnectionEventListener.connectorClosed");
    }

    
public void connectionErrorOccurred(javax.sql.ConnectionEvent event)
    
{
        System.out.println(
"In MyConnectionEventListener.connectorErrorOccurred");
    }

}

MyConnectionMetaData

The MyConnectionMetaData class provides meta information -- product name, the maximum number of connections allowed, and so on -- regarding the managed connection and the underlying physical connection class:

 

package myjca;
import javax.resource.ResourceException;
import javax.resource.spi.*;
public class MyConnectionMetaData
    
implements ManagedConnectionMetaData
{
    
private MyManagedConnection mc;
    
public MyConnectionMetaData(MyManagedConnection mc)
    
{
        System.out.println(
"In MyConnectionMetaData.constructor");
        
this.mc = mc;
    }

    
public String getEISProductName()
        
throws ResourceException
    
{
        System.out.println(
"In MyConnectionMetaData.getEISProductName");
        
return "myJCA";
    }

    
public String getEISProductVersion()
        
throws ResourceException
    
{
        System.out.println(
"In MyConnectionMetaData.getEISProductVersion");
        
return "1.0";
    }

    
public int getMaxConnections()
        
throws ResourceException
    
{
            System.out.println(
"In MyConnectionMetaData.getMaxConnections");
            
return 5;
    }

    
public String getUserName()
        
throws ResourceException
    
{
           
return mc.getUserName();
    }

}

MyConnection

Next up: the MyConnection class, which represents the underlying physical connection to the EIS. MyConnection is one of the few classes that does not implement an interface in the JCA specification. The implementation below is simplistic, but a working implementation might contain connectivity code using sockets, as well as other functionality:

 

package myjca;
public class MyConnection
{
    
private MyManagedConnection mc;
    
public MyConnection(MyManagedConnection mc)
    
{
        System.out.println(
"In MyConnection");
        
this.mc = mc;
    }

}


MyConnectionRequestInfo

The MyConnectionRequestInfo class contains the data (such as the user name, password, and other information) necessary to establish a connection:

package myjca;
import javax.resource.spi.ConnectionRequestInfo;
public class MyConnectionRequestInfo
    
implements ConnectionRequestInfo
{
    
private String user;
    
private String password;
    
public MyConnectionRequestInfo(String user, String password)
    
{
        System.out.println(
"In MyConnectionRequestInfo");
        
this.user = user;
        
this.password = password;
    }

    
public String getUser()
    
{
        System.out.println(
"In MyConnectionRequestInfo.getUser");
        
return user;
    }

    
public String getPassword()
    
{
        System.out.println(
"In MyConnectionRequestInfo.getPassword");
        
return password;
    }

    
public boolean equals(Object obj)
    
{
        System.out.println(
"In MyConnectionRequestInfo.equals");
        
if(obj == null)
            
return false;
        
if(obj instanceof MyConnectionRequestInfo)
        
{
            MyConnectionRequestInfo other 
= (MyConnectionRequestInfo)obj;
            
return isEqual(user, other.user) && isEqual(password, other.password);
        }
 else
        
{
            
return false;
        }

    }

    
public int hashCode()
    
{
        System.out.println(
"In MyConnectionRequestInfo.hashCode");
        String result 
= "" + user + password;
        
return result.hashCode();
    }

    
private boolean isEqual(Object o1, Object o2)
    
{
        System.out.println(
"In MyConnectionRequestInfo.isEqual");
        
if(o1 == null)
            
return o2 == null;
        
else
            
return o1.equals(o2);
    }

}


MyDataSource

The MyDataSource class serves as a connection factory for the underlying connections. Because the sample adapter does not implement the CCI interfaces, it implements the DataSource interface in the javax.sql package:

package myjca;
import java.io.PrintWriter;
import java.io.Serializable;
import java.sql.*;
import javax.naming.Reference;
import javax.resource.Referenceable;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ManagedConnectionFactory;
import javax.sql.DataSource;
public class MyDataSource
    
implements DataSource, Serializable, Referenceable
{
    
private String desc;
    
private ManagedConnectionFactory mcf;
    
private ConnectionManager cm;
    
private Reference reference;
    
public MyDataSource(ManagedConnectionFactory mcf, ConnectionManager cm)
    
{
        System.out.println(
"In MyDataSource");
        
this.mcf = mcf;
        
if(cm == null)
            
this.cm = new MyConnectionManager();
        
else
            
this.cm = cm;
    }

    
public Connection getConnection()
        
throws SQLException
    
{
        System.out.println(
"In MyDataSource.getConnection,1");
        
try
        
{
            
return (Connection)cm.allocateConnection(mcf, null);
        }

        
catch(ResourceException ex)
        
{
            
throw new SQLException(ex.getMessage());
        }

    }

    
public Connection getConnection(String username, String password)
        
throws SQLException
    
{
        System.out.println(
"In MyDataSource.getConnection,2");
        
try
        
{
            javax.resource.spi.ConnectionRequestInfo info 
= new MyConnectionRequestInfo(username, password);
            
return (Connection)cm.allocateConnection(mcf, info);
        }

        
catch(ResourceException ex)
        
{
            
throw new SQLException(ex.getMessage());
        }

    }

    
public int getLoginTimeout()
        
throws SQLException
    
{
        
return DriverManager.getLoginTimeout();
    }

    
public void setLoginTimeout(int seconds)
        
throws SQLException
    
{
        DriverManager.setLoginTimeout(seconds);
    }

    
public PrintWriter getLogWriter()
        
throws SQLException
    
{
        
return DriverManager.getLogWriter();
    }

    
public void setLogWriter(PrintWriter out)
        
throws SQLException
    
{
        DriverManager.setLogWriter(out);
    }

    
public String getDescription()
    
{
        
return desc;
    }

    
public void setDescription(String desc)
    
{
        
this.desc = desc;
    }

    
public void setReference(Reference reference)
    
{
        
this.reference = reference;
    }

    
public Reference getReference()
    
{
        
return reference;
    }

}


Compile and build myjca.rar

Now that you've seen the adapter' source code, it's time to build the myjca.rar file. First, I assume you have a source directory containing two subdirectories: myjca containing the .java files, and META-INF containing the configuration files.

To compile and build the rar file:

  1. Compile the class files by typing javac *.java in the myjca directory
  2. Build the myjca.jar from the source directory by entering jar cvf myjca.jar myjca
  3. Create the rar file using the myjca.jar and the META-INF directory by typing jar cvf myjca.rar myjca.jar META-INF


Deployment output

Once you deploy the adapter rar file (using the steps outlined in the beginning of the article), you should see the output of the println statements contained in most of the adapter's methods. You should see output similar to the following as the adapter deploys:

In MyManagedConnectionFactory.constructor
In MyManagedConnectionFactory.createManagedConnection
In MyManagedConnection
In MyConnectionEventListener
In MyManagedConnection.getMetaData
In MyConnectionMetaData.constructor
In MyConnectionMetaData.getEISProductName
In MyConnectionMetaData.getEISProductVersion
In MyConnectionMetaData.getMaxConnections

The output above shows the ManagedConnectionFactory's creation, which then invoked the ManagedConnection, which in turn created the ConnectionEventListener. Finally, you see that the application server called the ConnectionMetaData.

Get a connection

Now that you've deployed the adapter successfully, let's use the adapter to obtain a connection. The following JSP (JavaServer Pages) file does just that, by looking up the connection using JNDI (Java Naming and Directory Interface), then calling the getConnection() method on the DataSource:

<html>
<head>
       
<title>Connector example</title>
</head>
<body bgcolor=#ffffff>
<%@ page import="javax.naming.*, java.sql.*, javax.sql.*" %>
<% 
    InitialContext initCtx 
= null;
    
Object obj = null;
    try {
      initCtx 
= new InitialContext();
      DataSource ds 
= (javax.sql.DataSource)
      initCtx.lookup(
"MyConnector");      
      obj 
= ds.getConnection();
    } catch(NamingException ne) {
        System.out.println(
"Error with context: " + ne);    
    }
%>
<h2>Performed a lookup and got a connection</h2>
</body>
</html> 

You'll see the following output when the adapter acquires the connection:
In MyManagedConnectionFactory.createConnectionFactory,1
In MyDataSource
In MyDataSource.getConnection,1
In MyManagedConnectionFactory.matchManagedConnections
In MyManagedConnectionFactory.createManagedConnection
In MyManagedConnection
In MyConnectionEventListener
In MyManagedConnection.getMetaData
In MyConnectionMetaData.constructor
In MyConnectionMetaData.getEISProductName
In MyConnectionMetaData.getEISProductVersion
In MyConnectionMetaData.getMaxConnections
In MyManagedConnection.getUserName
In MyManagedConnection.getConnection
In MyConnection
In MyManagedConnection.addMyConnection
In MyManagedConnection.addConnectionEventListener
 

As you can see, many classes help obtain the connection.

Lessons learned

As you've likely figured out, the JCA specification's complexity makes implementing even a basic adapter a large undertaking. Moreover, the task grows larger when you add the transaction and security contracts (not implemented for this sample adapter), as well as the CCI interfaces.

The complexity shows that the JCA specification is really oriented towards commercial software vendors implementing adapters and their consulting/IT customers using them. In this context, the JCA interfaces' complexity makes sense, although a less flexible, simpler interface version would be nice.

Therefore, if you are considering using JCA to connect to a legacy system in your enterprise, you'd benefit from leveraging an off-the-shelf adapter rather than developing your own. If the system to which you need connectivity does not have a JCA adapter (that is, for a homegrown system), consider an alternative approach. Using Web services to connect to such a system may offer the best solution.

For its part, while JCA is still a new standard, it shows promise for making the task of integrating with an EIS less daunting.

Author Bio

Dirk Reinshagen, an architect at Zaplet, Inc., a commercial software vendor in the San Francisco Bay Area, has more than eight years of software architecture and development experience. He holds a B.S. in computer science from Tufts University.
 
摘自:http://www.javaworld.com/javaworld/jw-02-2002/jw-0201-jca2.html?page=1
posted @ 2006-10-30 13:19  Rookie.Zhang  阅读(363)  评论(0编辑  收藏  举报