Saturday, June 28, 2008

Spring and Lingo = Easy JMS

With Lingo from codeHaus, Spring remoting can be extended to support JMS.
Here’s a great article on Saniv Jivan’s blog that shows some of its capabilities (synchronous calls over JMS and asynchronous callbacks)

One feature really seducing is asynchronous callbacks over JMS
with POJOS (without a single line of JMS code).
The Lingo site does not provide much documentation on it
so thanks for the author of this nice article.

We applied this technique for our build system to distribute load
on different machines to speed up the process (we only have mono pro
build machines) and gets informed when tasks are done via callbacks.
We used Spring 2.0M4 and ActiveMQ 3.2.2 in standalone mode.

Note that I had troubles to make it run with Websphere MQ 5.3
First, recent MQ JMS 1.1 compliant Jars must be used and
a misinterpretation of the JMS specs by Websphere seems to break
the Lingo Spring JMS service exporter see

http://opensource.atlassian.com/projects/spring/browse/SPR-1324

which is for JMS templates but can also be applied to lingo.

Here’s the diff of org.logicblaze.lingo.jms.JmsServiceExporter between unpatched and patched version for Websphere MQ:

diff -aur lingo-1.1/src/java/org/logicblaze/lingo/jms/JmsServiceExporter.java li
ngo-1.1-patch/src/java/org/logicblaze/lingo/jms/JmsServiceExporter.java
--- lingo-1.1/src/java/org/logicblaze/lingo/jms/JmsServiceExporter.java 2006-06-
13 13:45:12.716722400 +0200
+++ lingo-1.1-patch/src/java/org/logicblaze/lingo/jms/JmsServiceExporter.java200
6-06-13 13:44:49.899847100 +0200
@@ -180,7 +180,7 @@   }
else {
-            return session.createConsumer(destination, messageSelector, noLocal
);
+            return session.createConsumer(destination, messageSelector);
}
}

Monday, June 23, 2008

Spring entityManagerFactory in jta and non-jta modes

This blog post is about using JPA with Spring in 2 contexts :

  • production with a JTA transaction manager
  • testing with transactions handled by jpa transaction manager.

It has been inspired by Erich Soomsam blog post
You can achieve such configuration with a PersistenceUnitPostProcessor having a single persistence.xml file and 2 Spring context files (1 for each environment).

Since you are likely to have at least 2 different Spring dataSource definitions : 1 for production that performs a JNDI lookup to find a bound datasource and 1 for development that uses a local and Spring declared datasource backed by a JDBC connection pool (C3p0 or DBCP), place the entityManager declaration in the same file as the datasource declaration.

Let’s say that the default persistence.xml use the non-jta datasource:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
   version="1.0">
   <persistence-unit name="seamphony" transaction-type="RESOURCE_LOCAL">
      <properties>
          <!-- Scan for annotated classes and Hibernate mapping XML files -->
          <property name="hibernate.archive.autodetection" value="class, hbm"/>
          <property name="hibernate.dialect"
                    value="org.hibernate.dialect.MySQLDialect"/>
      </properties>
   </persistence-unit>
</persistence>

Here’s how you can use Spring to post process the persistence unit and configure it for production (here
with MySQL datasource and JBoss Transaction Manager):

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
   <property name="dataSource" ref="dataSource"></property>
   <property name="jpaVendorAdapter">
	<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="database" value="MYSQL"></property>
            <property name="showSql" value="true"></property>
            <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"></property>
	</bean>
   </property>
   <property name="jpaPropertyMap">
      <map>
	<entry key="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
	<entry key="hibernate.transaction.flush_before_completion" value="true"/>
	<entry key="hibernate.transaction.auto_close_session" value="true"/>
	<entry key="hibernate.current_session_context_class" value="jta"/>
	<entry key="hibernate.connection.release_mode" value="auto"/>
      </map>
   </property>
   <property name="persistenceUnitPostProcessors">
      <list>
         <bean class="JtaPersistenceUnitPostProcessor">
            <property name="jtaMode" value="true"></property>
            <property name="jtaDataSource" ref="dataSource"></property>
         </bean>
      </list>
   </property>
