Skip to main content

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

Comments

Popular posts from this blog

Send e-mail with attachment through OSB

Oracle Service Bus (OSB) contains a good collection of adapter to integrate with any legacy application, including ftp, email, MQ, tuxedo. However e-mail still recognize as a stable protocol to integrate with any application asynchronously. Send e-mail with attachment is a common task of any business process. Inbound e-mail adapter which, integrated with OSB support attachment but outbound adapter doesn't. This post is all about sending attachment though JavaCallout action. There are two ways to handle attachment in OSB: 1) Use JavaCallout action to pass the binary data for further manipulation. It means write down a small java library which will get the attachment and send the e-mail. 2) Use integrated outbound e-mail adapter to send attachment, here you have to add a custom variable named attachment and assign the binary data to the body of the attachment variable. First option is very common and easy to implement through javax.mail api, however a much more developer manage t

Tip: SQL client for Apache Ignite cache

A new SQL client configuration described in  The Apache Ignite book . If it got you interested, check out the rest of the book for more helpful information. Apache Ignite provides SQL queries execution on the caches, SQL syntax is an ANSI-99 compliant. Therefore, you can execute SQL queries against any caches from any SQL client which supports JDBC thin client. This section is for those, who feels comfortable with SQL rather than execute a bunch of code to retrieve data from the cache. Apache Ignite out of the box shipped with JDBC driver that allows you to connect to Ignite caches and retrieve distributed data from the cache using standard SQL queries. Rest of the section of this chapter will describe how to connect SQL IDE (Integrated Development Environment) to Ignite cache and executes some SQL queries to play with the data. SQL IDE or SQL editor can simplify the development process and allow you to get productive much quicker. Most database vendors have their own front-en

Load balancing and fail over with scheduler

Every programmer at least develop one Scheduler or Job in their life time of programming. Nowadays writing or developing scheduler to get you job done is very simple, but when you are thinking about high availability or load balancing your scheduler or job it getting some tricky. Even more when you have a few instance of your scheduler but only one can be run at a time also need some tricks to done. A long time ago i used some data base table lock to achieved such a functionality as leader election. Around 2010 when Zookeeper comes into play, i always preferred to use Zookeeper to bring high availability and scalability. For using Zookeeper you have to need Zookeeper cluster with minimum 3 nodes and maintain the cluster. Our new customer denied to use such a open source product in their environment and i was definitely need to find something alternative. Definitely Quartz was the next choose. Quartz makes developing scheduler easy and simple. Quartz clustering feature brings the HA and