Java Champion / JavaOne Rockstar Adam Bien (adam-bien.com) is a self-employed consultant, lecturer, software architect, developer, and author in the enterprise Java sector. He is also the author of several books and articles on Java and Java EE technology, as well as distributed Java programming. adam has posted 59 posts at DZone. View Full User Profile

A Simple Transactional File JCA 1.5 Connector

09.10.2010
| 7881 views |
  • submit to reddit

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, here's how to build 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 is the 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.

From http://www.adam-bien.com/roller/abien/entry/a_simple_transactional_file_jca

Published at DZone with permission of its author, adam bien.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Henk De Boer replied on Sun, 2010/09/12 - 1:07am

JCA 1.6 connectors are lot simpler and more elegant - stay tuned.

Sounds interesting :)

King Sam replied on Fri, 2012/02/24 - 9:33am

Really interesting! Thanks for sharing it.

But how can it be modified to access a file in a given directory, passing the file name at run-time? Like a simple file uploader? Or better as a document manager: the system builds a document for the user, and the document must be archived in a fixed directory with a name composed from various parameters (as request type, user-id, etc.).

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.