Wednesday

Develop your custom resource adapter with maven

The goal of the post is to publish a complete tutorial of developing a resource adapter using maven2. On the web we could found a few complete tutorial about developing resource adapter which will also help to understand the JCA specification. This current post may help the developer to agile development of JCA adapter. On the developing process we are going to use maven2 software managment tool and oc4j container to deploy the adpater and MDB client as consumer of the adapter.
For simlicity, we will going to build a inbound file adapter, which will get file periodically from some drive. An MDB (Message driven bean) will subscribe to the adapter to get file and manipulate the content of the file.
1) Building the resource adapter:
1.1)FileActivationSpec.java
JCA activation specification for the inbound JCA resource adapter. This activation specification contains the following configuration items:
- filePath
- fileExt
- pollingInterval
These configuration items are used at runtime by the JCA resource adapter to connect to the file system. An JCA activation specification must adhere to the JavaBean standards.
package com.blu.jca.file;

import javax.resource.spi.ActivationSpec;
import javax.resource.spi.InvalidPropertyException;
import javax.resource.spi.ResourceAdapter;
import javax.resource.ResourceException;
import java.io.Serializable;
import java.util.logging.Logger;


public class FileActivationSpec implements Serializable, ActivationSpec {
private static Logger logger = Logger.getLogger(FileActivationSpec.class.getName());

private ResourceAdapter resourceAdapter;
private String filePath;
private String fileExt;
private int pollingInterval;

public FileActivationSpec() {
logger.info("[Start] FileActivationSpec");
}

public void validate() throws InvalidPropertyException {
logger.info("[start] validation");
}

public ResourceAdapter getResourceAdapter() {
return resourceAdapter;
}

public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException {
this.resourceAdapter = resourceAdapter;
}

public String getFilePath() {
return filePath;
}

public void setFilePath(String filePath) {
this.filePath = filePath;
}

public String getFileExt() {
return fileExt;
}

public void setFileExt(String fileExt) {
this.fileExt = fileExt;
}

public int getPollingInterval() {
return pollingInterval;
}

public void setPollingInterval(int pollingInterval) {
this.pollingInterval = pollingInterval;
}
}

1.2)FileResourceAdapter.java: Custom JCA File Resource Adapter implementation.
package com.blu.jca.file;

import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ResourceAdapterInternalException;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.work.WorkManager;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.ResourceException;
import javax.resource.cci.MessageListener;
import javax.transaction.xa.XAResource;
import java.util.logging.Logger;

public class FileResourceAdapter implements ResourceAdapter {
private static Logger logger = Logger.getLogger(FileResourceAdapter.class.getName());

private WorkManager workManager;
public FileResourceAdapter() {
logger.info("[Start] FileResourceAdapter()");
}

public void start(BootstrapContext bootstrapContext) throws ResourceAdapterInternalException {
logger.info("[Start] start()");
workManager = bootstrapContext.getWorkManager();
}

public void stop() {
logger.info("[stop] stop()");
}

public void endpointActivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec) throws ResourceException {
logger.info("[start] endpointActivation");
MessageEndpoint endPoint =  messageEndpointFactory.createEndpoint(null);
if(endPoint instanceof MessageListener){
FileWork fileWork = new FileWork(activationSpec, endPoint);
workManager.scheduleWork(fileWork);
}
}

public void endpointDeactivation(MessageEndpointFactory messageEndpointFactory, ActivationSpec activationSpec) {
logger.info("[start] endpointDeactivation");
}

public XAResource[] getXAResources(ActivationSpec[] activationSpecs) throws ResourceException {
return new XAResource[0];
}
}

1.3)FileWork.java: A Work instance implementation for this JCA resource adapter that would be executed by a WorkManager upon submission.
package com.blu.jca.file;

import com.blu.jca.bean.FileMessageBean;

import javax.resource.spi.work.Work;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.cci.MessageListener;
import javax.resource.ResourceException;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.io.*;

