📜 ⬆️ ⬇️

Java EE, JCA and jNode 2.X announce


Good day,% username%.
I’ll say right away that this post is about 99% Java EE Connector Architecture, with code examples. From where 1% about Fidonet undertook you will understand at the very end.

Summary for the lazy
JMS and JCA are related, inbox accepts MessageDrivenBean, outgoing messages are sent via ConnectionFactory.
The minimum packet for an incoming connection is 4 classes, for an outgoing connection - 8 classes and an adapter setting on the server side of the application.
Further details and pain


To begin with - the background and the solution of the business problem.
')

Formulation of the problem


I was given the task of integrating an existing business system (“System A”) with another system that was developed many years ago and understands only its own data transfer protocol (“System B”). It is impossible to modify other people's systems, respectively, the task has been reduced to writing a certain bus / proxy. Integration consists of sending messages back and forth with the conversion from one format to another in the process.

System “A” had many modern integration mechanisms, web services were recognized as the easiest to use. Under this case, the standard integration skeleton for JEE - JAX-WS + EJB + JMS was promptly filed down to ensure the delivery of the message.
But there were no standard tools for working with system “B”. Timid attempts to work with the network from the context of EJB did not succeed, Google suggested two solutions to the problem: crutches servlets for working with non-http or writing a JCA adapter. It is clear that the second path was chosen - I hadn’t worked with JCA before, and it’s always interesting to learn something new.

Study


Having started digging Google, I broke off pretty badly. Everywhere they wrote WHAT exactly you need to do (connector, manager, adapter, etc.), but almost never wrote HOW to do it. The standard way "to look at someone else's code and understand the process" failed - someone else's code was such a miser that it was impossible for me to understand something.

Two things saved me: JSR 322 and the only google adapter on google code . Actually, this was the starting point - having tightened the examples from jca-sockets and opening the pdf, I began to understand and understand how it actually works by scientific tyke.

Having spent about 16 hours on research and experiments, I found out the following:

The JCA module has two independent parts: Inbox and Outbox. These parts can be both together and separately. Moreover, there may be several. The module itself is registered by the class implementing javax.resource.spi.ResourceAdapter and specified in META-INF / ra.xml , while the ResourceAdapter is needed first of all to work with Inbox; For Outgoing, the adapter does nothing and its skeleton can even not be filled.

Inbox


The inbound channel is bound to MessageEndpoint (usually @MessageDrivenBean ; yes, JCA is JMS guts) and activated by ActivationSpec .
META-INF / ra.xml - description of ResourceAdapter and inbound streams
ra.xml
<connector xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/connector_1_7.xsd" version="1.7" metadata-complete="true"> <vendor-name>xxx-services</vendor-name> <eis-type>FidoNet</eis-type> <resourceadapter-version>2.5</resourceadapter-version> <resourceadapter> <!-- ,   javax.resource.spi.ResourceAdapter; config-property - ,     / --> <resourceadapter-class>in.fidonode.binkp.ra.BinkpServerResourceAdapter</resourceadapter-class> <config-property> <config-property-name>version</config-property-name> <config-property-type>java.lang.String</config-property-type> <config-property-value>jnode-jee 2.5 binkp/1.1</config-property-value> </config-property> <!--    --> <inbound-resourceadapter> <messageadapter> <messagelistener> <!-- ,    @MessageDrivenBean  ,       --> <messagelistener-type>in.fidonode.binkp.ra.BinkpMessageListener</messagelistener-type> <activationspec> <!-- - ,    @ActivationConfigProperty,        --> <activationspec-class>in.fidonode.binkp.ra.BinkpActivationSpec</activationspec-class> <!--    --> <required-config-property> <config-property-name>listenPort</config-property-name> </required-config-property> <!--    --> <config-property> <config-property-name>listenPort</config-property-name> <config-property-type>java.lang.Integer</config-property-type> <config-property-value>24554</config-property-value> </config-property> </activationspec> </messagelistener> </messageadapter> </inbound-resourceadapter> </resourceadapter> </connector> 



The BinkpMessageListener interface is for clients and must be in the classpath;

I will bring it here:
 public interface BinkpMessageListener { public void onMessage(FidoMessage message); } 


