adam bien's blog

A Simple Transactional File JCA 1.5 Connector (4 Classes / 2 Reusable) 📎

There is a common prejudice that JCA connectors have to be too complicated for day to day use. "Custom & lightweight" solutions are built instead, which are usually orders of magnitudes more complex, than a pragmatic JCA implementation. So, how to built one:

  • JCA connectors are deployed as .rar archives with internal structure similar to ejb-jar archives. You can reuse your existing packaging and just change the ending from .jar to .rar
  • Start with the ra.xml deployment descriptor. You "only" have to implement the elements listed in this deployment descriptor:
<connector xmlns="http://java.sun.com/xml/ns/j2ee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                   http://java.sun.com/xml/ns/j2ee/connector_1_5.xsd"
                     version="1.5">
                <display-name>Generic JCA</display-name>
                  <vendor-name>adam-bien.com</vendor-name>
                    <eis-type>Generic JCA</eis-type>
                      <resourceadapter-version>1.0</resourceadapter-version>
                        <resourceadapter>
                              <outbound-resourceadapter>
                                    <connection-definition>
                                          <managedconnectionfactory-class>...genericjca.GenericManagedConnectionFactory</managedconnectionfactory-class>
                                            <connectionfactory-interface>...genericjca.DataSource</connectionfactory-interface>
                                              <connectionfactory-impl-class>...genericjca.FileDataSource</connectionfactory-impl-class>
                                                <connection-interface>...genericjca.Connection</connection-interface>
                                                  <connection-impl-class>...genericjca.FileConnection</connection-impl-class>
                                                </connection-definition>
                                                  <transaction-support>LocalTransaction</transaction-support>
                                                    <authentication-mechanism>
                                                          <authentication-mechanism-type>BasicPassword</authentication-mechanism-type>
                                                            <credential-interface>javax.resource.spi.security.PasswordCredential</credential-interface>
                                                          </authentication-mechanism>
                                                            <reauthentication-support>false</reauthentication-support>
                                                          </outbound-resourceadapter>
                                                        </resourceadapter>

                                                      </connector>

                                                       

                                                      •  GenericManagedConnectionFactory and GenericManagedConnection are mostly reusable. These classes care about connection management. You will be able to manage a connector through e.g. the Glassfish admin console. The code is surprisingly simple - its mainly book keeping and logging. See  http://kenai.com/projects/javaee-patterns/, project GenericJCA.
                                                      • The "core" business logic resides in the FileConnection:
                                                      public class FileConnection implements Connection, LocalTransaction{

                                                              private String buffer;
                                                                private FileOutputStream fileOutputStream;
                                                                  private ConnectionRequestInfo connectionRequestInfo;
                                                                    public final static String FILE_NAME = "/temp/jcafile.txt";
                                                                      private GenericManagedConnection genericManagedConnection;
                                                                        private PrintWriter out;

                                                                            public FileConnection(PrintWriter out,GenericManagedConnection genericManagedConnection,ConnectionRequestInfo connectionRequestInfo) {
                                                                                  this.out = out;
                                                                                    this.genericManagedConnection = genericManagedConnection;
                                                                                      this.connectionRequestInfo = connectionRequestInfo;
                                                                                        this.initialize();
                                                                                      }

                                                                                          private void initialize(){
                                                                                                try {
                                                                                                      this.buffer = null;
                                                                                                        this.fileOutputStream = new FileOutputStream(FILE_NAME,true);
                                                                                                      } catch (FileNotFoundException ex) {
                                                                                                            Logger.getLogger(FileConnection.class.getName()).log(Level.SEVERE, null, ex);
                                                                                                              throw new IllegalStateException("Cannot initialize OutputStream: " + FILE_NAME);
                                                                                                            }

                                                                                                            }

                                                                                                                public void write(String content) {
                                                                                                                      this.buffer = content;
                                                                                                                    }

                                                                                                                        public void close() {
                                                                                                                                  this.genericManagedConnection.close();
                                                                                                                            }

                                                                                                                                public void destroy(){
                                                                                                                                      try {
                                                                                                                                            if(this.fileOutputStream != null)
                                                                                                                                                  this.fileOutputStream.close();
                                                                                                                                              this.fileOutputStream = null;
                                                                                                                                                this.buffer = null;
                                                                                                                                                 } catch (IOException ex) {
                                                                                                                                                      Logger.getLogger(FileConnection.class.getName()).log(Level.SEVERE, null, ex);
                                                                                                                                                        throw new IllegalStateException("Cannot close stream: " +ex,ex);
                                                                                                                                                      }
                                                                                                                                                    }

                                                                                                                                                        public void begin() throws ResourceException {
                                                                                                                                                              this.initialize();
                                                                                                                                                            }

                                                                                                                                                                public void commit() throws ResourceException {
                                                                                                                                                                      out.println("#FileConnection.commit "  +toString());
                                                                                                                                                                        try {
                                                                                                                                                                           this.fileOutputStream.write(this.buffer.getBytes());
                                                                                                                                                                             this.fileOutputStream.flush();
                                                                                                                                                                               this.fileOutputStream.close();
                                                                                                                                                                                } catch (IOException ex) {
                                                                                                                                                                                      Logger.getLogger(FileConnection.class.getName()).log(Level.SEVERE, null, ex);
                                                                                                                                                                                        throw new ResourceException(ex);
                                                                                                                                                                                      }
                                                                                                                                                                                    }

                                                                                                                                                                                        public void rollback() throws ResourceException {
                                                                                                                                                                                              out.println("#FileConnection.rollback  " +toString());
                                                                                                                                                                                                this.buffer = null;
                                                                                                                                                                                                  try {
                                                                                                                                                                                                        this.fileOutputStream.close();
                                                                                                                                                                                                      } catch (IOException ex) {
                                                                                                                                                                                                            Logger.getLogger(FileConnection.class.getName()).log(Level.SEVERE, null, ex);
                                                                                                                                                                                                              throw new ResourceException(ex);
                                                                                                                                                                                                            }


                                                                                                                                                                                                      The nice thing are transaction callbacks. You will be notified about the transaction progress by the container. At commit time, you just write the buffer to the file - in case of a rollback you have to clear the buffer. This sample is not fully transactional - because in general you will have to deal with corrupted files etc. - but it should be clear how it works.

                                                                                                                                                                                                      After installation, you will be able to inject the interface (and so the FileConnection) to your business logic.

                                                                                                                                                                                                      @Stateless

                                                                                                                                                                                                      public class JCAClientBean implements JCAClientRemote {

                                                                                                                                                                                                          @Resource(mappedName="jca/FileFactory")

                                                                                                                                                                                                          private DataSource dataSource;

                                                                                                                                                                                                       ...and the transactions will be propagated transparently for you.

                                                                                                                                                                                                      JCA connectors are not as lean as simple as EJB 3.1 or CDI,  but orders of magnitude more robust and more maintainable, than solutions and workarounds which are usually built instead.

                                                                                                                                                                                                      You will find a working example (tested with Glassfish v2) in http://kenai.com/projects/javaee-patterns/. See GenericJCA, GenericJCAAPI and JCAClient projects. I described this solution in dedicated chapter "Generic JCA", page 181 in Real World Java EE Patterns - Rethinking Best Practices".

                                                                                                                                                                                                      The example presented here is based on the ancient Java EE 5 technology. JCA 1.6 connectors are lot simpler and more elegant - stay tuned.