public class FileWork implements Work {
private static Logger logger = Logger.getLogger(FileWork.class.getName());

private FileActivationSpec activationSpec;
//private MessageListener messageListener;
private FileMessageListener messageListener;
private boolean released;
public FileWork(ActivationSpec activationSpec, MessageEndpoint messageEndpoint) {
logger.info("[start] FileWork()");
this.activationSpec = (FileActivationSpec) activationSpec;
this.messageListener = (FileMessageListener) messageEndpoint;
this.released = false;
}

public void release() {
this.released = true;                            
}

public boolean isReleased() {
return released;
}

public void run() {
logger.info("[start] run()");
int pollingInterval = activationSpec.getPollingInterval();
//set polling interval in ms
if(pollingInterval < 5){
pollingInterval = 5000;
}else{
pollingInterval *=1000;
}
logger.info("[Set Polling interval in ms:]"+pollingInterval);
while(!isReleased()){
//start read file from directory
try {
logger.info("[Start reading from folder]");
File folder = new File(activationSpec.getFilePath());
if(folder.exists()&& folder.isDirectory()){
File[] files = folder.listFiles();
// process file
for(File file:files){
FileMessageBean fBean = new FileMessageBean();
fBean.setRecordName("Jca Standard file record");
fBean.setFileName("[fileName]:"+file.getName());
fBean.setRecordShortDescription("JCA file record which wrappes the file data");
FileMessage message = new FileMessage();
message.setFileMesssageBean(fBean);
logger.info("Read file data to wrap");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
BufferedReader bf = new BufferedReader(new InputStreamReader(fis));
String data;
StringBuffer sbuffer = new StringBuffer();
while( (data = bf.readLine())!= null ){
sbuffer.append(data);
}
fBean.setData(sbuffer.toString());
messageListener.onMessage(message);
// sleep
Thread.currentThread().sleep(pollingInterval);
} catch (FileNotFoundException e) {
logger.log(Level.SEVERE,e.getMessage(),e);
throw new RuntimeException("FileNotFoundException:"+e.getMessage());
} catch(IOException e){
logger.log(Level.SEVERE,e.getMessage(),e);
throw new RuntimeException("Error on reading file:"+e.getMessage());
}finally {
try {
fis.close();
} catch (IOException e) {
logger.log(Level.SEVERE,e.getMessage(),e);
throw new RuntimeException("Error on reading file:"+e.getMessage());
}
}
}
}
} catch (InterruptedException e) {
logger.info("[Eroor on Run]"+ e.getMessage());
throw new RuntimeException("error in Run"+e.getMessage());
}
}
}
}
1.4) FileMessageBean: Bean serves as standardized data wrapper for the EIS data. In this case this record wraps a message from a file system.
package com.blu.jca.bean;

public class FileMessageBean {
private String fileName;
private String data;
private String recordName;
private String recordShortDescription;

public String getFileName() {
return fileName;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}

public String getData() {
return data;
}

public void setData(String data) {
this.data = data;
}

public String getRecordName() {
return recordName;
}

public void setRecordName(String recordName) {
this.recordName = recordName;
}

public String getRecordShortDescription() {
return recordShortDescription;
}

public void setRecordShortDescription(String recordShortDescription) {
this.recordShortDescription = recordShortDescription;
}

public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

FileMessageBean that = (FileMessageBean) o;

if (data != null ? !data.equals(that.data) : that.data != null) return false;
if (fileName != null ? !fileName.equals(that.fileName) : that.fileName != null) return false;

return true;
}

public int hashCode() {
int result;
result = (fileName != null ? fileName.hashCode() : 0);
result = 31 * result + (data != null ? data.hashCode() : 0);
return result;
}
}
1.5) FileMessage.java: Also a wrpper bean to FileMessageBean.
package com.blu.jca.file;

import com.blu.jca.bean.FileMessageBean;