Now consider the simplest implementation of ResourceAdapter
BinkpServerResourceAdapter.java
 public class BinkpServerResourceAdapter implements ResourceAdapter, Serializable { private static final long serialVersionUID = 1L; private static Logger log = Logger.getLogger(BinkpServerResourceAdapter.class .getName()); private ConcurrentHashMap<BinkpActivationSpec, BinkpEndpoint> activationMap = new ConcurrentHashMap<BinkpActivationSpec, BinkpEndpoint>(); private BootstrapContext ctx; private String version; @Override public void endpointActivation(MessageEndpointFactory endpointFactory, ActivationSpec spec) throws ResourceException { BinkpEndpoint activation = new BinkpEndpoint(ctx.getWorkManager(), (BinkpActivationSpec) spec, endpointFactory); activationMap.put((BinkpActivationSpec) spec, activation); activation.start(); log.info("endpointActivation(" + activation + ")"); } @Override public void endpointDeactivation(MessageEndpointFactory endpointFactory, ActivationSpec spec) { BinkpEndpoint activation = activationMap.remove(spec); if (activation != null) activation.stop(); log.info("endpointDeactivation(" + activation + ")"); } @Override public void start(BootstrapContext ctx) throws ResourceAdapterInternalException { this.ctx = ctx; log.info("start()"); } @Override public void stop() { for (BinkpEndpoint act : activationMap.values()) { act.stop(); } activationMap.clear(); log.info("stop()"); } @Override public XAResource[] getXAResources(ActivationSpec[] arg0) throws ResourceException { return null; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } } 



What's going on here? When a JCA module is loaded, an instance of the BinkpServerResourceAdapter class is created, its parameters are filled (in this case, the version field) and the start () method is called.
In fact, inside the start () method, you can do a lot of things, but in this example we simply save the context to get WorkManager 'from hereafter.

When the application server finds @MessageDrivenBean , it tries to find an adapter that sends messages to the interface that implements the bean. For JMS, this is a MessageListener , we have a BinkpMessageListener . An ActivationSpec is created (we have a BinkpActivationSpec that implements javax.resource.spi.ActivationSpec ), the fields in which are populated according to the data in the activationConfig, the MessageEndpointFactory is created and ResourceAdapter.endpointActivation () is called. In this function, you need to create the “server” that will accept incoming connections, whether it is a tcp / ip server or a stream to work with unix-socket, to create based on the config that was in MDB. The BinkpEndpoint class is that “server”.
BinkpEndpoint.java
 public class BinkpEndpoint implements Work, FidoMessageListener { private static final Logger logger = Logger.getLogger(BinkpEndpoint.class .getName()); private BinkpServer server; private final WorkManager workManager; private final MessageEndpointFactory messageEndpointFactory; public BinkpEndpoint(WorkManager workManager, BinkpActivationSpec activationSpec, MessageEndpointFactory messageEndpointFactory) { this.workManager = workManager; this.messageEndpointFactory = messageEndpointFactory; server = new BinkpServer(activationSpec.getListenPort(), this); } public void start() throws ResourceException { workManager.scheduleWork(this); } public void stop() { if (server != null) { server.stop(); } } /**  FidoMessageListener **/ @Override public Message incomingMessage(FidoMessage message) { String message = msg.encode(); BinkpMessageListener listener = (BinkpMessageListener) messageEndpointFactory .createEndpoint(null); listener.onMessage(message); } /**  Work **/ @Override public void run() { server.start(); } /**  Work **/ @Override public void release() { stop(); } } 



You may notice that some endpoint appears everywhere. I had some gag with it, so I will decipher:
Endpoint is what the “incoming” stream is listening to. This is what the endpointActication functions belong to.
MessageEndpoint is an MDB instance that processes a message. Obtained by calling MessageEndpointFactory.createEndpoint () (This function cannot be called from the main thread). It is easy to cast to the MDB interface.

Actually, everything. I will omit the BinkpServer implementation as unnecessary, but the principle should be clear, the minimum Incoming JCA is made of four classes (ResourceAdapter, MessageListener, ActivationSpec, Endpoint)