</bean>
<!--
 Datasource Lookup
-->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
   <property name="resourceRef">
       <value>false</value>
    </property>
   <property name="jndiName">
      <value>java:/MyDS</value>
    </property>
</bean>   <!--
 Transaction Manager
-->   <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
   <property name="transactionManagerName" value="java:/TransactionManager"></property>
   <property name="autodetectUserTransaction" value="false"></property>
</bean>

Here’s the class that reads the jta mode property and configure the transaction type accordingly:

import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;   public class JtaPersistenceUnitPostProcessor implements
		PersistenceUnitPostProcessor {   private boolean jtaMode = false;   private DataSource jtaDataSource;
	private PersistenceUnitTransactionType transacType = PersistenceUnitTransactionType.RESOURCE_LOCAL;   public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo mutablePersistenceUnitInfo) {   if (jtaMode) {
			transacType = PersistenceUnitTransactionType.JTA;
			mutablePersistenceUnitInfo.setJtaDataSource(this.getJtaDataSource());
		}   mutablePersistenceUnitInfo.setTransactionType(transacType);   }   public boolean isJtaMode() {
		return jtaMode;
	}   public void setJtaMode(boolean jtaMode) {
		this.jtaMode = jtaMode;
	}   public DataSource getJtaDataSource() {
		return jtaDataSource;   }   public void setJtaDataSource(DataSource jtaDataSource) {
		this.jtaDataSource = jtaDataSource;
	}   }

Spring really helps tuning your persistence unit for different environments. It could be achieved by a custom build task that could alter the persistence.xml file but since this example assumes that Spring is already used, it can be avoided.

Saturday, June 21, 2008

Jackbrabbit OCM and Spring