public class FileMessage {
private FileMessageBean fileMesssageBean;

public FileMessageBean getFileMesssageBean() {
return fileMesssageBean;
}

public void setFileMesssageBean(FileMessageBean fileMesssageBean) {
this.fileMesssageBean = fileMesssageBean;
}
}
1.6) FileMessageListener.java: It's a message listner for adapter.
package com.blu.jca.file;

import javax.resource.cci.MessageListener;

public interface FileMessageListener extends MessageListener {
void onMessage(FileMessage message);
}
1.7) pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.blu.jca.file</groupId>
<artifactId>jca-adapter</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>jca-adapter</name>
<url>http://maven.apache.org</url>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.resource</groupId>
<artifactId>connector15</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath><BPEL_Home>\lib\connector15.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.8) Generate rar file with deployment descriptors: 1.8.1) ra.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>File JCA Resource Adapter</display-name>
<vendor-name>Blue mountain international</vendor-name>
<eis-type>File Server</eis-type>
<resourceadapter-version>1.0</resourceadapter-version>
<resourceadapter>
<resourceadapter-class>com.blu.jca.file.FileResourceAdapter</resourceadapter-class>
<inbound-resourceadapter>
<messageadapter>
<messagelistener>
<!--<messagelistener-type>javax.resource.cci.MessageListener</messagelistener-type>-->
<messagelistener-type>com.blu.jca.file.FileMessageListener</messagelistener-type>
<activationspec>
<activationspec-class>com.blu.jca.file.FileActivationSpec</activationspec-class>
<required-config-property>
<config-property-name>filePath</config-property-name>
</required-config-property>
<required-config-property>
<config-property-name>fileExt</config-property-name>
</required-config-property>
<required-config-property>
<config-property-name>pollingInterval</config-property-name>
</required-config-property>
</activationspec>
</messagelistener>
</messageadapter>
</inbound-resourceadapter>
</resourceadapter>
</connector>
1.8.2) oc4j-ra.xml:
<?xml version="1.0" encoding="windows-1252" ?>
<oc4j-connector-factories xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.oracle.com/technology/oracleas/schema/oc4j-connector-factories-10_0.xsd"
schema-major-version="10" schema-minor-version="0">
<!-- When deployed the adapter you must define the name as FileResourceAdapter otherwise client will not found it-->
<connector-factory location="eis/FileResourceAdapter" connector-name="FileResourceAdapter">
<connection-pooling use="none"></connection-pooling>
<security-config use="none"></security-config>
</connector-factory>
</oc4j-connector-factories>
1.8.3) pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.blu.jca.file</groupId>
<artifactId>jca-rar</artifactId>
<packaging>rar</packaging>
<version>1.0</version>
<name>jca-rar</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>com.blu.jca.file</groupId>
<artifactId>jca-adapter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-rar-plugin</artifactId>
<configuration>
<includeJar>false</includeJar>
<!--<raXmlFile>src/main/resources/ra.xml</raXmlFile>-->
</configuration>
</plugin>
</plugins>
</build>
</project>
2) Deployment: During adapter deployment, the application server creates a new instance of ResourceAdapter and calls the start() method. ResourceAdapter may initialize any resources required for processing later. In the start() method, ResourceAdapter receives the BootStrapContext object, which can be used to retrieve the WorkManager. ResourceAdapter can use WorkManager to submit work, which in turn starts the work thread. The stop() method of ResourceAdapter is invoked when either the application server is shutting down or the resource adapter is being undeployed. following sequence diagram show even during resource adapter deployment. Deployment process is easy in oc4j, through web interface deploy the jca-rar-1.0.rar, during deployment provide resource adapter name as follows FileResourceAdapter. 3) Developing MDB client: 3.1) FileResourceAdapterClientMDBBean.java: EJB 3.0 Message Driven Bean (MDB) that acts as client for the File JCA Resource Adapter.
package com.blu.jca.client;

import oracle.j2ee.ejb.MessageDrivenDeployment;