Creating an Endpoint and Inbound Processing:
 @MessageDriven(messageListenerInterface = BinkpMessageListener.class, activationConfig = { @ActivationConfigProperty(propertyName = "listenPort", propertyValue = "24554") }) public class ReceiveMessageBean implements BinkpMessageListener { @Override public void onMessage(FidoMessage msg) { // do smth with mesaage } } 


Outgoing



And here - everything is more fun, the minimum “Outgoing” JCA is already made of 8 classes, which is 2 times more than “Incoming”. But let's go in order.

META-INF / ra.xml - description of the ResourceAdapter and outbound streams

ra.xml
 <connector xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/connector_1_7.xsd" version="1.7" metadata-complete="true"> <vendor-name>xxx-services</vendor-name> <eis-type>FidoNet</eis-type> <resourceadapter-version>2.5</resourceadapter-version> <resourceadapter> <!-- ,   javax.resource.spi.ResourceAdapter; config-property - ,     / --> <resourceadapter-class>in.fidonode.binkp.ra.BinkpServerResourceAdapter</resourceadapter-class> <config-property> <config-property-name>version</config-property-name> <config-property-type>java.lang.String</config-property-type> <config-property-value>jnode-jee 2.5 binkp/1.1</config-property-value> </config-property> <!--   .    --> <outbound-resourceadapter> <connection-definition> <!--      JEE-,       --> <managedconnectionfactory-class>in.fidonode.binkp.ra.ManagedConnectionFactory</managedconnectionfactory-class> <!--  ,     .       classpath --> <connectionfactory-interface>in.fidonode.binkp.ra.ConnectionFactory</connectionfactory-interface> <connectionfactory-impl-class>in.fidonode.binkp.ra.ConnectionFactoryImpl</connectionfactory-impl-class> <!-- ,     .       classpath --> <connection-interface>in.fidonode.binkp.ra.Connection</connection-interface> <connection-impl-class>in.fidonode.binkp.ra.ConnectionImpl</connection-impl-class> </connection-definition> <!--       ,     --> <transaction-support>NoTransaction</transaction-support> <reauthentication-support>false</reauthentication-support> </outbound-resourceadapter> <!--    --> <inbound-resourceadapter> <!-- ... --> </inbound-resourceadapter> </resourceadapter> </connector> 



Interfaces Connection and ConnectionFactory are for clients and must be in the classpath. Immediately bring them here, there is nothing interesting.

I will not give BinkpClient :-)
 public interface Connection { public BinkpClient connect(String hostname, int port); } public interface ConnectionFactory { public Connection createConnection(); } 


Connections are Managed and Unmanaged. The first - with whistles, listeners and others, the second - without.
The class that implements the ManagedConnectionFactory must be able to create both types of connections.
ManagedConnectionFactory.java
 public class ManagedConnectionFactory implements javax.resource.spi.ManagedConnectionFactory { private PrintWriter logwriter; private static final long serialVersionUID = 1L; /** *    unmanaged- */ @Override public Object createConnectionFactory() throws ResourceException { return new ConnectionFactoryImpl(); } /** *  managed-  managed-connection */ @Override public Object createConnectionFactory(ConnectionManager cxManager) throws ResourceException { return new ManagedConnectionFactoryImpl(this, cxManager); } /** *  managed- */ @Override public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException { return new in.fidonode.binkp.ra.ManagedConnection(); } @Override public PrintWriter getLogWriter() throws ResourceException { return logwriter; } @SuppressWarnings("rawtypes") @Override public ManagedConnection matchManagedConnections(Set connectionSet, Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException { ManagedConnection result = null; Iterator it = connectionSet.iterator(); while (result == null && it.hasNext()) { ManagedConnection mc = (ManagedConnection) it.next(); if (mc instanceof in.fidonode.binkp.ra.ManagedConnection) { result = mc; } } return result; } @Override public void setLogWriter(PrintWriter out) throws ResourceException { logwriter = out; } } 



When an application requests a connector from a JEE server, the application server asks the ManagedConnectionFactory to create a ConnectionFactory and sends it to the application.

As you can see, ConnectionFactory can also be Managed and Unmanaged. In principle, all this can be reduced to one class, but it strongly depends on what and how we transfer, whether there are transactions, etc.
ConnectionFactoryIml just makes new ConnectionImpl () , but ManagedConnectionFactoryImpl is a bit more complicated:

ManagedConnectionFactoryImpl.java
 public class ManagedConnectionFactoryImpl implements ConnectionFactory { private ManagedConnectionFactory factory; private ConnectionManager manager; public ManagedConnectionFactoryImpl(ManagedConnectionFactory factory, ConnectionManager manager) { super(); this.factory = factory; this.manager = manager; } /**  managed-  -ManagedConnectionFactory **/ @Override public Connection createConnection() { try { return (Connection) manager.allocateConnection(factory, null); } catch (ResourceException e) { return null; } } } 



ManagedConnection , which implements javax.resource.spi.ManagedConnection, is a wrapper for the Connection interface, which adds a whistle and listeners. It is this class that returns ManagedConnectionFactory.createManagedConnection () , which we call when creating a connection from ManagedConnectionFactoryImpl.createConnection () via ConnectionManager.allocateConnection ()

ManagedConnection.java
 public class ManagedConnection implements javax.resource.spi.ManagedConnection { private PrintWriter logWriter; private Connection connection; private List<ConnectionEventListener> listeners; public ManagedConnection() { listeners = Collections .synchronizedList(new ArrayList<ConnectionEventListener>()); } @Override public void associateConnection(Object connection) throws ResourceException { if (connection != null && connection instanceof Connection) { this.connection = (Connection) connection; } } @Override public Object getConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException { if (connection == null) { connection = new ManagedConnectionImpl(); } return connection; } @Override public void cleanup() throws ResourceException { } @Override public void destroy() throws ResourceException { } @Override public PrintWriter getLogWriter() throws ResourceException { return logWriter; } @Override public ManagedConnectionMetaData getMetaData() throws ResourceException { throw new NotSupportedException(); } @Override public XAResource getXAResource() throws ResourceException { throw new NotSupportedException(); } @Override public LocalTransaction getLocalTransaction() throws ResourceException { return null; } @Override public void setLogWriter(PrintWriter out) throws ResourceException { logWriter = out; } @Override public void addConnectionEventListener(ConnectionEventListener listener) { if (listener != null) { listeners.add(listener); } } @Override public void removeConnectionEventListener(ConnectionEventListener listener) { if (listener != null) { listeners.remove(listener); } } } 



Well, now we come to the simplest - the implementation of the connection :-)
 public class ConnectionImpl implements Connection { @Override public BinkpClient connect(String hostname, int port) { return new BinkpClient(hostname, port); } } 


Outgoing call chain for establishing outgoing connection
ManagedConnectionFactory.createConnectionFactory ()
-> ManagedConnectionFactoryImpl.createConnection ()
-> onnectionManager.allocateConnection ()
---> ManagedConnectionFactory.createManagedConnection ()
----> ManagedConnection.getConnection ()
-----> ManagedConnectionImpl.connect ()

Well, do not forget to configure the application server to work with this adapter, and also specify jndi.

Code to call:
  private BinkpClient createBinkpClient(String host, int port) { ConnectionFactory cf = ((ConnectionFactory) new InitialContext().lookup("java:eis/BinkpConnectionFactory")); Connection conn = cf.getConnection(); return conn.connect(host, port); } 

And where does Fido?



And almost nothing. The fact is that the original task was not about binkp at all, but it was working, and therefore fell under the NDA. Therefore, having dealt with JCA and deciding that you need to write an article on Habré (by the way, looking back, I begin to understand why no one has written such an article. And this is still without transactions!), I froze the old idea - jnode fork for JEE servers to run the node in the form of a single ear. At one time, I didn’t have enough JCA knowledge to launch a project :-)

Under this all I wrote the above examples, and they even earned. So if you want to practice in java ee in general and refactoring from java se in particular, write letters and commit code. Yes, I still take points.

Thank you for your attention, stay with us. You can write typos in the comments, I have no doubt that there are dozens of them here.

Source: https://habr.com/ru/post/251131/


All Articles