Content Repository API becomes more and more popular nowadays, however it is rather difficult in use because its takes time of learning and use. OCM (Object Content Mapping) module is the great tool to helps developers save a lot of time to develop the content-driven application and Spring is the great DI platform to hide most of complexity of initializing, creating and executing repository. We are developing the engroup ECM module base on Jackbrabbit OCM and Spring Module. During development, we look for help in many forums, websites but unfortunately we do not seek the full solution, we hope that this article provide the full example of using Jackbrabbit and Spring in the real application. Part of engroup ECM code base is included in attached file, it is developed base on jackrabbit 1.5 (snapshot version - you can get it at apache maven repository http://people.apache.org/maven-snapshot-repository/), spring modules 0.9 and the patch spring-ocm got at http://jira.springframework.org/browse/MOD-446

First, create the POJOs for JCR repository:

  @Node(jcrMixinTypes = “mix:versionable”)
  public class Content {
    @Field(uuid=true)
    protected String id;
    @Field(path=true)
    protected String path;
    @Field
    protected String name;
    …
  }

Note: if you want to create the POJO inherit the JCR fields of its parent class, you must use the extend property field of annotation Node like the following example:

  @Node(jcrMixinTypes = “mix:versionable”, extend = AbstractFile.class)
  public class File extends Content {
    @Field
    protected byte[] content;
    …
  }

The next step is creating the spring beans to init the repository, register nodes types and POJOs with repository. Here is the part of configuration file (you can see the full file in the attachment):

  • Initialize the repository:
        <bean id="repository" class="org.springmodules.jcr.jackrabbit.RepositoryFactoryBean">
          <property name="configuration" value="classpath:jackrabbit-repo.xml" />
          <property name="homeDir" value="file:/tmp/repository" />
        </bean>
        <bean id="jcrSessionFactory" class="org.springmodules.jcr.jackrabbit.ocm.JackrabbitSessionFactory">
          <property name="repository" ref="repository" />
          <property name="credentials">
            <bean class="javax.jcr.SimpleCredentials">
              <constructor-arg index="0" value="superuser" />
              <!-- create the credentials using a bean factory -->
                <constructor-arg index="1">
                  <bean factory-bean="password" factory-method="toCharArray" />
                </constructor-arg>
            </bean>
          </property>
          <property name="nodeTypes2Import" value="nodetypes/custom_nodetypes.xml" />
        </bean>
  • Make the mapping between POJOs and annotation mapper
        <bean id="jcrMappingDescriptor" class="org.apache.jackrabbit.ocm.mapper.impl.annotation.AnnotationMapperImpl">
          <constructor-arg index="0">
            <!--Put all your POJOs in this list-->
            <list>
              <value>com.engroup.module.ecm.domain.AbstractFile</value>
              <value>com.engroup.module.ecm.domain.Content</value>
              <value>com.engroup.module.ecm.domain.File</value>
              <value>com.engroup.module.ecm.domain.Folder</value>
            </list>
          </constructor-arg>
        </bean>
  • Declare the Content Service Bean with transaction management support
    
     <bean id=”internalContentService”
        class=”com.engroup.module.ecm.service.impl.ContentServiceImpl”>
        <property name=”jcrTemplate” ref=”jcrMappingTemplate” />
     </bean>
     <bean id=”contentService” parent=”baseTransactionProxy”>
        <property name=”proxyInterfaces”>
          <value>com.engroup.module.ecm.service.ContentService</value>
        </property>
        <property name=”target”>
          <ref bean=”internalContentService” />
        </property>
        <property name=”transactionAttributes”>
          <props>
            <prop key=”*”>PROPAGATION_REQUIRED</prop>
          </props>
        </property>
      </bean>

Well, all configuration tasks are done. Now, you can access the content of repository by using POJOs. Thanks for spring modules that helps you reduce lot of code for initilizing and manage repository. The tasks later just be simply like you work with Hibernate entity, all works are done by Java code (Of course, you need to know a little advance knowledge of repository to customize data types etc for your needs). After I create the service, now it is time to write some little unit test to make sure all configurations are set properly :)

  @RunWith(UnitilsJUnit4TestClassRunner.class)
  public class ContentServiceTest {
    @SpringBean
    private ContentService<Content> contentService;

    @Test
    public void testSave() {
      File file = createFile();
      contentService.save(file);
      file = (File)contentService.findByPath("/nextss");
      Assert.assertThat(file.getPath(), is("/nextss"));
      Assert.assertThat(file.getFileType(), is(FileType.UNDEFINED));
      contentService.remove(file);
    }

    private File createFile() {
      File file = new File();
      file.setId("1");
      file.setPath("/nextss");
      file.setName("ABC");
      file.setFileType(FileType.UNDEFINED);
      file.setTitle("Test Exam");
      file.setLastModified(Calendar.getInstance().toString());
      file.setContent("Hello world".getBytes());
      file.setComment("AAA");
      return file;
    }
    ...
  }

I am happy when the unit test run well :). Hope it is the part complements with Jackrabbit OCM and Spring modules - OCM. Welcomes any comments from you.

Resources:

Tuesday, June 17, 2008

Who hides in your Spring factory?

Using schema-based configuration in Spring framework is powerful yet leads to less transparent configuration. Here I introduce single class that allows you to unleash all details of your Spring context.

Schema based configuration

Spring framework has very interesting and powerful feature - ability to use custom namespaces within ordinary Spring configuration file.

Using that functionality, it's possible to create custom XML element (with providing necessary XSD scheme to Spring) that will be parsed and used for declaring specific beans that corresponds to appropriate custom tags from scheme.

That's feature is really cool, since, from one hand, it allows to create own DSL (domain specific language) that is plugged into usual Spring declaration. Using DSL instead of Spring declaration is pretty convenient since it allows to have clean, compact and, what is more important, domain specific markup instead of generic one.

In addition, by introducing support of such functionality, Spring encourages vendors of third-party tools and libraries to plug them into Spring as components with functionality exposed via elements in custom namespace provided by vendor.

In general, support of custom namespaces in Spring context brings higher level of abstraction and increases overall productivity of developers.

Caveat

Higher level of abstraction, as usual, adds more complexity and leaves many things under the hood. Until you use namespace you've implemented by own, you can now which beans will be created in Spring context as you use it (well, at least until during some reasonable period of time after developing it). However, what to do if you simply use tags provided by someone else? First, you got that library somehow and description of tag promises that if you use it you may throw away all old configuration for persistence, hibernate etc. etc. since at the moment of adding that custom tag into your context it scans your brain and does everything much better as you can even imagine... Sure thing, you think that it could be great and use it...

