This is the second tutorial about Java API for Batch Applications (JSR-352) on WildFly application server. In the first Java EE Batch Tutorial we have learnt how to use chunk steps which are iterative executions of read/process/write operations on a set of items. Batchlets, on the other hand, include activities which are not executed in an iterative style as chunk but as atomic activities. In short a batchlet is a step which is called once. It either succeeds or fails. If it fails it can be restarted and it runs again.
In the following example we will show how to combine Batchlets, Flows and Deciders in order to create a Workflow-like logic using simply a few java classes and the Job Specification Language XML file. So here’s our simple job which takes care of copying some files, then a decision node evaluates if the hard disk space is getting low. If so, a mail is sent to the administrator otherwise the job is completed:
And here’s the job XML file which reflects this job:
<job id="bpmJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0"> <flow id="mainprocess" next="sendemail"> <step id="copyfiles" next="decider1"> <batchlet ref="copyFilesBatchlet" /> </step> <decision id="decider1" ref="decisionNode"> <next on="DSK_SPACE_LOW" to="sendemail" /> <end on="DSK_SPACE_OK" /> </decision> </flow> <step id="sendemail"> <properties> <property name="mail.from" value="SENDER-EMAIL" /> <property name="mail.to" value="DESTINATION-EMAIL" /> </properties> <batchlet ref="mailBatchlet" /> </step> </job>
The JSL XML file needs to be created in the META-INF/batch-jobs of your application so if you are using Maven to build your project a good place to include the XML file is in the resources/META-INF/batch-jobs folder.
The job includes a flow and a step named “sendmail”. The flow is a composition of steps which run as a single unit. In our case, the flow “mainProcess” includes a first step named “copyfiles” which can be used to copy files and a decision node which does some checks and drivers the execution towards the step “sendemail” or to the end.
The copyFilesBatchlet is a CDI bean which extends javax.batch.api.AbstractBatchlet and implements the process method as follows:
@Named public class CopyFilesBatchlet extends AbstractBatchlet { @Inject JobContext jobContext; @Override public String process() { System.out.println("Running inside SendBillBatchlet batchlet "); Properties parameters = getParameters(); String source = parameters.getProperty("source"); String destination = parameters.getProperty("destination"); // JDK 1.7 API try { Files.copy(new File(source).toPath(), new File(destination).toPath()); return "COMPLETED"; } catch (IOException e) { e.printStackTrace(); } return "FAILED"; } private Properties getParameters() { JobOperator operator = BatchRuntime.getJobOperator(); return operator.getParameters(jobContext.getExecutionId()); } }
As you can see, within the process method the actual copy of files happens. The origin and destination files are taken from parameters included when starting up the job so that you can dynamically change the file names. In order to collect the job parameters we programatically interact with the JobOperator, which is available via the BatchRuntime class. The JobOperator requires a job execution id which is collected from the JobContext interface. If the Batchlet completes successfully, it exits with a “COMPLETED” exit code, otherwise a “FAILED” exit code is returned.
The other component of our job is the Decider which does a check up on the file system free space and returns an exit code as well.
@Named public class DecisionNode implements Decider { @Inject JobContext jobContext; @Override public String decide(StepExecution[] ses) throws Exception { Properties parameters = getParameters(); String fs = parameters.getProperty("filesystem"); File file = new File(fs); long totalSpace = file.getTotalSpace(); if (totalSpace > 100000) { return "DSK_SPACE_OK"; } else { return "DSK_SPACE_LOW"; } } private Properties getParameters() { JobOperator operator = BatchRuntime.getJobOperator(); return operator.getParameters(jobContext.getExecutionId()); } }
As you can see a Decider has to implement the javax.batch.api.Decider interface and its decide method. Within it, you can choose the return value of the Decider that is used in the JSL file to drive the path in one direction or in another.
The last Batchlet included in this example is the mailBatchlet which is triggered in case the Decider returned “DSK_SPACE_LOW”.
@Named public class MailBatchlet extends AbstractBatchlet { @Resource(mappedName="java:jboss/mail/Default") private Session mailSession; @Inject StepContext stepContext; @Inject JobContext jobContext; @Override public String process() { System.out.println("Running inside MailBatchlet batchlet "); String fromAddress = stepContext.getProperties().getProperty("mail.from"); String toAddress = stepContext.getProperties().getProperty("mail.to"); try { MimeMessage m = new MimeMessage(mailSession); Address from = new InternetAddress(fromAddress); Address[] to = new InternetAddress[] {new InternetAddress(toAddress) }; m.setFrom(from); m.setRecipients(Message.RecipientType.TO, to); m.setSubject("WildFly Mail"); m.setSentDate(new java.util.Date()); m.setContent("Job Execution id "+jobContext.getExecutionId()+" warned disk space getting low!","text/plain"); Transport.send(m); } catch (javax.mail.MessagingException e) { e.printStackTrace(); } return "COMPLETED"; } }
What is interesting to note in this Batchlet is that it uses some properties relative to the Step which are specified in the JSL file:
<step id="sendemail"> <properties> <property name="mail.from" value="SENDER-EMAIL" /> <property name="mail.to" value="DESTINATION-EMAIL" /> </properties> <batchlet ref="mailBatchlet" /> </step>
Properties provide a convenient wat to specify some attributes which are not so dynamic like parameters yet they can be changed without the need to recompile the project. In order to execute our job we need to code a simple Client which can be a Servlet for example:
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { JobOperator jo = BatchRuntime.getJobOperator(); Properties p = new Properties(); p.setProperty("source", "/home/user1/file.dmp"); p.setProperty("destination", "/usr/share/dmp"); p.setProperty("filesystem", "/usr/"); long id = jo.start("bpmJob",p); out.println("Job submitted: " + id ); } catch (JobStartException | JobSecurityException ex) { out.println("Error submitting Job! " + ex.getMessage() ); ex.printStackTrace(); } out.flush(); }
If you have already gone through our first tutorial about chunk steps, the Servlet client should look familiar. The only difference here is that we are including some parameters in order to make dynamic the job. You can compile the example by including the code Maven dependencies required for Batch API and CDI:
<repositories> <repository> <id>JBoss Repository</id> <url>https://repository.jboss.org/nexus/content/groups/public/">https://repository.jboss.org/nexus/content/groups/public/</url> </repository> </repositories> <dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-7.0</artifactId> <version>1.0.0.Final</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- Import the Batch API --> <dependency> <groupId>org.jboss.spec.javax.batch</groupId> <artifactId>jboss-batch-api_1.0_spec</artifactId> <version>1.0.0.Final</version> </dependency> <!-- Import the Mail API --> <dependency> <groupId>com.sun.mail</groupId> <artifactId>javax.mail</artifactId> <version>1.5.1</version> </dependency> <!-- Import the CDI API --> <dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <scope>provided</scope> </dependency> <!-- Import the Common Annotations API (JSR-250) --> <dependency> <groupId>org.jboss.spec.javax.annotation</groupId> <artifactId>jboss-annotations-api_1.2_spec</artifactId> <scope>provided</scope> </dependency> <!-- Import the Servlet API --> <dependency> <groupId>org.jboss.spec.javax.servlet</groupId> <artifactId>jboss-servlet-api_3.1_spec</artifactId> <scope>provided</scope> </dependency> </dependencies>
That’s all! enjoy the Java EE Batch API with WildFly application server!