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:
- Part 1. A look at the J2EE Connector Architecture
- Part 2. Build your own J2EE Connector Architecture adapter
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:
- Navigate to the connector tree in the Administration Console
- Click on the "Install new connector" link
- 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(this, null);
}
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:
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:
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:
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:
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:
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:
- Compile the class files by typing
javac *.java
in themyjca
directory - Build the
myjca.jar
from the source directory by enteringjar cvf myjca.jar myjca
- Create the rar file using the
myjca.jar
and theMETA-INF
directory by typingjar 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
You'll see the following output when the adapter acquires the connection:The output above shows the
ManagedConnectionFactory
's creation, which then invoked theManagedConnection
, which in turn created theConnectionEventListener
. Finally, you see that the application server called theConnectionMetaData
.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 theDataSource
:<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>
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.addConnectionEventListenerAs 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