And until everything work fine - no one cares what is under that tag (it scans the brain, after all!). But if something goes wrong - well, if you have ordinary Spring config, you have a chance to take a look to configuration and find the problem' source. Not for custom tags - all details are hidden from outside world.

Seriously, using of custom namespaces in Spring config has such a drawback - in general, you don't know what actually happens when you use such a tags.

Solution

We here in SoftAMIS have got that problem some time ago trying to figure quite subtle problems with configuration. To avoid that in the future, we've created small utility that performs dump of internals for given Spring context (by examining registered bean definitions). Of course, it was not practical to invent some new format for such a dump, so old good Spring configuration is used - however, it unwraps all custom tags into their internal representation.

Similarly to approach from Spring reference:) If you have in your context declaration like that:

 

<util:map id="testMap"> <entry key="key" value="value"/> </util:map> <util:property-path path="testMap.values"/>

in corresponding dumped context you'll get:

<bean name="testMap" class="org.springframework.beans.factory.config.MapFactoryBean">
  <property name="sourceMap">
    <map> <entry key="key" value="value"/> </map>
  </property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPathFactoryBean" name="testMap.values" p:propertyPath="values" p:targetBeanName="testMap"/>

How to use

To make dump of Spring context, I've wrote custom BeanFactoryPostProcessor. Ones is invoked by Spring automatically (if factory is created via application context) and simply inspects internal bean definitions registered in context and generates appropriate XML for them.

Therefore, everything you need to obtain dump of Spring factory - simply add the following declaration into context for wich you'd like to have such dump:

<bean class="org.softamis.tools4spring.dump.DumpBeanFactoryPostProcessor" p:generateSchemaBasedContext="true" p:outputLocation="z:\context.xml"/>

I's possible to specify location of file which dump should be written to and also specify whether dump should be created based on DTD or XSD. In later case, applicable attributes will be written using p: namespace

Of course, since that class works on context level, it's perfectly will dump Spring factory created from several configuration files.

License and download

To download source code for that class, please use this link. That class is licensed under Apache License, so it could be used both in open source and commercial applications.

I hope that this small utility will be helpful for you and will save your time in some tight situation.

Monday, June 16, 2008

Spring & LDAP

Just like you have JDBC/Hibernate/iBatis templates in Spring, we also have an LDAPTemplate. You can download the spring LDAP library from http://springframework.org/ldap. I like this template approach simply because it lets us avoid common pitfalls such as not cleaning up resources after using an API (in JDBC its the connection, statement and resultset). Why bother when the template can do this for you. Same holds true for LDAP queries.
For this example I had the following setup:

  • Apache Directory Server 1.5.2. I decided to use the sample directory data.
  • Installed the Apache Directory Studio eclipse plugin.
To confirm your setup. Open eclipse and go to the LDAP perspective. Create a new connection with following information:
  • hostname - localhost
  • port - 10389
  • Bind DN or user - uid=admin,ou=system
  • password - secret (this is the default password for apache ds)
This should let you into the directory. Under dc=example,dc=com I added two organizations (asia and americas).

Now for the Spring stuff. 

package trial;

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Service;
@Service
public class LDAPSampleImpl implements LDAPSample {

@Autowired
private LdapTemplate ldapTemplate;

@Override
public List getOrgNames() {
return ldapTemplate.list("");
}
}

The spring XML file looks like:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<context:annotation-config />
<context:component-scan base-package="trial" />

<bean id="ldapContextSource"
class="org.springframework.ldap.core.support.LdapContextSource">
<property name="url" value="ldap://localhost:10389" />
<property name="base" value="dc=example,dc=com" />
<property name="userDn" value="uid=admin,ou=system" />
<property name="password" value="secret" />
</bean>

<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="ldapContextSource" />
</bean>
</beans>

Everything above is self explanatory. Now for the test case to execute all of this.