import javax.resource.cci.MessageListener;
import javax.resource.cci.Record;
import javax.resource.ResourceException;
import javax.ejb.MessageDriven;
import javax.ejb.ActivationConfigProperty;
import java.util.logging.Logger;

import com.blu.jca.file.FileMessageListener;
import com.blu.jca.file.FileMessage;
import com.blu.jca.bean.FileMessageBean;
@MessageDriven(
messageListenerInterface=FileMessageListener.class,
activationConfig = {
@ActivationConfigProperty(
propertyName="filePath", propertyValue="D:\\JcaFolder"),
@ActivationConfigProperty(
propertyName="fileExt", propertyValue="*.*"),
@ActivationConfigProperty(
propertyName="pollingInterval", propertyValue="10")
})
/**
* Resource adapter name during deployment by web
* */
@MessageDrivenDeployment(resourceAdapter = "FileResourceAdapter")
public class FileResourceAdapterClientMDBBean implements  FileMessageListener {
private static Logger logger = Logger.getLogger(FileResourceAdapterClientMDBBean.class.getName());

public FileResourceAdapterClientMDBBean() {
logger.info("[start] FileResourceAdapterClientMDBBean");
}

public Record onMessage(Record record) throws ResourceException {
return record;
}

public void onMessage(FileMessage message) {
logger.info("[start]:File client MDB fileMessage OnMessage");
FileMessageBean fBean = message.getFileMesssageBean();

logger.info("[Record Name]:"+fBean.getRecordName());
logger.info("[Description:]:"+fBean.getRecordShortDescription());
logger.info("[File Name:]:"+fBean.getFileName());
logger.info("[File Data:]:"+fBean.getData());        
logger.info("[End:] On Message");        
}
}
3.2) pom.xml for generate ejb
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.blu.jca.ejb</groupId>
<artifactId>jca-ejb</artifactId>
<packaging>ear</packaging>
<version>1.0-SNAPSHOT</version>
<name>jca-ejb</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>com.blu.jca.client</groupId>
<artifactId>jca-client</artifactId>
<version>1.0-SNAPSHOT</version>
<type>ejb</type>
</dependency>
<dependency>
<groupId>com.blu.jca.file</groupId>
<artifactId>jca-adapter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<finalName>jcaEjb-client</finalName>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<displayName>JCA ejb client</displayName>
<description>Client for JCA file adapter</description>
<!--<version>1.3</version>-->
<modules>
<ejbModule>
<groupId>com.blu.jca.client</groupId>
<artifactId>jca-client</artifactId>
<bundleFileName>jca-client-1.0-SNAPSHOT.jar</bundleFileName>
</ejbModule>
<jarModule>
<groupId>com.blu.jca.file</groupId>
<artifactId>jca-adapter</artifactId>
<!--<bundleFileName>jca-adapter-1.0-SNAPSHOT.jar</bundleFileName>-->
<includeInApplicationXml>false</includeInApplicationXml>
</jarModule>
</modules>
<resourcesDir>${basedir}/target/classes</resourcesDir>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<targetPath>META-INF</targetPath>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
after generating ejb package, we will also deploy it on oc4j container. During deployment of MDB client, if following errors will occure, please check the resource adapter name during deployement and mdb client target adpater name. They must be same. No ResourceAdapterWrapper instance found at the specified resource-adapter() ... 4) Test: Just put any text file on inbound file directory and wait some time. Adapter will collect the file and send it's content to the MDB. All the event will see from the oc4j console. In the conclusion, for further reading see JCA1.5 specification. A much more ideas will get from the follwing resource during writing the post.
  • http://www.oracle.com/technology/pub/articles/luttikhuizen-adapters.doc
  • Have your application call my application, Part 3: The resource adapter
    Geronimo message-driven beans, JCA resource adapters, and e-mail. http://www.ibm.com/developerworks/opensource/edu/os-dw-os-ag-callme1.html?S_TACT=105AGX44&S_CMP=GRNMO
Post a Comment