Java EE consists of many standards: JSP, servlet, EJB, JCA, JSF etc..
Most Java EE developers know all of the standards listed above with one exception: JCA. Only a small fraction of Java EE developers know about JCA.
Which is sort of a pity since JCA can be a good solution for some special cases. And it is therefor useful if a Java EE developer has a basic understanding of JCA.
My article Modern Java EE also only covers JSP, servlet, EJB and JSF.
This article should help fix that.
JCA (Java Connector Architecture) is the Java EE standard for how Java EE applications can interact with other systems in a non-standard way.
JCA 1.0 was introduced in J2EE 1.4 back in 2003.
JCA adapters come in two flavors:
Outbound adapter:
Inbound adapter:
(EIS = Enterprise Information System)
JCA 1.0 only had outbound adapters. Inbound adapters was added to JCA 1.5 that came with Java EE 5 in 2006.
This article covers JCA inbound adapters.
For JCA inbound adapters see article JCA 2 - Inbound adapters.
All examples will have the following logic:
The example is of course absurd trivial, but that just mean that the business logic will not distract from the topic of this article.
The examples has been tested on WildFly 10 (JBoss).
6 different outbound adapters will be shown:
Mockup server classes:
package jcademo.mockup;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MulTCP {
public static void main(String[] args) throws IOException {
// listen port
ServerSocket ss = new ServerSocket(20001);
while(true) {
// accept connections
Socket s = ss.accept();
// start handler thread
new Thread() {
public void run() {
try {
// open
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
PrintStream ps = new PrintStream(s.getOutputStream(), true, "UTF-8");
// read and parse line with numbers
String line;
while((line = br.readLine()) != null) {
String[] parts = line.split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
// calculate and write result
int c = a + b;
ps.println(c);
ps.flush();
}
// close
ps.close();
br.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
//ss.close();
}
}
package jcademo.mockup;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCP {
public static void main(String[] args) throws IOException {
// listen port
ServerSocket ss = new ServerSocket(20002);
while(true) {
// accept connections
Socket s = ss.accept();
// start handler thread
new Thread() {
public void run() {
try {
// open
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
PrintStream ps = new PrintStream(s.getOutputStream(), true, "UTF-8");
// read and parse line with numbers
String line = br.readLine();
String[] parts = line.split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
// calculate and write result
int c = a + b;
ps.println(c);
ps.flush();
// close
ps.close();
br.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
//ss.close();
}
}
package jcademo.mockup;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class PermTCP {
public static void main(String[] args) throws IOException {
// listen port
ServerSocket ss = new ServerSocket(20003);
while(true) {
// accept connections
Socket s = ss.accept();
try {
// open
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
PrintStream ps = new PrintStream(s.getOutputStream(), true, "UTF-8");
while(true) {
// read id, read and parse line with numbers
String id = br.readLine();
if(id == null) break;
String line = br.readLine();
if(line == null) break;
String[] parts = line.split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
// calculate and write result
int c = a + b;
ps.println(id);
ps.println(c);
ps.flush();
}
// close
ps.close();
br.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//ss.close();
}
}
package jcademo.mockup;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDP {
public static void main(String[] args) throws IOException {
// open datagram socket
DatagramSocket dgs = new DatagramSocket(2004);
// receive datagram with id and line with numbers
byte[] b1 = new byte[1000];
DatagramPacket dp1 = new DatagramPacket(b1, b1.length);
dgs.receive(dp1);
String text = new String(dp1.getData(), "UTF-8");
// parse id and numbers
String[] textparts = text.split("\r\n");
String id = textparts[0];
String line = textparts[1];
String[] lineparts = line.split(" ");
int a = Integer.parseInt(lineparts[0]);
int b = Integer.parseInt(lineparts[1]);
// calculate result
int c = a + b;
// send datagram with id and result
String s2 = id + "\r\n" + c;
byte[] b2 = s2.getBytes("UTF-8");
DatagramPacket dp2 = new DatagramPacket(b2, b2.length);
dp2.setAddress(dp1.getAddress());
dp2.setPort(dp1.getPort());
dgs.send(dp2);
// close
dgs.close();
}
}
Examples with:
will not be shown.
HTTP can be done using Apache HTTP Client.
For JMS see Java examples in this article.
For remote EJB call examples see:
JCA outbound adapters are really a generalization of database connections and database connection pool.
A database connection pool works with pooled database connections that forward to managed database connections, where the managed database connections are allocated and deallocated from the pool and temporarily associated with a pooled connection.
A JCA outbound adapter with a connection manager works with pooled connections that forward to managed connections, where the managed database connections are allocated and deallocated from the pool and temporarily associated with a pooled connection.
One obvious question is: why use a JCA outbound adapter instead of just having the business logic establish a direct connection when needed and close it again when done?
There are at least 4 potential reasons for that:
For a quick intro to database connection pools see here.
Note that an outbound adapter can be accessed via two interfaces:
The CCI interface seemed smart 15 years ago. But today it just seems like an unnecessary complication. So I will recommend not to use CCI.
To create a resource adapter you need to:
The connection interface is just a simple interface defining business action methods.
The connection implementation class implements the connection interface but just forward all business methods to the managaed connection.
The connection factory interface is just a simple interface defining how to get a connection.
The connection factory implementation class implements the connection factory interface and use the connection manager to get a connection with associated managed connection.
The managed connection class implemeneds ManagedConnection and does the actual work.
The managed connection factory class implemeneds ManagedConnectionFactory and has methods to create managed connections and connection factories.
To use a resource adapter you need to:
Starting the adapter works like:
Class diagram:
Sequence diagram:
The JCA standard defines a framework for transactions and security as well. None of my examples will use any of these. But be aware that they do exist.
It is usually possible to define pool behavior in an application server specific way.
In the examples (deployed on WildFly) I will define pool behavior in ironjacamar.xml.
Snippet:
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>10</max-pool-size>
<prefill>true</prefill>
<use-strict-min>true</use-strict-min>
<capacity>
<decrementer class-name="org.jboss.jca.core.connectionmanager.pool.capacity.SizeDecrementer">
<config-property name="size">1</config-property>
</decrementer>
</capacity>
</pool>
This instruct the connection manager to:
Snippet:
<timeout>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>4</idle-timeout-minutes>
<allocation-retry>2</allocation-retry>
<allocation-retry-wait-millis>3000</allocation-retry-wait-millis>
</timeout>
This instruct the connection manager to:
JCA adapters are packaged in RAR (Resource ARchive) files, which is really a special flavor of JAR files.
The structure of a RAR file is:
META-INF/ra.xml META-INF/application-server-specific-deployment-descriptors.xml java-code.jar native-code.dll native=code.so
The RAR file can either be deployed directly or be packaged inside an EAR file, where application.xml has:
<module>
<connector>my-connector.rar</connector>
</module>
Hint: with WildFly to make the classes in the RAR file available to a WAR file then add a jboss-deployment-structure.xml to the WAR's WEB-INF with:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="deployment.my-connector.rar" export="true"/>
</dependencies>
</deployment>
</jboss-deployment-structure>
Example sending requests via TCP and receiving response via TCP (multiple connections, multiple requests per connection).
For intro to TCP in Java see Java examples here.
Connection interface:
package jcademo.raoutbound.multcp;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
public interface TestConnection extends Connection {
public int add(int a, int b) throws ResourceException;
}
Connection implementation class:
package jcademo.raoutbound.multcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionMetaData;
import javax.resource.cci.Interaction;
import javax.resource.cci.LocalTransaction;
import javax.resource.cci.ResultSetInfo;
public class TestConnectionImpl implements TestConnection {
private MulTCPManagedConnection mc;
private boolean debug;
// create
public TestConnectionImpl(MulTCPManagedConnection mc, boolean debug) {
this.mc = mc;
this.debug = debug;
if(debug) {
System.out.println("Connection created");
}
}
// business logic
@Override
public int add(int a, int b) throws ResourceException {
return mc.add(a, b);
}
// close
@Override
public void close() {
if(debug) {
System.out.println("Connection closed");
}
mc.close();
mc = null;
}
// CCI support
@Override
public Interaction createInteraction() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
// result set info support
@Override
public ResultSetInfo getResultSetInfo() throws ResourceException {
throw new ResourceException("Result set info not supported");
}
}
Connection factory interface:
package jcademo.raoutbound.multcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
public interface TestConnectionFactory extends ConnectionFactory {
public TestConnection getConnection() throws ResourceException;
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException;
}
Connection factory implementation class:
package jcademo.raoutbound.multcp;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResourceAdapterMetaData;
import javax.resource.spi.ConnectionManager;
public class TestConnectionFactoryImpl implements TestConnectionFactory {
private static final long serialVersionUID = 1L;
private MulTCPManagedConnectionFactory mcf;
private ConnectionManager cm;
// instantiate
public TestConnectionFactoryImpl() {
this(null, null);
}
public TestConnectionFactoryImpl(MulTCPManagedConnectionFactory mcf, ConnectionManager cm) {
this.mcf = mcf;
this.cm = cm;
}
// get connection from connection manager
@Override
public TestConnection getConnection() throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
@Override
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
// CCI support
@Override
public RecordFactory getRecordFactory() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// reference
private Reference ref;
@Override
public Reference getReference() throws NamingException {
return ref;
}
@Override
public void setReference(Reference ref) {
this.ref = ref;
}
// meta data support
@Override
public ResourceAdapterMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection class:
package jcademo.raoutbound.multcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
public class MulTCPManagedConnection implements ManagedConnection {
private boolean debug;
private TestConnection con;
private Socket s;
private PrintStream ps;
private BufferedReader br;
// create
public MulTCPManagedConnection(String host, int port, boolean debug) throws ResourceException {
this.debug = debug;
try {
s = new Socket(host, port);
ps = new PrintStream(s.getOutputStream(), true, "UTF-8");
br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
} catch (IOException e) {
throw new ResourceException(e);
}
if(debug) {
System.out.printf("Managed connection created (%s:%d)\n", host, port);
}
}
// business logic
public int add(int a, int b) throws ResourceException {
try {
ps.println(a + " " + b);
ps.flush();
String line = br.readLine();
int c = Integer.parseInt(line);
return c;
} catch (IOException e) {
throw new ResourceException(e);
}
}
// called by Connection.close => informs connection manager that connection is closed
public void close() {
for(ConnectionEventListener cel : cellst) {
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(con);
cel.connectionClosed(ce);
}
}
// called by connection manager to destroy this instance when adapter is shutting down or pool size is decreased
@Override
public void destroy() throws ResourceException {
try {
br.close();
ps.close();
s.close();
} catch (IOException e) {
throw new ResourceException(e);
}
if(debug) {
System.out.println("Managed connection destroyed");
}
}
// called by connection manager when it is informed that connection is closed
@Override
public void cleanup() {
con = null;
}
// get connection and save connection reference
@Override
public TestConnection getConnection(Subject subj, ConnectionRequestInfo cri) {
con = new TestConnectionImpl(this, debug);
return con;
}
// change connection reference
@Override
public void associateConnection(Object con) {
this.con = (TestConnection)con;
}
// manage listeners (used by connection manager)
private List<ConnectionEventListener> cellst = new ArrayList<>();
@Override
public void addConnectionEventListener(ConnectionEventListener cel) {
cellst.add(cel);
}
@Override
public void removeConnectionEventListener(ConnectionEventListener cel) {
cellst.remove(cel);
}
// log support:
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() throws ResourceException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
@Override
public XAResource getXAResource() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ManagedConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection factory class:
package jcademo.raoutbound.multcp;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
public class MulTCPManagedConnectionFactory implements ManagedConnectionFactory {
private static final long serialVersionUID = 1L;
// config properties
private String host;
private Integer port;
private Boolean debug;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Boolean getDebug() {
return debug;
}
public void setDebug(Boolean debug) {
this.debug = debug;
}
// get connection factory
@Override
public ConnectionFactory createConnectionFactory(ConnectionManager cm) {
return new TestConnectionFactoryImpl(this, cm);
}
@Override
public ConnectionFactory createConnectionFactory() throws ResourceException {
throw new ResourceException("Non-managed not supported");
}
// get managed connection
@Override
public ManagedConnection createManagedConnection(Subject subj, ConnectionRequestInfo cri) throws ResourceException {
return new MulTCPManagedConnection(host, port, debug);
}
// pick a connection from set when asked by connection manager
@Override
public ManagedConnection matchManagedConnections(@SuppressWarnings("rawtypes") Set pool, Subject subj, ConnectionRequestInfo cri) {
@SuppressWarnings("rawtypes") Iterator it = pool.iterator();
while(it.hasNext()) {
ManagedConnection mc = (ManagedConnection)it.next();
if(mc instanceof MulTCPManagedConnection) {
return mc;
}
}
return null;
}
// required by some specification
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
// log support
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
}
Resource adapter class:
package jcademo.raoutbound.multcp;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.xa.XAResource;
// JCA Adapter
public class MulTCPAdapter implements ResourceAdapter {
@Override
public void start(BootstrapContext bctx) {
}
@Override
public void endpointActivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void endpointDeactivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void stop() {
}
@Override
public XAResource[] getXAResources(ActivationSpec[] as) throws ResourceException {
throw new ResourceException("no XA support");
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
}
Resource adapter descriptor (ra.xml):
<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/connector_1_6.xsd"
version="1.6">
<!--
JCA Adaptor: Outbound multi TCP
ManagedConnectionFactory class: jcademo.raoutbound.multcp.MulTCPManagedConnectionFactory
property host = localhost
property port = 20001
property debug = true
Connectionfactory interface: jcademo.raoutbound.multcp.TestConnectionFactory
Connectionfactory class: jcademo.raoutbound.multcp.TestConnectionFactoryImpl
Connection interface: jcademo.raoutbound.multcp.TestConnection
Connection class: jcademo.raoutbound.multcp.TestConnectionImpl
-->
<display-name>Outbound multi TCP</display-name>
<vendor-name>AV</vendor-name>
<eis-type>Demo</eis-type>
<resourceadapter-version>1.0</resourceadapter-version>
<resourceadapter>
<resourceadapter-class>jcademo.raoutbound.multcp.MulTCPAdapter</resourceadapter-class>
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>jcademo.raoutbound.multcp.MulTCPManagedConnectionFactory</managedconnectionfactory-class>
<config-property>
<config-property-name>host</config-property-name>
<config-property-type>java.lang.String</config-property-type>
<config-property-value>localhost</config-property-value>
</config-property>
<config-property>
<config-property-name>port</config-property-name>
<config-property-type>java.lang.Integer</config-property-type>
<config-property-value>20001</config-property-value>
</config-property>
<config-property>
<config-property-name>debug</config-property-name>
<config-property-type>java.lang.Boolean</config-property-type>
<config-property-value>true</config-property-value>
</config-property>
<connectionfactory-interface>jcademo.raoutbound.multcp.TestConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>jcademo.raoutbound.multcp.TestConnectionFactoryImpl</connectionfactory-impl-class>
<connection-interface>jcademo.raoutbound.multcp.TestConnection</connection-interface>
<connection-impl-class>jcademo.raoutbound.multcp.TestConnectionImpl</connection-impl-class>
</connection-definition>
<transaction-support>NoTransaction</transaction-support>
</outbound-resourceadapter>
</resourceadapter>
</connector>
Binding for WildFly/JBoss (ironjacamar.xml):
<?xml version="1.0" encoding="UTF-8"?>
<ironjacamar>
<connection-definitions>
<connection-definition class-name="jcademo.raoutbound.multcp.MulTCPManagedConnectionFactory" jndi-name="java:/eis/MulTCP">
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>10</max-pool-size>
<prefill>true</prefill>
<use-strict-min>true</use-strict-min>
<capacity>
<decrementer class-name="org.jboss.jca.core.connectionmanager.pool.capacity.SizeDecrementer">
<config-property name="size">1</config-property>
</decrementer>
</capacity>
</pool>
<timeout>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>4</idle-timeout-minutes>
<allocation-retry>2</allocation-retry>
<allocation-retry-wait-millis>3000</allocation-retry-wait-millis>
</timeout>
</connection-definition>
</connection-definitions>
</ironjacamar>
Test client:
package jcademo.client;
import java.io.IOException;
import javax.annotation.Resource;
import javax.resource.ResourceException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jcademo.raoutbound.multcp.TestConnection;
import jcademo.raoutbound.multcp.TestConnectionFactory;
@WebServlet(urlPatterns={"/testmultcp"})
public class TestMulTCP extends HttpServlet {
private static final long serialVersionUID = 1L;
@Resource(mappedName = "java:/eis/MulTCP")
private TestConnectionFactory cf;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
TestConnection con = cf.getConnection();
int c = con.add(123, 456);
con.close();
resp.getWriter().println(c);
} catch (ResourceException e) {
e.printStackTrace();
}
}
}
Example sending requests via TCP and receiving response via TCP (multiple connections, multiple requests per connection) using the generic CCI interface.
For intro to TCP in Java see Java examples here.
Connection interface:
package jcademo.raoutbound.multcp_cci;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
public interface TestConnection extends Connection {
public int add(int a, int b) throws ResourceException;
}
Connection implementation class:
package jcademo.raoutbound.multcp_cci;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionMetaData;
import javax.resource.cci.Interaction;
import javax.resource.cci.LocalTransaction;
import javax.resource.cci.ResultSetInfo;
public class TestConnectionImpl implements TestConnection {
private MulTCPManagedConnection mc;
private boolean debug;
// create
public TestConnectionImpl(MulTCPManagedConnection mc, boolean debug) {
this.mc = mc;
this.debug = debug;
if(debug) {
System.out.println("Connection created");
}
}
// business logic
@Override
public int add(int a, int b) throws ResourceException {
return mc.add(a, b);
}
// close
@Override
public void close() {
if(debug) {
System.out.println("Connection closed");
}
mc.close();
mc = null;
}
// CCI support
@Override
public Interaction createInteraction() {
return new CCI_Interaction(this);
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
// result set info support
@Override
public ResultSetInfo getResultSetInfo() throws ResourceException {
throw new ResourceException("Result set info not supported");
}
}
Connection factory interface:
package jcademo.raoutbound.multcp_cci;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
public interface TestConnectionFactory extends ConnectionFactory {
public TestConnection getConnection() throws ResourceException;
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException;
}
Connection factory implementation class:
package jcademo.raoutbound.multcp_cci;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResourceAdapterMetaData;
import javax.resource.spi.ConnectionManager;
public class TestConnectionFactoryImpl implements TestConnectionFactory {
private static final long serialVersionUID = 1L;
private MulTCPManagedConnectionFactory mcf;
private ConnectionManager cm;
// instantiate
public TestConnectionFactoryImpl() {
this(null, null);
}
public TestConnectionFactoryImpl(MulTCPManagedConnectionFactory mcf, ConnectionManager cm) {
this.mcf = mcf;
this.cm = cm;
}
// get connection from connection manager
@Override
public TestConnection getConnection() throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
@Override
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
// CCI support
@Override
public RecordFactory getRecordFactory() {
return new CCI_RecordFactory();
}
// reference
private Reference ref;
@Override
public Reference getReference() throws NamingException {
return ref;
}
@Override
public void setReference(Reference ref) {
this.ref = ref;
}
// meta data support
@Override
public ResourceAdapterMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection class:
package jcademo.raoutbound.multcp_cci;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
public class MulTCPManagedConnection implements ManagedConnection {
private boolean debug;
private TestConnection con;
private Socket s;
private PrintStream ps;
private BufferedReader br;
// create
public MulTCPManagedConnection(String host, int port, boolean debug) throws ResourceException {
this.debug = debug;
try {
s = new Socket(host, port);
ps = new PrintStream(s.getOutputStream(), true, "UTF-8");
br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
} catch (IOException e) {
throw new ResourceException(e);
}
if(debug) {
System.out.printf("Managed connection created (%s:%d)\n", host, port);
}
}
// business logic
public int add(int a, int b) throws ResourceException {
try {
ps.println(a + " " + b);
ps.flush();
String line = br.readLine();
int c = Integer.parseInt(line);
return c;
} catch (IOException e) {
throw new ResourceException(e);
}
}
// called by Connection.close => informs connection manager that connection is closed
public void close() {
for(ConnectionEventListener cel : cellst) {
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(con);
cel.connectionClosed(ce);
}
}
// called by connection manager to destroy this instance when adapter is shutting down or pool size is decreased
@Override
public void destroy() throws ResourceException {
try {
br.close();
ps.close();
s.close();
} catch (IOException e) {
throw new ResourceException(e);
}
if(debug) {
System.out.println("Managed connection destroyed");
}
}
// called by connection manager when it is informed that connection is closed
@Override
public void cleanup() {
con = null;
}
// get connection and save connection reference
@Override
public TestConnection getConnection(Subject subj, ConnectionRequestInfo cri) {
con = new TestConnectionImpl(this, debug);
return con;
}
// change connection reference
@Override
public void associateConnection(Object con) {
this.con = (TestConnection)con;
}
// manage listeners (used by connection manager)
private List<ConnectionEventListener> cellst = new ArrayList<>();
@Override
public void addConnectionEventListener(ConnectionEventListener cel) {
cellst.add(cel);
}
@Override
public void removeConnectionEventListener(ConnectionEventListener cel) {
cellst.remove(cel);
}
// log support:
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() throws ResourceException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
@Override
public XAResource getXAResource() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ManagedConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection factory class:
package jcademo.raoutbound.multcp_cci;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
public class MulTCPManagedConnectionFactory implements ManagedConnectionFactory {
private static final long serialVersionUID = 1L;
// config properties
private String host;
private Integer port;
private Boolean debug;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Boolean getDebug() {
return debug;
}
public void setDebug(Boolean debug) {
this.debug = debug;
}
// get connection factory
@Override
public ConnectionFactory createConnectionFactory(ConnectionManager cm) {
return new TestConnectionFactoryImpl(this, cm);
}
@Override
public ConnectionFactory createConnectionFactory() throws ResourceException {
throw new ResourceException("Non-managed not supported");
}
// get managed connection
@Override
public ManagedConnection createManagedConnection(Subject subj, ConnectionRequestInfo cri) throws ResourceException {
return new MulTCPManagedConnection(host, port, debug);
}
// pick a connection from set when asked by connection manager
@Override
public ManagedConnection matchManagedConnections(@SuppressWarnings("rawtypes") Set pool, Subject subj, ConnectionRequestInfo cri) {
@SuppressWarnings("rawtypes") Iterator it = pool.iterator();
while(it.hasNext()) {
ManagedConnection mc = (ManagedConnection)it.next();
if(mc instanceof MulTCPManagedConnection) {
return mc;
}
}
return null;
}
// required by some specification
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
// log support
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
}
Resource adapter class:
package jcademo.raoutbound.multcp_cci;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.xa.XAResource;
// JCA Adapter
public class MulTCPAdapter implements ResourceAdapter {
@Override
public void start(BootstrapContext bctx) {
}
@Override
public void endpointActivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void endpointDeactivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void stop() {
}
@Override
public XAResource[] getXAResources(ActivationSpec[] as) throws ResourceException {
throw new ResourceException("no XA support");
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
}
CCI interface classes:
package jcademo.raoutbound.multcp_cci;
import java.util.AbstractList;
import javax.resource.cci.IndexedRecord;
@SuppressWarnings("rawtypes")
public class CCI_AB_IndexedRecord extends AbstractList implements IndexedRecord {
private static final long serialVersionUID = 1L;
private int a;
private int b;
// record name and description
@Override
public String getRecordName() {
return "AB";
}
@Override
public void setRecordName(String name) {
// name is fixed
}
@Override
public String getRecordShortDescription() {
return "A and B (input)";
}
@Override
public void setRecordShortDescription(String description) {
// description is fixed
}
// core methods
@Override
public Object get(int index) {
switch(index) {
case 0:
return a;
case 1:
return b;
default:
throw new IndexOutOfBoundsException(Integer.toString(index));
}
}
@Override
public Object set(int index, Object element) {
switch(index) {
case 0:
int olda = a;
a = (Integer)element;
return olda;
case 1:
int oldb = b;
b = (Integer)element;
return oldb;
default:
throw new IndexOutOfBoundsException(Integer.toString(index));
}
}
@Override
public int size() {
return 2;
}
// required
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package jcademo.raoutbound.multcp_cci;
import java.util.AbstractMap;
import java.util.HashSet;
import java.util.Set;
import javax.resource.cci.MappedRecord;
@SuppressWarnings("rawtypes")
public class CCI_AB_MappedRecord extends AbstractMap implements MappedRecord {
private static final long serialVersionUID = 1L;
private int a;
private int b;
// record name and description
@Override
public String getRecordName() {
return "AB";
}
@Override
public void setRecordName(String name) {
// name is fixed
}
@Override
public String getRecordShortDescription() {
return "A and B (input)";
}
@Override
public void setRecordShortDescription(String description) {
// description is fixed
}
// core methods
@Override
public Set entrySet() {
Set res = new HashSet();
res.add(new AbstractMap.SimpleEntry("A", (Integer)a));
res.add(new AbstractMap.SimpleEntry("B", (Integer)b));
return res;
}
@Override
public Object put(Object key, Object value) {
if(key.equals("A")) {
int olda = a;
a = (Integer) value;
return olda;
} else if(key.equals("B")) {
int oldb = b;
b = (Integer) value;
return oldb;
} else {
throw new IllegalArgumentException("Bad key: " + key);
}
}
// required
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package jcademo.raoutbound.multcp_cci;
import java.util.AbstractList;
import javax.resource.cci.IndexedRecord;
@SuppressWarnings("rawtypes")
public class CCI_C_IndexedRecord extends AbstractList implements IndexedRecord {
private static final long serialVersionUID = 1L;
private int c;
// record name and description
@Override
public String getRecordName() {
return "C";
}
@Override
public void setRecordName(String name) {
// name is fixed
}
@Override
public String getRecordShortDescription() {
return "C (output)";
}
@Override
public void setRecordShortDescription(String description) {
// description is fixed
}
// core methods
@Override
public Object get(int index) {
switch(index) {
case 0:
return c;
default:
throw new IndexOutOfBoundsException(Integer.toString(index));
}
}
@Override
public Object set(int index, Object element) {
switch(index) {
case 0:
int oldc = c;
c = (Integer)element;
return oldc;
default:
throw new IndexOutOfBoundsException(Integer.toString(index));
}
}
@Override
public int size() {
return 1;
}
// required
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package jcademo.raoutbound.multcp_cci;
import java.util.AbstractMap;
import java.util.HashSet;
import java.util.Set;
import javax.resource.cci.MappedRecord;
@SuppressWarnings("rawtypes")
public class CCI_C_MappedRecord extends AbstractMap implements MappedRecord {
private static final long serialVersionUID = 1L;
private int c;
// record name and description
@Override
public String getRecordName() {
return "C";
}
@Override
public void setRecordName(String name) {
// name is fixed
}
@Override
public String getRecordShortDescription() {
return "C (output)";
}
@Override
public void setRecordShortDescription(String description) {
// description is fixed
}
// core methods
@Override
public Set entrySet() {
Set res = new HashSet();
res.add(new AbstractMap.SimpleEntry("C", (Integer)c));
return res;
}
@Override
public Object put(Object key, Object value) {
if(key.equals("C")) {
int oldc = c;
c = (Integer) value;
return oldc;
} else {
throw new IllegalArgumentException("Bad key: " + key);
}
}
// required
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package jcademo.raoutbound.multcp_cci;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.IndexedRecord;
import javax.resource.cci.Interaction;
import javax.resource.cci.InteractionSpec;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.Record;
import javax.resource.cci.ResourceWarning;
public class CCI_Interaction implements Interaction {
// connection
private TestConnection con;
public CCI_Interaction(TestConnection con) {
this.con = con;
}
@Override
public Connection getConnection() {
return con;
}
// execute
@Override
public Record execute(InteractionSpec ispec, Record input) throws ResourceException {
Record output;
if(input instanceof IndexedRecord) {
output = new CCI_C_IndexedRecord();
} else if(input instanceof MappedRecord) {
output = new CCI_C_MappedRecord();
} else {
throw new ResourceException("Unknown record type " + input.getClass().getName());
}
return execute(ispec, input, output) ? output : null;
}
@Override
public boolean execute(InteractionSpec ispec, Record input, Record output) throws ResourceException {
int a;
int b;
if(input instanceof IndexedRecord) {
IndexedRecord input2 = (IndexedRecord)input;
a = (Integer)input2.get(0);
b = (Integer)input2.get(1);
} else if(input instanceof MappedRecord) {
MappedRecord input2 = (MappedRecord)input;
a = (Integer)input2.get("A");
b = (Integer)input2.get("B");
} else {
throw new ResourceException("Unknown record type " + input.getClass().getName());
}
CCI_InteractionSpec ispec2 = (CCI_InteractionSpec)ispec;
int c;
if(ispec2.getFunctionName().equals("ADD")) {
c = con.add(a, b);
} else {
throw new ResourceException("Unknown function name " + ispec2.getFunctionName());
}
if(output instanceof IndexedRecord) {
IndexedRecord output2 = (IndexedRecord)output;
output2.set(0, c);
} else if(output instanceof MappedRecord) {
MappedRecord output2 = (MappedRecord)output;
output2.put("C", c);
} else {
throw new ResourceException("Unknown record type " + output.getClass().getName());
}
return true;
}
// close
@Override
public void close() throws ResourceException {
// nothing
}
// warnings
@Override
public ResourceWarning getWarnings() throws ResourceException {
throw new ResourceException("Warnings not supported");
}
@Override
public void clearWarnings() throws ResourceException {
throw new ResourceException("Warnings not supported");
}
}
package jcademo.raoutbound.multcp_cci;
import javax.resource.cci.InteractionSpec;
public interface CCI_InteractionSpec extends InteractionSpec {
public String getFunctionName();
public void setFunctionName(String functionName);
public int getInteractionVerb();
public void setInteractionVerb(int interactionVerb);
}
package jcademo.raoutbound.multcp_cci;
public class CCI_InteractionSpecImpl implements CCI_InteractionSpec {
private static final long serialVersionUID = 1L;
private String functionName = "ADD";
private int interactionVerb = SYNC_SEND_RECEIVE;
@Override
public String getFunctionName() {
return functionName;
}
@Override
public void setFunctionName(String functionName) {
this.functionName = functionName;
}
@Override
public int getInteractionVerb() {
return interactionVerb;
}
@Override
public void setInteractionVerb(int interactionVerb) {
this.interactionVerb = interactionVerb;
}
}
package jcademo.raoutbound.multcp_cci;
import javax.resource.ResourceException;
import javax.resource.cci.IndexedRecord;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.RecordFactory;
public class CCI_RecordFactory implements RecordFactory {
@Override
public IndexedRecord createIndexedRecord(String recordName) throws ResourceException {
if(recordName.equals("AB")) {
return new CCI_AB_IndexedRecord();
} else if(recordName.equals("C")) {
return new CCI_C_IndexedRecord();
} else {
throw new ResourceException("Unknown record type " + recordName);
}
}
@Override
public MappedRecord createMappedRecord(String recordName) throws ResourceException {
if(recordName.equals("AB")) {
return new CCI_AB_MappedRecord();
} else if(recordName.equals("C")) {
return new CCI_C_MappedRecord();
} else {
throw new ResourceException("Unknown record type " + recordName);
}
}
}
Resource adapter descriptor (ra.xml):
<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/connector_1_6.xsd"
version="1.6">
<!--
JCA Adaptor: Outbound multi TCP with CCI
ManagedConnectionFactory class: jcademo.raoutbound.multcp_cci.MulTCPManagedConnectionFactory
property host = localhost
property port = 20001
property debug = true
Connectionfactory interface: jcademo.raoutbound.multcp_cci.TestConnectionFactory
Connectionfactory class: jcademo.raoutbound.multcp_cci.TestConnectionFactoryImpl
Connection interface: jcademo.raoutbound.multcp_cci.TestConnection
Connection class: jcademo.raoutbound.multcp_cci.TestConnectionImpl
-->
<display-name>Outbound multi TCP with CCI</display-name>
<vendor-name>AV</vendor-name>
<eis-type>Demo</eis-type>
<resourceadapter-version>1.0</resourceadapter-version>
<resourceadapter>
<resourceadapter-class>jcademo.raoutbound.multcp_cci.MulTCPAdapter</resourceadapter-class>
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>jcademo.raoutbound.multcp_cci.MulTCPManagedConnectionFactory</managedconnectionfactory-class>
<config-property>
<config-property-name>host</config-property-name>
<config-property-type>java.lang.String</config-property-type>
<config-property-value>localhost</config-property-value>
</config-property>
<config-property>
<config-property-name>port</config-property-name>
<config-property-type>java.lang.Integer</config-property-type>
<config-property-value>20001</config-property-value>
</config-property>
<config-property>
<config-property-name>debug</config-property-name>
<config-property-type>java.lang.Boolean</config-property-type>
<config-property-value>true</config-property-value>
</config-property>
<connectionfactory-interface>jcademo.raoutbound.multcp_cci.TestConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>jcademo.raoutbound.multcp_cci.TestConnectionFactoryImpl</connectionfactory-impl-class>
<connection-interface>jcademo.raoutbound.multcp_cci.TestConnection</connection-interface>
<connection-impl-class>jcademo.raoutbound.multcp_cci.TestConnectionImpl</connection-impl-class>
</connection-definition>
<transaction-support>NoTransaction</transaction-support>
</outbound-resourceadapter>
<adminobject>
<adminobject-interface>jcademo.raoutbound.multcp_cci.CCI_InteractionSpec</adminobject-interface>
<adminobject-class>jcademo.raoutbound.multcp_cci.CCI_InteractionSpecImpl</adminobject-class>
</adminobject>
</resourceadapter>
</connector>
Binding for WildFly/JBoss (ironjacamar.xml):
<?xml version="1.0" encoding="UTF-8"?>
<ironjacamar>
<connection-definitions>
<connection-definition class-name="jcademo.raoutbound.multcp_cci.MulTCPManagedConnectionFactory" jndi-name="java:/eis/MulTCP_CCI">
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>10</max-pool-size>
<prefill>true</prefill>
<use-strict-min>true</use-strict-min>
<capacity>
<decrementer class-name="org.jboss.jca.core.connectionmanager.pool.capacity.SizeDecrementer">
<config-property name="size">1</config-property>
</decrementer>
</capacity>
</pool>
<timeout>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>4</idle-timeout-minutes>
<allocation-retry>2</allocation-retry>
<allocation-retry-wait-millis>3000</allocation-retry-wait-millis>
</timeout>
</connection-definition>
</connection-definitions>
<admin-objects>
<admin-object class-name="jcademo.raoutbound.multcp_cci.CCI_InteractionSpecImpl" jndi-name="java:/eis/MulTCP_IS"/>
</admin-objects>
</ironjacamar>
Test client:
package jcademo.client;
import java.io.IOException;
import javax.annotation.Resource;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.IndexedRecord;
import javax.resource.cci.Interaction;
import javax.resource.cci.InteractionSpec;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.RecordFactory;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns={"/testmultcp_cci"})
public class TestMulTCP_CCI extends HttpServlet {
private static final long serialVersionUID = 1L;
@Resource(mappedName = "java:/eis/MulTCP_CCI")
private ConnectionFactory cf;
@Resource(mappedName = "java:/eis/MulTCP_IS")
private InteractionSpec is;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
int c;
// open
Connection con = cf.getConnection();
Interaction i = con.createInteraction();
RecordFactory rf = cf.getRecordFactory();
// test index records
IndexedRecord ir = rf.createIndexedRecord("AB");
ir.set(0, 123);
ir.set(1, 456);
IndexedRecord oir = (IndexedRecord)i.execute(is, ir);
c = (Integer)oir.get(0);
resp.getWriter().println(c);
// test mapped record
MappedRecord mr = rf.createMappedRecord("AB");
mr.put("A", 123);
mr.put("B", 456);
MappedRecord omr = (MappedRecord)i.execute(is, mr);
c = (Integer)omr.get("C");
resp.getWriter().println(c);
// close
con.close();
} catch (ResourceException e) {
e.printStackTrace();
}
}
}
And I really don't get the CCI interface. Yes - it seems smart that one can write the client code referencing only general Java EE types without referencing any adapter specific types. But then one realize that the code instead contains a large number of adapter specific values - and then the point is sort of lost. I recommend avoiding CCI interface - it just obfuscates the client code unnecessary.
Example sending requests via TCP and receiving response via TCP (multiple connections, one request per connection).
For intro to TCP in Java see Java examples here.
Connection interface:
package jcademo.raoutbound.tcp;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
public interface TestConnection extends Connection {
public int add(int a, int b) throws ResourceException;
}
Connection implementation class:
package jcademo.raoutbound.tcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionMetaData;
import javax.resource.cci.Interaction;
import javax.resource.cci.LocalTransaction;
import javax.resource.cci.ResultSetInfo;
public class TestConnectionImpl implements TestConnection {
private TCPManagedConnection mc;
private boolean debug;
// create
public TestConnectionImpl(TCPManagedConnection mc, boolean debug) {
this.mc = mc;
this.debug = debug;
if(debug) {
System.out.println("Connection created");
}
}
// business logic
@Override
public int add(int a, int b) throws ResourceException {
return mc.add(a, b);
}
// close
@Override
public void close() {
if(debug) {
System.out.println("Connection closed");
}
mc.close();
mc = null;
}
// CCI support
@Override
public Interaction createInteraction() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
// result set info support
@Override
public ResultSetInfo getResultSetInfo() throws ResourceException {
throw new ResourceException("Result set info not supported");
}
}
Connection factory interface:
package jcademo.raoutbound.tcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
public interface TestConnectionFactory extends ConnectionFactory {
public TestConnection getConnection() throws ResourceException;
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException;
}
Connection factory implementation class:
package jcademo.raoutbound.tcp;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResourceAdapterMetaData;
import javax.resource.spi.ConnectionManager;
public class TestConnectionFactoryImpl implements TestConnectionFactory {
private static final long serialVersionUID = 1L;
private TCPManagedConnectionFactory mcf;
private ConnectionManager cm;
// instantiate
public TestConnectionFactoryImpl() {
this(null, null);
}
public TestConnectionFactoryImpl(TCPManagedConnectionFactory mcf, ConnectionManager cm) {
this.mcf = mcf;
this.cm = cm;
}
// get connection from connection manager
@Override
public TestConnection getConnection() throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
@Override
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
// CCI support
@Override
public RecordFactory getRecordFactory() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// reference
private Reference ref;
@Override
public Reference getReference() throws NamingException {
return ref;
}
@Override
public void setReference(Reference ref) {
this.ref = ref;
}
// meta data support
@Override
public ResourceAdapterMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection class:
package jcademo.raoutbound.tcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
public class TCPManagedConnection implements ManagedConnection {
private boolean debug;
private TestConnection con;
private String host;
private int port;
// create
public TCPManagedConnection(String host, int port, boolean debug) throws ResourceException {
this.debug = debug;
this.host = host;
this.port = port;
if(debug) {
System.out.printf("Managed connection created (%s:%d)\n", host, port);
}
}
// business logic
public int add(int a, int b) throws ResourceException {
try {
Socket s = new Socket(host, port);
PrintStream ps = new PrintStream(s.getOutputStream(), true, "UTF-8");
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
ps.println(a + " " + b);
ps.flush();
String line = br.readLine();
br.close();
ps.close();
s.close();
int c = Integer.parseInt(line);
return c;
} catch (IOException e) {
throw new ResourceException(e);
}
}
// called by Connection.close => informs connection manager that connection is closed
public void close() {
for(ConnectionEventListener cel : cellst) {
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(con);
cel.connectionClosed(ce);
}
}
// called by connection manager to destroy this instance when adapter is shutting down or pool size is decreased
@Override
public void destroy() throws ResourceException {
if(debug) {
System.out.println("Managed connection destroyed");
}
}
// called by connection manager when it is informed that connection is closed
@Override
public void cleanup() {
con = null;
}
// get connection and save connection reference
@Override
public TestConnection getConnection(Subject subj, ConnectionRequestInfo cri) {
con = new TestConnectionImpl(this, debug);
return con;
}
// change connection reference
@Override
public void associateConnection(Object con) {
this.con = (TestConnection)con;
}
// manage listeners (used by connection manager)
private List<ConnectionEventListener> cellst = new ArrayList<>();
@Override
public void addConnectionEventListener(ConnectionEventListener cel) {
cellst.add(cel);
}
@Override
public void removeConnectionEventListener(ConnectionEventListener cel) {
cellst.remove(cel);
}
// log support:
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() throws ResourceException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
@Override
public XAResource getXAResource() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ManagedConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection factory class:
package jcademo.raoutbound.tcp;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
public class TCPManagedConnectionFactory implements ManagedConnectionFactory {
private static final long serialVersionUID = 1L;
// config properties
private String host;
private Integer port;
private Boolean debug;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Boolean getDebug() {
return debug;
}
public void setDebug(Boolean debug) {
this.debug = debug;
}
// get connection factory
@Override
public ConnectionFactory createConnectionFactory(ConnectionManager cm) {
return new TestConnectionFactoryImpl(this, cm);
}
@Override
public ConnectionFactory createConnectionFactory() throws ResourceException {
throw new ResourceException("Non-managed not supported");
}
// get managed connection
@Override
public ManagedConnection createManagedConnection(Subject subj, ConnectionRequestInfo cri) throws ResourceException {
return new TCPManagedConnection(host, port, debug);
}
// pick a connection from set when asked by connection manager
@Override
public ManagedConnection matchManagedConnections(@SuppressWarnings("rawtypes") Set pool, Subject subj, ConnectionRequestInfo cri) {
@SuppressWarnings("rawtypes") Iterator it = pool.iterator();
while(it.hasNext()) {
ManagedConnection mc = (ManagedConnection)it.next();
if(mc instanceof TCPManagedConnection) {
return mc;
}
}
return null;
}
// required by some specification
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
// log support
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
}
Resource adapter class:
package jcademo.raoutbound.tcp;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.xa.XAResource;
// JCA Adapter
public class TCPAdapter implements ResourceAdapter {
@Override
public void start(BootstrapContext bctx) {
}
@Override
public void endpointActivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void endpointDeactivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void stop() {
}
@Override
public XAResource[] getXAResources(ActivationSpec[] as) throws ResourceException {
throw new ResourceException("no XA support");
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
}
Resource adapter descriptor (ra.xml):
<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/connector_1_6.xsd"
version="1.6">
<!--
JCA Adaptor: Outbound TCP
ManagedConnectionFactory class: jcademo.raoutbound.tcp.TCPManagedConnectionFactory
property host = localhost
property port = 20001
property debug = true
Connectionfactory interface: jcademo.raoutbound.tcp.TestConnectionFactory
Connectionfactory class: jcademo.raoutbound.tcp.TestConnectionFactoryImpl
Connection interface: jcademo.raoutbound.tcp.TestConnection
Connection class: jcademo.raoutbound.tcp.TestConnectionImpl
-->
<display-name>Outbound TCP</display-name>
<vendor-name>AV</vendor-name>
<eis-type>Demo</eis-type>
<resourceadapter-version>1.0</resourceadapter-version>
<resourceadapter>
<resourceadapter-class>jcademo.raoutbound.tcp.TCPAdapter</resourceadapter-class>
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>jcademo.raoutbound.tcp.TCPManagedConnectionFactory</managedconnectionfactory-class>
<config-property>
<config-property-name>host</config-property-name>
<config-property-type>java.lang.String</config-property-type>
<config-property-value>localhost</config-property-value>
</config-property>
<config-property>
<config-property-name>port</config-property-name>
<config-property-type>java.lang.Integer</config-property-type>
<config-property-value>20002</config-property-value>
</config-property>
<config-property>
<config-property-name>debug</config-property-name>
<config-property-type>java.lang.Boolean</config-property-type>
<config-property-value>true</config-property-value>
</config-property>
<connectionfactory-interface>jcademo.raoutbound.tcp.TestConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>jcademo.raoutbound.tcp.TestConnectionFactoryImpl</connectionfactory-impl-class>
<connection-interface>jcademo.raoutbound.tcp.TestConnection</connection-interface>
<connection-impl-class>jcademo.raoutbound.tcp.TestConnectionImpl</connection-impl-class>
</connection-definition>
<transaction-support>NoTransaction</transaction-support>
</outbound-resourceadapter>
</resourceadapter>
</connector>
Binding for WildFly/JBoss (ironjacamar.xml):
<?xml version="1.0" encoding="UTF-8"?>
<ironjacamar>
<connection-definitions>
<connection-definition class-name="jcademo.raoutbound.tcp.TCPManagedConnectionFactory" jndi-name="java:/eis/TCP">
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>10</max-pool-size>
<prefill>true</prefill>
<use-strict-min>true</use-strict-min>
<capacity>
<decrementer class-name="org.jboss.jca.core.connectionmanager.pool.capacity.SizeDecrementer">
<config-property name="size">1</config-property>
</decrementer>
</capacity>
</pool>
<timeout>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>4</idle-timeout-minutes>
<allocation-retry>2</allocation-retry>
<allocation-retry-wait-millis>3000</allocation-retry-wait-millis>
</timeout>
</connection-definition>
</connection-definitions>
</ironjacamar>
Test client:
package jcademo.client;
import java.io.IOException;
import javax.annotation.Resource;
import javax.resource.ResourceException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jcademo.raoutbound.tcp.TestConnection;
import jcademo.raoutbound.tcp.TestConnectionFactory;
@WebServlet(urlPatterns={"/testtcp"})
public class TestTCP extends HttpServlet {
private static final long serialVersionUID = 1L;
@Resource(mappedName = "java:/eis/TCP")
private TestConnectionFactory cf;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
TestConnection con = cf.getConnection();
int c = con.add(123, 456);
con.close();
resp.getWriter().println(c);
} catch (ResourceException e) {
e.printStackTrace();
}
}
}
Example sending requests via TCP and receiving response via TCP (single permanent connection, multiple requests).
The problem of having multiple managed connections and a single physical TCP connection is solved by introducing a TCP multi-plexer that send requests and return responses on a Java BlockingQueue to each managed connection.
For intro to TCP in Java see Java examples here.
Connection interface:
package jcademo.raoutbound.permtcp;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
public interface TestConnection extends Connection {
public int add(int a, int b) throws ResourceException;
}
Connection implementation class:
package jcademo.raoutbound.permtcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionMetaData;
import javax.resource.cci.Interaction;
import javax.resource.cci.LocalTransaction;
import javax.resource.cci.ResultSetInfo;
public class TestConnectionImpl implements TestConnection {
private PermTCPManagedConnection mc;
private boolean debug;
// create
public TestConnectionImpl(PermTCPManagedConnection mc, boolean debug) {
this.mc = mc;
this.debug = debug;
if(debug) {
System.out.println("Connection created");
}
}
// business logic
@Override
public int add(int a, int b) throws ResourceException {
return mc.add(a, b);
}
// close
@Override
public void close() {
if(debug) {
System.out.println("Connection closed");
}
mc.close();
mc = null;
}
// CCI support
@Override
public Interaction createInteraction() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
// result set info support
@Override
public ResultSetInfo getResultSetInfo() throws ResourceException {
throw new ResourceException("Result set info not supported");
}
}
Connection factory interface:
package jcademo.raoutbound.permtcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
public interface TestConnectionFactory extends ConnectionFactory {
public TestConnection getConnection() throws ResourceException;
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException;
}
Connection factory implementation class:
package jcademo.raoutbound.permtcp;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResourceAdapterMetaData;
import javax.resource.spi.ConnectionManager;
public class TestConnectionFactoryImpl implements TestConnectionFactory {
private static final long serialVersionUID = 1L;
private PermTCPManagedConnectionFactory mcf;
private ConnectionManager cm;
// instantiate
public TestConnectionFactoryImpl() {
this(null, null);
}
public TestConnectionFactoryImpl(PermTCPManagedConnectionFactory mcf, ConnectionManager cm) {
this.mcf = mcf;
this.cm = cm;
}
// get connection from connection manager
@Override
public TestConnection getConnection() throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
@Override
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
// CCI support
@Override
public RecordFactory getRecordFactory() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// reference
private Reference ref;
@Override
public Reference getReference() throws NamingException {
return ref;
}
@Override
public void setReference(Reference ref) {
this.ref = ref;
}
// meta data support
@Override
public ResourceAdapterMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection class:
package jcademo.raoutbound.permtcp;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
public class PermTCPManagedConnection implements ManagedConnection {
private boolean debug;
private String id;
private BlockingQueue<String> q;
private TestConnection con;
// create
public PermTCPManagedConnection(boolean debug) throws ResourceException {
this.debug = debug;
id = UUID.randomUUID().toString();
q = new ArrayBlockingQueue<String>(10);
TCPMultiPlexer.getInstance().register(id, q);
if(debug) {
System.out.println("Managed connection created");
}
}
// business logic
public int add(int a, int b) throws ResourceException {
try {
TCPMultiPlexer.getInstance().send(id, a + " " + b);
String line = q.take();
int c = Integer.parseInt(line);
return c;
} catch (NumberFormatException e) {
throw new ResourceException(e);
} catch (InterruptedException e) {
throw new ResourceException(e);
}
}
// called by Connection.close => informs connection manager that connection is closed
public void close() {
for(ConnectionEventListener cel : cellst) {
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(con);
cel.connectionClosed(ce);
}
}
// called by connection manager to destroy this instance when adapter is shutting down or pool size is decreased
@Override
public void destroy() throws ResourceException {
TCPMultiPlexer.getInstance().deregister(id);
if(debug) {
System.out.println("Managed connection destroyed");
}
}
// called by connection manager when it is informed that connection is closed
@Override
public void cleanup() {
con = null;
}
// get connection and save connection reference
@Override
public TestConnection getConnection(Subject subj, ConnectionRequestInfo cri) {
con = new TestConnectionImpl(this, debug);
return con;
}
// change connection reference
@Override
public void associateConnection(Object con) {
this.con = (TestConnection)con;
}
// manage listeners (used by connection manager)
private List<ConnectionEventListener> cellst = new ArrayList<>();
@Override
public void addConnectionEventListener(ConnectionEventListener cel) {
cellst.add(cel);
}
@Override
public void removeConnectionEventListener(ConnectionEventListener cel) {
cellst.remove(cel);
}
// log support:
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() throws ResourceException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
@Override
public XAResource getXAResource() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ManagedConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection factory class:
package jcademo.raoutbound.permtcp;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
public class PermTCPManagedConnectionFactory implements ManagedConnectionFactory {
private static final long serialVersionUID = 1L;
// config properties
private Boolean debug;
public Boolean getDebug() {
return debug;
}
public void setDebug(Boolean debug) {
this.debug = debug;
}
// get connection factory
@Override
public ConnectionFactory createConnectionFactory(ConnectionManager cm) {
return new TestConnectionFactoryImpl(this, cm);
}
@Override
public ConnectionFactory createConnectionFactory() throws ResourceException {
throw new ResourceException("Non-managed not supported");
}
// get managed connection
@Override
public ManagedConnection createManagedConnection(Subject subj, ConnectionRequestInfo cri) throws ResourceException {
return new PermTCPManagedConnection(debug);
}
// pick a connection from set when asked by connection manager
@Override
public ManagedConnection matchManagedConnections(@SuppressWarnings("rawtypes") Set pool, Subject subj, ConnectionRequestInfo cri) {
@SuppressWarnings("rawtypes") Iterator it = pool.iterator();
while(it.hasNext()) {
ManagedConnection mc = (ManagedConnection)it.next();
if(mc instanceof PermTCPManagedConnection) {
return mc;
}
}
return null;
}
// required by some specification
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
// log support
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
}
Resource adapter class:
package jcademo.raoutbound.permtcp;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkManager;
import javax.transaction.xa.XAResource;
// JCA Adapter
public class PermTCPAdapter implements ResourceAdapter {
private String host;
private int port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
private Work w;
@Override
public void start(BootstrapContext bctx) {
WorkManager wm = bctx.getWorkManager();
try {
w = new PermTCPWorker(host, port);
wm.scheduleWork(w);
} catch (WorkException e) {
e.printStackTrace();
}
}
@Override
public void endpointActivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void endpointDeactivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void stop() {
w.release();
}
@Override
public XAResource[] getXAResources(ActivationSpec[] as) throws ResourceException {
throw new ResourceException("no XA support");
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
}
Utility classes:
package jcademo.raoutbound.permtcp;
import java.io.IOException;
import javax.resource.spi.work.Work;
public class PermTCPWorker implements Work {
private String host;
private int port;
private boolean done;
public PermTCPWorker(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public void run() {
done = false;
while(!done) {
try {
Thread.sleep(1000);
TCPMultiPlexer.getInstance().connect(host, port);
TCPMultiPlexer.getInstance().receive();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void release() {
try {
done = true;
TCPMultiPlexer.getInstance().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package jcademo.raoutbound.permtcp;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
public class TCPMultiPlexer {
// singleton
private static TCPMultiPlexer instance = new TCPMultiPlexer();
public static TCPMultiPlexer getInstance() {
return instance;
}
// data
private Socket s;
private PrintStream ps;
private BufferedReader br;
private Map<String, BlockingQueue<String>> qmap = new HashMap<String, BlockingQueue<String>>();
// connect
public void connect(String host, int port) throws IOException {
s = new Socket(host, port);
ps = new PrintStream(s.getOutputStream(), true, "UTF-8");
br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
}
// register receive queue
public void register(String id, BlockingQueue<String> q) {
synchronized(qmap) {
qmap.put(id, q);
}
}
// send
public void send(String id, String line) throws InterruptedException {
synchronized(ps) {
ps.println(id);
ps.println(line);
ps.flush();
}
}
// deregister receive queue
public void deregister(String id) {
synchronized(qmap) {
qmap.remove(id);
}
}
// close
public void close() throws IOException {
br.close();
ps.close();
s.close();
}
// receive loop that put responses in correct queue
public void receive() throws IOException, InterruptedException {
while(true) {
String id = br.readLine();
if(id == null) break;
String line = br.readLine();
if(line == null) break;
qmap.get(id).put(line);
}
}
}
Resource adapter descriptor (ra.xml):
<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/connector_1_6.xsd"
version="1.6">
<!--
JCA Adaptor: Outbound permament TCP
ManagedConnectionFactory class: jcademo.raoutbound.permtcp.PermTCPManagedConnectionFactory
property host = localhost
property port = 20003
property debug = true
Connectionfactory interface: jcademo.raoutbound.tcp.TestConnectionFactory
Connectionfactory class: jcademo.raoutbound.tcp.TestConnectionFactoryImpl
Connection interface: jcademo.raoutbound.tcp.TestConnection
Connection class: jcademo.raoutbound.tcp.TestConnectionImpl
-->
<display-name>Outbound permanent TCP</display-name>
<vendor-name>AV</vendor-name>
<eis-type>Demo</eis-type>
<resourceadapter-version>1.0</resourceadapter-version>
<resourceadapter>
<resourceadapter-class>jcademo.raoutbound.permtcp.PermTCPAdapter</resourceadapter-class>
<config-property>
<config-property-name>host</config-property-name>
<config-property-type>java.lang.String</config-property-type>
<config-property-value>localhost</config-property-value>
</config-property>
<config-property>
<config-property-name>port</config-property-name>
<config-property-type>java.lang.Integer</config-property-type>
<config-property-value>20003</config-property-value>
</config-property>
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>jcademo.raoutbound.permtcp.PermTCPManagedConnectionFactory</managedconnectionfactory-class>
<config-property>
<config-property-name>debug</config-property-name>
<config-property-type>java.lang.Boolean</config-property-type>
<config-property-value>true</config-property-value>
</config-property>
<connectionfactory-interface>jcademo.raoutbound.permtcp.TestConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>jcademo.raoutbound.permtcp.TestConnectionFactoryImpl</connectionfactory-impl-class>
<connection-interface>jcademo.raoutbound.permtcp.TestConnection</connection-interface>
<connection-impl-class>jcademo.raoutbound.permtcp.TestConnectionImpl</connection-impl-class>
</connection-definition>
<transaction-support>NoTransaction</transaction-support>
</outbound-resourceadapter>
</resourceadapter>
</connector>
Binding for WildFly/JBoss (ironjacamar.xml):
<?xml version="1.0" encoding="UTF-8"?>
<ironjacamar>
<connection-definitions>
<connection-definition class-name="jcademo.raoutbound.permtcp.PermTCPManagedConnectionFactory" jndi-name="java:/eis/PermTCP">
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>10</max-pool-size>
<prefill>true</prefill>
<use-strict-min>true</use-strict-min>
<capacity>
<decrementer class-name="org.jboss.jca.core.connectionmanager.pool.capacity.SizeDecrementer">
<config-property name="size">1</config-property>
</decrementer>
</capacity>
</pool>
<timeout>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>4</idle-timeout-minutes>
<allocation-retry>2</allocation-retry>
<allocation-retry-wait-millis>3000</allocation-retry-wait-millis>
</timeout>
</connection-definition>
</connection-definitions>
</ironjacamar>
Test client:
package jcademo.client;
import java.io.IOException;
import javax.annotation.Resource;
import javax.resource.ResourceException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jcademo.raoutbound.permtcp.TestConnection;
import jcademo.raoutbound.permtcp.TestConnectionFactory;
@WebServlet(urlPatterns={"/testpermtcp"})
public class TestPermTCP extends HttpServlet {
private static final long serialVersionUID = 1L;
@Resource(mappedName = "java:/eis/PermTCP")
private TestConnectionFactory cf;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
TestConnection con = cf.getConnection();
int c = con.add(123, 456);
con.close();
resp.getWriter().println(c);
} catch (ResourceException e) {
e.printStackTrace();
}
}
}
Example sending requests via UDP and receiving response via UDP.
The problem of having multiple managed connections and a single UDP port is solved by introducing am UDP multi-plexer that send requests and return responses on a Java BlockingQueue to each managed connection.
For intro to UDP in Java see Java examples here.
Connection interface:
package jcademo.raoutbound.udp;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
public interface TestConnection extends Connection {
public int add(int a, int b) throws ResourceException;
}
Connection implementation class:
package jcademo.raoutbound.udp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionMetaData;
import javax.resource.cci.Interaction;
import javax.resource.cci.LocalTransaction;
import javax.resource.cci.ResultSetInfo;
public class TestConnectionImpl implements TestConnection {
private UDPManagedConnection mc;
private boolean debug;
// create
public TestConnectionImpl(UDPManagedConnection mc, boolean debug) {
this.mc = mc;
this.debug = debug;
if(debug) {
System.out.println("Connection created");
}
}
// business logic
@Override
public int add(int a, int b) throws ResourceException {
return mc.add(a, b);
}
// close
@Override
public void close() {
if(debug) {
System.out.println("Connection closed");
}
mc.close();
mc = null;
}
// CCI support
@Override
public Interaction createInteraction() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
// result set info support
@Override
public ResultSetInfo getResultSetInfo() throws ResourceException {
throw new ResourceException("Result set info not supported");
}
}
Connection factory interface:
package jcademo.raoutbound.udp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
public interface TestConnectionFactory extends ConnectionFactory {
public TestConnection getConnection() throws ResourceException;
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException;
}
Connection factory implementation class:
package jcademo.raoutbound.udp;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResourceAdapterMetaData;
import javax.resource.spi.ConnectionManager;
public class TestConnectionFactoryImpl implements TestConnectionFactory {
private static final long serialVersionUID = 1L;
private UDPManagedConnectionFactory mcf;
private ConnectionManager cm;
// instantiate
public TestConnectionFactoryImpl() {
this(null, null);
}
public TestConnectionFactoryImpl(UDPManagedConnectionFactory mcf, ConnectionManager cm) {
this.mcf = mcf;
this.cm = cm;
}
// get connection from connection manager
@Override
public TestConnection getConnection() throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
@Override
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
// CCI support
@Override
public RecordFactory getRecordFactory() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// reference
private Reference ref;
@Override
public Reference getReference() throws NamingException {
return ref;
}
@Override
public void setReference(Reference ref) {
this.ref = ref;
}
// meta data support
@Override
public ResourceAdapterMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection class:
package jcademo.raoutbound.udp;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
public class UDPManagedConnection implements ManagedConnection {
private boolean debug;
private String id;
private BlockingQueue<String> q;
private TestConnection con;
// create
public UDPManagedConnection(boolean debug) throws ResourceException {
this.debug = debug;
id = UUID.randomUUID().toString();
q = new ArrayBlockingQueue<String>(10);
UDPMultiPlexer.getInstance().register(id, q);
if(debug) {
System.out.println("Managed connection created");
}
}
// business logic
public int add(int a, int b) throws ResourceException {
try {
UDPMultiPlexer.getInstance().send(id, a + " " + b);
String line = q.take();
int c = Integer.parseInt(line);
return c;
} catch (NumberFormatException e) {
throw new ResourceException(e);
} catch (InterruptedException e) {
throw new ResourceException(e);
} catch (IOException e) {
throw new ResourceException(e);
}
}
// called by Connection.close => informs connection manager that connection is closed
public void close() {
for(ConnectionEventListener cel : cellst) {
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(con);
cel.connectionClosed(ce);
}
}
// called by connection manager to destroy this instance when adapter is shutting down or pool size is decreased
@Override
public void destroy() throws ResourceException {
UDPMultiPlexer.getInstance().deregister(id);
if(debug) {
System.out.println("Managed connection destroyed");
}
}
// called by connection manager when it is informed that connection is closed
@Override
public void cleanup() {
con = null;
}
// get connection and save connection reference
@Override
public TestConnection getConnection(Subject subj, ConnectionRequestInfo cri) {
con = new TestConnectionImpl(this, debug);
return con;
}
// change connection reference
@Override
public void associateConnection(Object con) {
this.con = (TestConnection)con;
}
// manage listeners (used by connection manager)
private List<ConnectionEventListener> cellst = new ArrayList<>();
@Override
public void addConnectionEventListener(ConnectionEventListener cel) {
cellst.add(cel);
}
@Override
public void removeConnectionEventListener(ConnectionEventListener cel) {
cellst.remove(cel);
}
// log support:
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() throws ResourceException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
@Override
public XAResource getXAResource() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ManagedConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection factory class:
package jcademo.raoutbound.udp;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
public class UDPManagedConnectionFactory implements ManagedConnectionFactory {
private static final long serialVersionUID = 1L;
// config properties
private Boolean debug;
public Boolean getDebug() {
return debug;
}
public void setDebug(Boolean debug) {
this.debug = debug;
}
// get connection factory
@Override
public ConnectionFactory createConnectionFactory(ConnectionManager cm) {
return new TestConnectionFactoryImpl(this, cm);
}
@Override
public ConnectionFactory createConnectionFactory() throws ResourceException {
throw new ResourceException("Non-managed not supported");
}
// get managed connection
@Override
public ManagedConnection createManagedConnection(Subject subj, ConnectionRequestInfo cri) throws ResourceException {
return new UDPManagedConnection(debug);
}
// pick a connection from set when asked by connection manager
@Override
public ManagedConnection matchManagedConnections(@SuppressWarnings("rawtypes") Set pool, Subject subj, ConnectionRequestInfo cri) {
@SuppressWarnings("rawtypes") Iterator it = pool.iterator();
while(it.hasNext()) {
ManagedConnection mc = (ManagedConnection)it.next();
if(mc instanceof UDPManagedConnection) {
return mc;
}
}
return null;
}
// required by some specification
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
// log support
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
}
Resource adapter class:
package jcademo.raoutbound.udp;
import java.io.IOException;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkManager;
import javax.transaction.xa.XAResource;
// JCA Adapter
public class UDPAdapter implements ResourceAdapter {
private String host;
private int port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
private Work w;
@Override
public void start(BootstrapContext bctx) {
WorkManager wm = bctx.getWorkManager();
try {
UDPMultiPlexer.getInstance().associate(host, port);
w = new UDPWorker();
wm.scheduleWork(w);
} catch (WorkException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void endpointActivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void endpointDeactivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void stop() {
w.release();
}
@Override
public XAResource[] getXAResources(ActivationSpec[] as) throws ResourceException {
throw new ResourceException("no XA support");
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
}
Utility classes:
package jcademo.raoutbound.udp;
import java.io.IOException;
import javax.resource.spi.work.Work;
public class UDPWorker implements Work {
private boolean done;
@Override
public void run() {
done = false;
while(!done) {
try {
UDPMultiPlexer.getInstance().receive();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void release() {
try {
done = true;
UDPMultiPlexer.getInstance().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package jcademo.raoutbound.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
// utility class to send and receive a bundle of lines (id + numbers to add) + client address + client port
public class UDPUtil {
public static class DataAndDestination {
private String data;
private InetAddress address;
private int port;
public DataAndDestination(String data, InetAddress address, int port) {
this.data = data;
this.address = address;
this.port = port;
}
public String getData() {
return data;
}
public InetAddress getAddress() {
return address;
}
public int getPort() {
return port;
}
}
public static DataAndDestination receive(DatagramSocket dgs) throws IOException {
byte[] b = new byte[1000];
DatagramPacket dp = new DatagramPacket(b, b.length);
synchronized(dgs) {
dgs.receive(dp);
}
return new DataAndDestination(new String(dp.getData(), 0, dp.getLength(), "UTF-8"), dp.getAddress(), dp.getPort());
}
public static void send(DatagramSocket dgs, DataAndDestination dad) throws IOException {
byte[] b = dad.getData().getBytes("UTF-8");
DatagramPacket dp = new DatagramPacket(b, b.length);
dp.setAddress(dad.getAddress());
dp.setPort(dad.getPort());
synchronized(dgs) {
dgs.send(dp);
}
}
}
package jcademo.raoutbound.udp;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
public class UDPMultiPlexer {
// singleton
private static UDPMultiPlexer instance = new UDPMultiPlexer();
public static UDPMultiPlexer getInstance() {
return instance;
}
// data
private InetAddress addr;
private int port;
private DatagramSocket dgs;
private Map<String, BlockingQueue<String>> qmap = new HashMap<String, BlockingQueue<String>>();
// connect
public void associate(String host, int port) throws IOException {
addr = InetAddress.getByName(host);
this.port = port;
dgs = new DatagramSocket();
}
// register receive queue
public void register(String id, BlockingQueue<String> q) {
synchronized(qmap) {
qmap.put(id, q);
}
}
// send
public void send(String id, String line) throws IOException {
UDPUtil.send(dgs, new UDPUtil.DataAndDestination(id + "\r\n" + line, addr, port));
}
// deregister receive queue
public void deregister(String id) {
synchronized(qmap) {
qmap.remove(id);
}
}
// close
public void close() throws IOException {
dgs.close();
}
// receive loop that put responses in correct queue
public void receive() throws IOException, InterruptedException {
while(true) {
UDPUtil.DataAndDestination dad = UDPUtil.receive(dgs);
String[] parts = dad.getData().split("\r\n");
String id = parts[0];
String line = parts[1];
qmap.get(id).put(line);
}
}
}
Resource adapter descriptor (ra.xml):
<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/connector_1_6.xsd"
version="1.6">
<!--
JCA Adaptor: Outbound UDP
ManagedConnectionFactory class: jcademo.raoutbound.udp.UDPManagedConnectionFactory
property host = localhost
property port = 20004
property debug = true
Connectionfactory interface: jcademo.raoutbound.udp.TestConnectionFactory
Connectionfactory class: jcademo.raoutbound.udp.TestConnectionFactoryImpl
Connection interface: jcademo.raoutbound.udp.TestConnection
Connection class: jcademo.raoutbound.udp.TestConnectionImpl
-->
<display-name>Outbound UDP</display-name>
<vendor-name>AV</vendor-name>
<eis-type>Demo</eis-type>
<resourceadapter-version>1.0</resourceadapter-version>
<resourceadapter>
<resourceadapter-class>jcademo.raoutbound.udp.UDPAdapter</resourceadapter-class>
<config-property>
<config-property-name>host</config-property-name>
<config-property-type>java.lang.String</config-property-type>
<config-property-value>localhost</config-property-value>
</config-property>
<config-property>
<config-property-name>port</config-property-name>
<config-property-type>java.lang.Integer</config-property-type>
<config-property-value>20004</config-property-value>
</config-property>
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>jcademo.raoutbound.udp.UDPManagedConnectionFactory</managedconnectionfactory-class>
<config-property>
<config-property-name>debug</config-property-name>
<config-property-type>java.lang.Boolean</config-property-type>
<config-property-value>true</config-property-value>
</config-property>
<connectionfactory-interface>jcademo.raoutbound.udp.TestConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>jcademo.raoutbound.udp.TestConnectionFactoryImpl</connectionfactory-impl-class>
<connection-interface>jcademo.raoutbound.udp.TestConnection</connection-interface>
<connection-impl-class>jcademo.raoutbound.udp.TestConnectionImpl</connection-impl-class>
</connection-definition>
<transaction-support>NoTransaction</transaction-support>
</outbound-resourceadapter>
</resourceadapter>
</connector>
Binding for WildFly/JBoss (ironjacamar.xml):
<?xml version="1.0" encoding="UTF-8"?>
<ironjacamar>
<connection-definitions>
<connection-definition class-name="jcademo.raoutbound.udp.UDPManagedConnectionFactory" jndi-name="java:/eis/UDP">
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>10</max-pool-size>
<prefill>true</prefill>
<use-strict-min>true</use-strict-min>
<capacity>
<decrementer class-name="org.jboss.jca.core.connectionmanager.pool.capacity.SizeDecrementer">
<config-property name="size">1</config-property>
</decrementer>
</capacity>
</pool>
<timeout>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>4</idle-timeout-minutes>
<allocation-retry>2</allocation-retry>
<allocation-retry-wait-millis>3000</allocation-retry-wait-millis>
</timeout>
</connection-definition>
</connection-definitions>
</ironjacamar>
Test client:
package jcademo.client;
import java.io.IOException;
import javax.annotation.Resource;
import javax.resource.ResourceException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jcademo.raoutbound.udp.TestConnection;
import jcademo.raoutbound.udp.TestConnectionFactory;
@WebServlet(urlPatterns={"/testudp"})
public class TestUDP extends HttpServlet {
private static final long serialVersionUID = 1L;
@Resource(mappedName = "java:/eis/UDP")
private TestConnectionFactory cf;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
TestConnection con = cf.getConnection();
int c = con.add(123, 456);
con.close();
resp.getWriter().println(c);
} catch (ResourceException e) {
e.printStackTrace();
}
}
}
Example sending requests via TCP and receiving response via TCP (multiple connections, multiple requests per connection) with the actual TCP communication done in C.
For intro to TCP in C see C examples here.
Connection interface:
package jcademo.raoutbound.natmultcp;
import javax.resource.ResourceException;
import javax.resource.cci.Connection;
public interface TestConnection extends Connection {
public int add(int a, int b) throws ResourceException;
}
Connection implementation class:
package jcademo.raoutbound.natmultcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionMetaData;
import javax.resource.cci.Interaction;
import javax.resource.cci.LocalTransaction;
import javax.resource.cci.ResultSetInfo;
public class TestConnectionImpl implements TestConnection {
private NatMulTCPManagedConnection mc;
private boolean debug;
// create
public TestConnectionImpl(NatMulTCPManagedConnection mc, boolean debug) {
this.mc = mc;
this.debug = debug;
if(debug) {
System.out.println("Connection created");
}
}
// business logic
@Override
public int add(int a, int b) throws ResourceException {
return mc.add(a, b);
}
// close
@Override
public void close() {
if(debug) {
System.out.println("Connection closed");
}
mc.close();
mc = null;
}
// CCI support
@Override
public Interaction createInteraction() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
// result set info support
@Override
public ResultSetInfo getResultSetInfo() throws ResourceException {
throw new ResourceException("Result set info not supported");
}
}
Connection factory interface:
package jcademo.raoutbound.natmultcp;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
public interface TestConnectionFactory extends ConnectionFactory {
public TestConnection getConnection() throws ResourceException;
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException;
}
Connection factory implementation class:
package jcademo.raoutbound.natmultcp;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionSpec;
import javax.resource.cci.RecordFactory;
import javax.resource.cci.ResourceAdapterMetaData;
import javax.resource.spi.ConnectionManager;
public class TestConnectionFactoryImpl implements TestConnectionFactory {
private static final long serialVersionUID = 1L;
private NatMulTCPManagedConnectionFactory mcf;
private ConnectionManager cm;
// instantiate
public TestConnectionFactoryImpl() {
this(null, null);
}
public TestConnectionFactoryImpl(NatMulTCPManagedConnectionFactory mcf, ConnectionManager cm) {
this.mcf = mcf;
this.cm = cm;
}
// get connection from connection manager
@Override
public TestConnection getConnection() throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
@Override
public TestConnection getConnection(ConnectionSpec cs) throws ResourceException {
return (TestConnection)cm.allocateConnection(mcf, null);
}
// CCI support
@Override
public RecordFactory getRecordFactory() throws ResourceException {
throw new ResourceException("CCI not supported");
}
// reference
private Reference ref;
@Override
public Reference getReference() throws NamingException {
return ref;
}
@Override
public void setReference(Reference ref) {
this.ref = ref;
}
// meta data support
@Override
public ResourceAdapterMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection class:
package jcademo.raoutbound.natmultcp;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.ConnectionEventListener;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionMetaData;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;
public class NatMulTCPManagedConnection implements ManagedConnection {
private boolean debug;
private TestConnection con;
private long sd;
// create
public NatMulTCPManagedConnection(String host, int port, boolean debug) throws ResourceException {
this.debug = debug;
sd = NatMulTCP.open(host, port);
if(sd == 0) {
throw new ResourceException("NatMulTCP.open returned 0");
}
if(debug) {
System.out.printf("Managed connection created (%s:%d)\n", host, port);
}
}
// business logic
public int add(int a, int b) throws ResourceException {
if(!NatMulTCP.send(sd, a + " " + b + "\r\n")) { // add \r\n
throw new ResourceException("NatMulTCP.send returned false");
}
String line = NatMulTCP.receive(sd);
if(line == null) {
throw new ResourceException("NatMulTCP.receive returned null");
}
line = line.substring(0, line.length() - 2); // drop \r\n
int c = Integer.parseInt(line);
return c;
}
// called by Connection.close => informs connection manager that connection is closed
public void close() {
for(ConnectionEventListener cel : cellst) {
ConnectionEvent ce = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
ce.setConnectionHandle(con);
cel.connectionClosed(ce);
}
}
// called by connection manager to destroy this instance when adapter is shutting down or pool size is decreased
@Override
public void destroy() throws ResourceException {
NatMulTCP.close(sd);
if(debug) {
System.out.println("Managed connection destroyed");
}
}
// called by connection manager when it is informed that connection is closed
@Override
public void cleanup() {
con = null;
}
// get connection and save connection reference
@Override
public TestConnection getConnection(Subject subj, ConnectionRequestInfo cri) {
con = new TestConnectionImpl(this, debug);
return con;
}
// change connection reference
@Override
public void associateConnection(Object con) {
this.con = (TestConnection)con;
}
// manage listeners (used by connection manager)
private List<ConnectionEventListener> cellst = new ArrayList<>();
@Override
public void addConnectionEventListener(ConnectionEventListener cel) {
cellst.add(cel);
}
@Override
public void removeConnectionEventListener(ConnectionEventListener cel) {
cellst.remove(cel);
}
// log support:
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() throws ResourceException {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
// transaction support
@Override
public LocalTransaction getLocalTransaction() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
@Override
public XAResource getXAResource() throws ResourceException {
throw new ResourceException("Transaction not supported");
}
// meta data support
@Override
public ManagedConnectionMetaData getMetaData() throws ResourceException {
throw new ResourceException("Meta data not supported");
}
}
Managed connection factory class:
package jcademo.raoutbound.natmultcp;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionFactory;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;
public class NatMulTCPManagedConnectionFactory implements ManagedConnectionFactory {
private static final long serialVersionUID = 1L;
// config properties
private String host;
private Integer port;
private Boolean debug;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Boolean getDebug() {
return debug;
}
public void setDebug(Boolean debug) {
this.debug = debug;
}
// get connection factory
@Override
public ConnectionFactory createConnectionFactory(ConnectionManager cm) {
return new TestConnectionFactoryImpl(this, cm);
}
@Override
public ConnectionFactory createConnectionFactory() throws ResourceException {
throw new ResourceException("Non-managed not supported");
}
// get managed connection
@Override
public ManagedConnection createManagedConnection(Subject subj, ConnectionRequestInfo cri) throws ResourceException {
return new NatMulTCPManagedConnection(host, port, debug);
}
// pick a connection from set when asked by connection manager
@Override
public ManagedConnection matchManagedConnections(@SuppressWarnings("rawtypes") Set pool, Subject subj, ConnectionRequestInfo cri) {
@SuppressWarnings("rawtypes") Iterator it = pool.iterator();
while(it.hasNext()) {
ManagedConnection mc = (ManagedConnection)it.next();
if(mc instanceof NatMulTCPManagedConnection) {
return mc;
}
}
return null;
}
// required by some specification
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
// log support
private PrintWriter logWriter;
@Override
public PrintWriter getLogWriter() {
return logWriter;
}
@Override
public void setLogWriter(PrintWriter logWriter) {
this.logWriter = logWriter;
}
}
Resource adapter class:
package jcademo.raoutbound.natmultcp;
import javax.resource.ResourceException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.xa.XAResource;
// JCA Adapter
public class NatMulTCPAdapter implements ResourceAdapter {
@Override
public void start(BootstrapContext bctx) {
}
@Override
public void endpointActivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void endpointDeactivation(MessageEndpointFactory mepf, ActivationSpec as) {
}
@Override
public void stop() {
}
@Override
public XAResource[] getXAResources(ActivationSpec[] as) throws ResourceException {
throw new ResourceException("no XA support");
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object o) {
return this == o;
}
}
Native Java class:
package jcademo.raoutbound.natmultcp;
public class NatMulTCP {
static {
try {
System.loadLibrary("NatMulTCP");
} catch(Throwable t) {
t.printStackTrace();
}
}
native public static long open(String host, int port);
native public static boolean send(long sd, String msg);
native public static String receive(long sd);
native public static void close(long sd);
}
Native C code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <errno.h>
#include "jcademo_raoutbound_natmultcp_NatMulTCP.h"
JNIEXPORT jlong JNICALL Java_jcademo_raoutbound_natmultcp_NatMulTCP_open(JNIEnv *cntx, jclass jc, jstring host, jint port)
{
int sd, status;
const char *chost;
char cport[6];
struct addrinfo hints, *res;
#ifdef WIN32
WSADATA WSAData;
WSAStartup(0x0101, &WSAData);
#endif
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = 0;
chost = (*cntx)->GetStringUTFChars(cntx, host, 0);
itoa(port, cport, 10);
status = getaddrinfo(chost, cport, &hints, &res);
(*cntx)->ReleaseStringUTFChars(cntx, host, chost);
if(status != 0) return -1;
sd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(sd < 0) return -1;
status = connect(sd, res->ai_addr, res->ai_addrlen);
if(status != 0) return -1;
freeaddrinfo(res);
return sd;
}
static char buf[1000];
JNIEXPORT jboolean JNICALL Java_jcademo_raoutbound_natmultcp_NatMulTCP_send(JNIEnv *cntx, jclass jc, jlong sd, jstring msg)
{
int status;
const char *cmsg;
cmsg = (*cntx)->GetStringUTFChars(cntx, msg, 0);
strcpy(buf, cmsg); // move message to non-JNI memory
status = send(sd, (char *)&buf, strlen(buf), 0);
(*cntx)->ReleaseStringUTFChars(cntx, msg, cmsg);
if(status < 0) return 0;
return 1;
}
JNIEXPORT jstring JNICALL Java_jcademo_raoutbound_natmultcp_NatMulTCP_receive(JNIEnv *cntx, jclass jc, jlong sd)
{
int ix;
char res[100];
strcpy(res, "");
ix = 0;
while(strstr(res, "\r\n") == NULL && ix < sizeof(res)) {
ix = ix + recv(sd, res + ix, sizeof(res) - ix, 0);
res[ix] = '\0';
}
return (*cntx)->NewStringUTF(cntx, res);
}
JNIEXPORT void JNICALL Java_jcademo_raoutbound_natmultcp_NatMulTCP_close(JNIEnv *cntx, jclass jc, jlong sd)
{
#ifdef WIN32
closesocket(sd);
WSACleanup();
#else
close(sd);
#endif
}
Resource adapter descriptor (ra.xml):
<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/connector_1_6.xsd"
version="1.6">
<!--
JCA Adaptor: Outbound native multi TCP
ManagedConnectionFactory class: jcademo.raoutbound.natmultcp.NatMulTCPManagedConnectionFactory
property host = localhost
property port = 20001
property debug = true
Connectionfactory interface: jcademo.raoutbound.natmultcp.TestConnectionFactory
Connectionfactory class: jcademo.raoutbound.natmultcp.TestConnectionFactoryImpl
Connection interface: jcademo.raoutbound.natmultcp.TestConnection
Connection class: jcademo.raoutbound.natmultcp.TestConnectionImpl
-->
<display-name>Outbound native multi TCP</display-name>
<vendor-name>AV</vendor-name>
<eis-type>Demo</eis-type>
<resourceadapter-version>1.0</resourceadapter-version>
<resourceadapter>
<resourceadapter-class>jcademo.raoutbound.natmultcp.NatMulTCPAdapter</resourceadapter-class>
<outbound-resourceadapter>
<connection-definition>
<managedconnectionfactory-class>jcademo.raoutbound.natmultcp.NatMulTCPManagedConnectionFactory</managedconnectionfactory-class>
<config-property>
<config-property-name>host</config-property-name>
<config-property-type>java.lang.String</config-property-type>
<config-property-value>localhost</config-property-value>
</config-property>
<config-property>
<config-property-name>port</config-property-name>
<config-property-type>java.lang.Integer</config-property-type>
<config-property-value>20001</config-property-value>
</config-property>
<config-property>
<config-property-name>debug</config-property-name>
<config-property-type>java.lang.Boolean</config-property-type>
<config-property-value>true</config-property-value>
</config-property>
<connectionfactory-interface>jcademo.raoutbound.natmultcp.TestConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>jcademo.raoutbound.natmultcp.TestConnectionFactoryImpl</connectionfactory-impl-class>
<connection-interface>jcademo.raoutbound.natmultcp.TestConnection</connection-interface>
<connection-impl-class>jcademo.raoutbound.natmultcp.TestConnectionImpl</connection-impl-class>
</connection-definition>
<transaction-support>NoTransaction</transaction-support>
</outbound-resourceadapter>
</resourceadapter>
</connector>
Binding for WildFly/JBoss (ironjacamar.xml):
<?xml version="1.0" encoding="UTF-8"?>
<ironjacamar>
<connection-definitions>
<connection-definition class-name="jcademo.raoutbound.natmultcp.NatMulTCPManagedConnectionFactory" jndi-name="java:/eis/NatMulTCP">
<pool>
<min-pool-size>5</min-pool-size>
<max-pool-size>10</max-pool-size>
<prefill>true</prefill>
<use-strict-min>true</use-strict-min>
<capacity>
<decrementer class-name="org.jboss.jca.core.connectionmanager.pool.capacity.SizeDecrementer">
<config-property name="size">1</config-property>
</decrementer>
</capacity>
</pool>
<timeout>
<blocking-timeout-millis>5000</blocking-timeout-millis>
<idle-timeout-minutes>4</idle-timeout-minutes>
<allocation-retry>2</allocation-retry>
<allocation-retry-wait-millis>3000</allocation-retry-wait-millis>
</timeout>
</connection-definition>
</connection-definitions>
</ironjacamar>
Test client:
package jcademo.client;
import java.io.IOException;
import javax.annotation.Resource;
import javax.resource.ResourceException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jcademo.raoutbound.natmultcp.TestConnection;
import jcademo.raoutbound.natmultcp.TestConnectionFactory;
@WebServlet(urlPatterns={"/testnatmultcp"})
public class TestNatMulTCP extends HttpServlet {
private static final long serialVersionUID = 1L;
@Resource(mappedName = "java:/eis/NatMulTCP")
private TestConnectionFactory cf;
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
TestConnection con = cf.getConnection();
int c = con.add(123, 456);
con.close();
resp.getWriter().println(c);
} catch (ResourceException e) {
e.printStackTrace();
}
}
}
Obviously there is no reason to use C just to use TCP - the socket API in Java is much more convenient than in C. But using this example allows this code to be functional equivalent to previous examples.
Here is an example (Multi TCP).
multcp.jar:
0 Sun Jun 23 21:24:26 EDT 2019 META-INF/ 105 Sun Jun 23 21:24:24 EDT 2019 META-INF/MANIFEST.MF 0 Fri Jun 21 20:38:42 EDT 2019 jcademo/ 0 Fri Jun 21 20:38:42 EDT 2019 jcademo/raoutbound/ 0 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/ 1550 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/MulTCPAdapter.class 5418 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/MulTCPManagedConnection.class 3741 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/MulTCPManagedConnectionFactory.class 255 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/TestConnection.class 725 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/TestConnectionFactory.class 2417 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/TestConnectionFactoryImpl.class 1856 Fri Jun 21 20:38:44 EDT 2019 jcademo/raoutbound/multcp/TestConnectionImpl.class
test-multcp.rar:
0 Sun Jun 23 21:24:26 EDT 2019 META-INF/ 105 Sun Jun 23 21:24:24 EDT 2019 META-INF/MANIFEST.MF 8677 Sun Jun 23 21:24:26 EDT 2019 multcp.jar 1145 Fri May 10 22:19:16 EDT 2019 META-INF/ironjacamar.xml 2948 Fri May 10 19:35:52 EDT 2019 META-INF/ra.xml
test.war:
0 Sun Jun 23 21:24:26 EDT 2019 META-INF/ 105 Sun Jun 23 21:24:24 EDT 2019 META-INF/MANIFEST.MF 0 Sun Jun 23 21:24:26 EDT 2019 WEB-INF/ 268 Sun Apr 21 20:55:40 EDT 2019 WEB-INF/web.xml 0 Sun Jun 23 21:24:26 EDT 2019 WEB-INF/classes/ 0 Fri Jun 21 20:38:42 EDT 2019 WEB-INF/classes/jcademo/ 0 Fri Jun 21 20:38:44 EDT 2019 WEB-INF/classes/jcademo/client/ 1612 Fri Jun 21 20:38:44 EDT 2019 WEB-INF/classes/jcademo/client/TestMulTCP.class 613 Fri May 17 21:23:22 EDT 2019 WEB-INF/jboss-deployment-structure.xml
test.ear:
0 Sun Jun 23 21:24:28 EDT 2019 META-INF/ 105 Sun Jun 23 21:24:26 EDT 2019 META-INF/MANIFEST.MF 898 Sun May 19 20:50:50 EDT 2019 META-INF/application.xml 9121 Sun Jun 23 21:24:26 EDT 2019 test-multcp.rar 7899 Sun Jun 23 21:24:26 EDT 2019 test.war
Version | Date | Description |
---|---|---|
1.0 | May 22nd 2019 | Initial version |
See list of all articles here
Please send comments to Arne Vajhøj