package trial;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring-context.xml" })
public class DriverTestCase {

@Autowired
private LDAPSample ldap;
@Test
public void testGreeting() {
System.out.println(ldap.getOrgNames());
}
}

Running this unit test results in output
>> [ou=asia, ou=americas]

Wednesday, June 4, 2008

Send E-mail Using Spring and JavaMail

 

This brief tutorial will show how to send e-mail using Spring and JavaMail. JavaMail can handle e-mail storage as well, but here we're just worrying about sending e-mail.

I happen to be using Spring 2.5 but this ought to work for earlier versions of Spring as well (at least Spring 2.0 I think).

Let's jump right in. You can configure your JavaMail session either in Spring itself or with JNDI. We'll look at both alternatives.

Alternative 1: Configuring JavaMail with Spring

You may be operating in an environment where you don't have a JNDI enterprise naming context (ENC) available. Or you may have some reason not to use it even if you do have a JNDI ENC. For example, I've written a simple application monitor, and I'll be adding e-mail alerting shortly. This is a standalone app and so there's no JNDI ENC. No problem; I can just configure JavaMail in the Spring application context configuration as shown below.

<!-- Mail service -->
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="your.smtphost.com"/>
    <property name="port" value="25"/>
    <property name="username" value="yourusername"/>
    <property name="password" value="yourpassword"/>
    <property name="javaMailProperties">
        <props>
            <!-- Use SMTP-AUTH to authenticate to SMTP server -->
            <prop key="mail.smtp.auth">true</prop>
            <!-- Use TLS to encrypt communication with SMTP server -->
            <prop key="mail.smtp.starttls.enable">true</prop>
        </props>
    </property>
</bean>

This bean is, as its name suggests, a mail sender. It is basically a wrapper around JavaMail SMTP, and the configuration reflects that. In the example I'm showing how you would enable SMTP-AUTH (supports authentication to the SMTP server) and TLS (supports message encryption), assuming your SMTP server has those capabilities. For more information see my article SMTP and SMTP-AUTH.

IMPORTANT: You will need to inject mailSender into your mail-sending service bean.

So that's how to configure JavaMail from Spring. Now here's how to do the same thing with JNDI, which you may want to do if you're running in an environment with a JNDI ENC (like an app server or a servlet container).

Alternative 2: Configuring JavaMail with JNDI

Server JNDI configuration (using Tomcat 6 as an example)

First you will need to expose a JavaMail session factory through JNDI in your server environment. This is environment-dependent, but let's look at an example.

Say you're using Tomcat 6. There are a couple things you must do. First, move mail.jar and activation.jar to your tomcat/lib directory. I say "move" rather than "copy" because you will get an odd error if you leave the two JARs in your application classpath. The error is

java.lang.IllegalArgumentException: 
    Cannot convert value of type [javax.mail.Session] to required type
    [javax.mail.Session] for property 'session': no matching editors
    or conversion strategy found

Second, define your Tomcat JNDI configuration, which might look like this (e.g. in your context.xml file):

Code listing: /META-INF/context.xml

<?xml version="1.0" encoding="UTF-8"?>

<Context path="/myapp" docBase="myapp" debug="5" crossContext="false">

    <!-- JavaMail session factory -->
    <Resource name="mail/Session"
              auth="Container"
              type="javax.mail.Session"
              username="yourusername"
              password="yourpassword"
              mail.debug="true"
              mail.user="yourusername"
              mail.password="yourpassword"
              mail.transport.protocol="smtp"
              mail.smtp.host="your.smtphost.com"
              mail.smtp.auth="true"
              mail.smtp.port="25"
              mail.smtp.starttls.enable="true"/>
</Context>

As with the non-JNDI example, I'm configuring for SMTP-AUTH and TLS. If you are using SMTP-AUTH (authenticated SMTP sessions, which you activate using mail.smtp.auth="true"), then you will need to specify the username and password twice, as shown above. Also, if your SMTP server supports it, you can tell JavaMail to encrypt sessions using TLS by setting mail.smtp.starttls.enable=true.

The above discussion applies only to Tomcat 6 (see Apache Tomcat 6.0 JNDI Resources HOWTO for detailed instructions); you'll need to consult your server docs to expose a JavaMail session factory through JNDI in your environment.

Spring configuration

We still need to create a mail sender, but the configuration is simpler since we did all the heavy lifting in context.xml (or whatever, depending on your server environment). So for the Spring application context, all we need is:

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="session" ref="mailSession"/>
</bean>

IMPORTANT: As before, you will need to inject mailSender into your mail-sending service bean.

So that takes care of configuring the mail sender, and also the JavaMail session factory if you are using JNDI. Let's visit one more topic before we dive into the code itself.

Creating an E-mail Template (Optional)

Sometimes the e-mail you want to send fits inside a standard template (e.g., maybe it always has the same sender, or maybe the same recipient, or whatever). You can define a template using Spring. Here's an example:

Code listing: Spring app context config file

<!-- Mail message -->
<bean id="mailMessage" class="org.springframework.mail.SimpleMailMessage">
    <property name="from">
        <value><![CDATA[Simple Application Monitor <noreply@somehost.com>]]></value>
    </property>
    <property name="to">
        <value><![CDATA[System Administrator <sysadmin@somehost.com>]]></value>
    </property>
    <property name="subject" value="SAM Alert"/>
</bean>

You can include as many e-mail templates in a Spring app context configuration, including none if you don't need a template. Here I've defined a template that specifies a "from" field, a "to" field and a "subject" field. It doesn't specify the date or the body. That template works say for an application monitoring system but it wouldn't work for an e-commerce site's order confirmation e-mail, which would need to have a variable "to" field.

IMPORTANT: I didn't show it above, but you will need to inject any e-mail templates (i.e. mail messages) you create into your mail-sending service bean. Otherwise the service bean has no way to use the template.

So that's that. Time for the Java code that actually sends the e-mail.

How to Send the E-mail from Your Service Bean

It turns out that the coding part is much simpler than the configuration (not that the config was too bad). Let's suppose for the sake of example that we want to send an e-mail based on the template that we defined above, and that you've injected that template into your service bean as mailMessage. Then here's the service bean code that allows you to use the template to send an e-mail:

Code listing: Your service bean

SimpleMailMessage message = new SimpleMailMessage(mailMessage);
message.setSentDate(new Date());
message.setText("Blah blah blah...");
mailSender.send(message);

You can see we're creating a new message based on the mailMessage e-mail template and mailSender that we injected into said service bean.

And that's it! Not too painful, right?

by Willie Wheeler

Monday, June 2, 2008

Spring Batch - Hello World

This is an introductory tutorial to Spring Batch. It does not aim to provide a complete guide to the framework but rather to facilitate the first contact. Spring Batch is quite rich in functionalities, and this is basically how I started learning it. Keep in mind that we will only be scratching the surface.

Before we start

All the examples will have the lofty task of printing "Hello World!" though in different ways. They were developed with Spring Batch 1.0. I'll provide a Maven 2 project and I'll run the examples with Maven but of course it is not a requirement to work with Spring Batch.

Spring Batch in 2 Words

Fortunately, Spring Batch model objects have self-explanatory names. Let's try to enumerate the most important and to link them together:

A batch Job is composed of one or more Steps. A JobInstance represents a given Job, parametrized with a set of typed properties called JobParameters. Each run of of a JobInstance is a JobExecution. Imagine a job reading entries from a data base and generating an xml representation of it and then doing some clean-up. We have a Job composed of 2 steps: reading/writing and clean-up. If we parametrize this job by the date of the generated data then our Friday the 13th job is a JobInstance. Each time we run this instance (if a failure occurs for instance) is a JobExecution. This model gives a great flexibility regarding how jobs are launched and run. This naturally brings us to launching jobs with their job parameters, which is the responsibility of JobLauncher. Finally, various objects in the framework require a JobRepository to store runtime information related to the batch execution. In fact, Spring Batch domain model is much more elaborate but this will suffice for our purpose.

Well, it took more than 2 words and I feel compelled to make a joke about it, but I won't. So let's move to the next section.

Common Objects

For each job, we will use a separate xml context definition file. However there is a number of common objects that we will need recurrently. I will group them in an applicationContext.xml which will be imported from within job definitions. Let's go through these common objects:

JobLauncher

JobLaunchers are responsible for starting a Job with a given job parameters. The provided implementation, SimpleJobLauncher, relies on a TaskExecutor to launch the jobs. If no specific TaskExecutor is set then a SyncTaskExecutor is used.

JobRepository

We will use the SimpleJobRepository implementation which requires a set of execution Daos to store its information.

JobInstanceDao, JobExecutionDao, StepExecutionDao

These data access objects are used by SimpleJobRepository to store execution related information. Two sets of implementations are provided by Spring Batch: Map based (in-memory) and Jdbc based. In a real application the Jdbc variants are more suitable but we will use the simpler in-memory alternative in this example.

Here's our applicationContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
      <property name="jobRepository" ref="jobRepository"/>
  </bean>
    
  <bean id="jobRepository" class="org.springframework.batch.core.repository.support.SimpleJobRepository">
      <constructor-arg>
          <bean class="org.springframework.batch.core.repository.dao.MapJobInstanceDao"/>
      </constructor-arg>
      <constructor-arg>
          <bean class="org.springframework.batch.core.repository.dao.MapJobExecutionDao" />
      </constructor-arg>
      <constructor-arg>
          <bean class="org.springframework.batch.core.repository.dao.MapStepExecutionDao"/>
      </constructor-arg>
  </bean>

</beans>
Hello World with Tasklets

A tasklet is an object containing any custom logic to be executed as a part of a job. Tasklets are built by implementing the Tasklet interface. Let's implement a simple tasklet that simply prints a message:

public class PrintTasklet implements Tasklet{

  private String message;

  public void setMessage(String message) {
      this.message = message;
  }
    
  public ExitStatus execute() throws Exception {
      System.out.print(message);
      return ExitStatus.FINISHED;
  }
}

Notice that the execute method returns an ExitStatus to indicate the status of the execution of the tasklet.

We will define our first job now in a simpleJob.xml application context. We will use the SimpleJob implementation which executes all of its steps sequentailly. In order to plug a tasklet into a job, we need a TaskletStep. I also added an abstract bean definition for tasklet steps in order to simplify the configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
                                
  <import resource="applicationContext.xml"/>
    
  <bean id="hello" class="helloworld.PrintTasklet">
      <property name="message" value="Hello"/>
  </bean>
    
  <bean id="space" class="helloworld.PrintTasklet">
      <property name="message" value=" "/>
  </bean>
   
  <bean id="world" class="helloworld.PrintTasklet">
      <property name="message" value="World!"/>
  </bean>

  <bean id="taskletStep" abstract="true"
      class="org.springframework.batch.core.step.tasklet.TaskletStep">
      <property name="jobRepository" ref="jobRepository"/>
  </bean>
    
  <bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob">
      <property name="name" value="simpleJob" />
      <property name="steps">
          <list>
              <bean parent="taskletStep">
                  <property name="tasklet" ref="hello"/>
              </bean>
              <bean parent="taskletStep">
                  <property name="tasklet" ref="space"/>
              </bean>
              <bean parent="taskletStep">;
                  <property name="tasklet" ref="world"/>
              </bean>
          </list>
      </property>
      <property name="jobRepository" ref="jobRepository"/>
  </bean>
</beans>
Running the Job

Now we need something to kick-start the execution of our jobs. Spring Batch provides a convenient class to achieve that from the command line: CommandLineJobRunner. In its simplest form this class takes 2 arguments: the xml application context containing the job to launch and the bean id of that job. It naturally requires a JobLauncher to be configured in the application context. Here's how to launch the job with Maven. Of course, it can be run with the java command directly (you need to specify the class path then):


mvn exec:java -Dexec.mainClass=org.springframework.batch.core.launch.support.CommandLineJobRunner 
-Dexec.args="simpleJob.xml simpleJob"

Hopefully, your efforts will be rewarded with a "Hello World!" printed on the console.

The code source can be downloaded here.

by Tareq Abed Rabbo