ICE09 . playing with java, scala, groovy and spring .

Playing with Spring

Archive for the ‘Mail’ Category

Spring-batch with GMail-Notification using Velocity

Posted by ice09 on August 15, 2010

The sources to this blog entry are available here. The SpringSource Tool Suite (STS) or an accordingly configured Eclipse (with a mandatory Maven plugin and an optional Spring IDE plugin) has to be installed.

The problem

The Spring Batch project is really useful in creating new batch jobs. The framework is straightforward and gained quite a lot usefulness and user-friendliness with the 2.x branch. However, even with the great reference documentation and the getting started section, it takes its time to create a simple project with success/error-mail notification.
I will create a really simple batch job using the new 2.x features, adding the almost always necessary mail notification after a successful or failed job execution. The mail context should be rendered using Velocity. This post does not explain the Spring (Batch) components used, please refer to the Spring Batch reference documentation for detailed explanations.

The setup

The structure is taken from the Template Project in STS, the howto is available in the getting started page.

project structure

It is important to note the separation of concerns in the two spring contexts launch-context.xml and module-context.xml. The first one contains all beans relevant to the job’s infrastructure, there is no business logic contained. The module config is included using import resource. The module config just contains business logic, the actual batch job logic is configured here.

module-config.xml

<?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:batch="http://www.springframework.org/schema/batch"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd">

	<batch:job id="job1" xmlns="http://www.springframework.org/schema/batch">
		<batch:step id="step1" parent="simpleStep">
			<batch:tasklet ref="processorTasklet">
				<batch:listeners>
					<batch:listener ref="errorlistener"/>
				</batch:listeners>
			</batch:tasklet>
		</batch:step>
		<batch:listeners>
			<batch:listener ref="samplemailnotification"/>
		</batch:listeners>
	</batch:job>
	
	<bean id="samplemailnotification" parent="mailnotification">
		<property name="errorMailPath" value="mailtemplate.vm"/>
		<property name="successMailPath" value="mailtemplate.vm"/>
		<property name="subject" value="Mail from batch-sample"/>
	</bean>
	
	<bean id="processorTasklet" class="org.springframework.sample.batch.example.ExampleProcessor"/>

</beans>

The processor used just writes a data item to a shared Map. This Map is initialized and written into the job execution context by the StatusMailJobListener in its JobExecutionListener.beforeJob(...) method. In afterJob(...) this Map is evaluated and the following logic is applied: if an error-element is present, remove all provided data and just set the error-element in the Velocity context. Otherwise, copy all data from the values-element into the Velocity context. The origin of the values-element is explained below.

package org.springframework.sample.batch.example;

//[imports excluded]
public class StatusMailJobListener implements JobExecutionListener {
	
	private String recipient;
	private String sender;
	private String successMailPath;
	private String errorMailPath;
	private String subject;
	
	@Autowired
	private JavaMailSenderImpl mailSender;
	@Autowired
	private VelocityEngine velocityEngine;

	public void afterJob(JobExecution jobExecution) {
		String exitCode = jobExecution.getExitStatus().getExitCode();
		if (exitCode.equals(ExitStatus.COMPLETED.getExitCode())) {
			sendMail(successMailPath, "[OK] " + subject, jobExecution.getExecutionContext());
		} else {
			sendMail(errorMailPath, "[ERROR] " + subject, jobExecution.getExecutionContext());
		}
	}

	private void sendMail(final String messagePath, final String subject, final ExecutionContext executionContext) {
		MimeMessagePreparator preparator = new MimeMessagePreparator() {
			public void prepare(MimeMessage mimeMessage) throws Exception {
				MimeMessageHelper message = new MimeMessageHelper(mimeMessage);
				message.setSubject(subject);
				message.setTo(recipient);
				message.setFrom(sender);
				Map<String, String> values = (Map<String, String>) executionContext.get("values");
				if (executionContext.containsKey("error")) {
					values.clear();
					values.put("error", executionContext.getString("error"));
				}
				String text = 
					VelocityEngineUtils.
						mergeTemplateIntoString(velocityEngine, messagePath, values);
				message.setText(text, true);
			}
		};
		mailSender.send(preparator);
	}

	public void beforeJob(JobExecution jobExecution) {
		Map<String, String> values = new HashMap<String, String>();
		jobExecution.getExecutionContext().put("values", values);
	}

        //[setters excluded]

}

For this setup to work, a contract is defined: every step/tasklet, which wants to add information to the context which should be available in the Velocity context for rendering the mail content, must write to the predefined Map.
This can be done eg. by using ((Map)chunkContext.getStepContext().getJobExecutionContext().get("values")).put("content", "add this text"). Initially, this Map is created by the MailNotificationListener as described above (of course, you should check for existance of this Map first in real code).
The errorhandling is done magically by Spring, if an error occurs, the Listeners can just check the ExitStatus and react accordingly (compare the afterJob(...) method).

package org.springframework.sample.batch.example;

//[imports excluded]

public class ExampleProcessor implements Tasklet {

	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
		//throw new RuntimeException("bla");
		((Map<String, String>)chunkContext.getStepContext().getJobExecutionContext().get("values")).put("test", "[successfully invoked exampleprocessor]");
		return RepeatStatus.FINISHED;
	}

}

How and when is the error-element set into the job execution context? This is done by the last missing item, the ErrorListener, which is a StepExecutionListener.

package org.springframework.sample.batch.example;

//[imports excluded]

public class ErrorListener implements StepExecutionListener {

	public ExitStatus afterStep(StepExecution stepExecution) {
		String exitCode = stepExecution.getExitStatus().getExitCode();
		if (exitCode.equals(ExitStatus.FAILED.getExitCode())) {
			StringBuilder messages = new StringBuilder();
			for (Throwable error : stepExecution.getFailureExceptions()) {
				messages.append(error.getMessage());
			}
			stepExecution.getJobExecution().getExecutionContext().put("error", messages.toString());
			return stepExecution.getExitStatus();
		}
		return ExitStatus.COMPLETED;
	}

	public void beforeStep(StepExecution stepExecution) {
		// Nothing to do here.
	}

}

Tips & Tricks

There is not much missing, however, for this thing to run with GMail, certain properties have to be provided, therefore I will list the complete launch-config here. However, this is available in the sources to this post as well. Just fill in the correct data (GMail-account, recipients) and it should work as excepted. The velocity template is so short, I attached it as well FWIW.

launch-config.xml

<?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:batch="http://www.springframework.org/schema/batch"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
		http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd">

	<import resource="classpath:/META-INF/spring/module-context.xml" />

	<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
    	<property name="transactionManager" ref="transactionManager"/>
	</bean>

	<bean id="jobLauncher"
		class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.batch.support.transaction.ResourcelessTransactionManager">
	</bean>

	<bean id="mailnotification" class="org.springframework.sample.batch.example.StatusMailJobListener" abstract="true">
		<property name="recipient" value="MAILADDRESS_OF_RECIPIENTS"/>
		<property name="sender" value="MAILADDRESS_OF_SENDER"/>
	</bean>
	
	<bean id="errorlistener" class="org.springframework.sample.batch.example.ErrorListener"/>

	<util:properties id="props">
		<prop key="mail.smtp.starttls.enable">true</prop>
	</util:properties>

   <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
      <property name="host" value="smtp.googlemail.com"/>
      <property name="username" value="YOUR_GMAIL_ACCOUNT"/>
      <property name="password" value="YOUR_GMAIL_PASSWORD"/>
      <property name="javaMailProperties" ref="props"/>
   </bean>

	<bean id="simpleStep"
		class="org.springframework.batch.core.step.item.SimpleStepFactoryBean"
		abstract="true">
		<property name="transactionManager" ref="transactionManager" />
		<property name="jobRepository" ref="jobRepository" />
		<property name="startLimit" value="100" />
		<property name="commitInterval" value="1" />
	</bean>
	
	<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
      <property name="velocityProperties">
         <value>
          resource.loader=class
          class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
         </value>
      </property>
   </bean>

</beans>

mailtemplate.vm

#if ($error)
Sorry, there was an error, please check the logs.<br/>
exceptionmessages: $error 
#else
Cool, it worked, there is a message for you: $test
#end
Advertisements

Posted in Mail, Spring Batch, Velocity | 1 Comment »

Consuming Services – the Spring Web Service Client

Posted by ice09 on August 25, 2008

There is a downloadable zipped Eclipse project at the end of this post.

Having created a sample (mail) web service some posts ago, we would like to have a client consuming this service.

With Spring Web Services, this can easily be done by using the ubiquitous Template Pattern and, in this special case, the WebServiceTemplate . As the project structure can be downloaded, only the two important snippets are shown here:

The application-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="mailclient" class="de.mail.MailClient">
    	<property name="webServiceTemplate" ref="webServiceTemplate"/>
    </bean>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="marshaller" ref="marshaller"/>
        <property name="unmarshaller" ref="marshaller"/>
        <property name="defaultUri" value="http://localhost:8080/mailservice"/>
    </bean>

    <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
		<property name="classesToBeBound">
			<list>
				<value>org.mail.mailschema.MailRequest</value>
				<value>org.mail.mailschema.MailResponse</value>
			</list>
		</property>
	</bean>

</beans>

and the actual Web Service client:

package de.mail;

import org.mail.mailschema.MailRequest;
import org.mail.mailschema.MailResponse;
import org.springframework.ws.client.core.WebServiceTemplate;

public class MailClient {

	private WebServiceTemplate webServiceTemplate;

	public void setWebServiceTemplate(WebServiceTemplate webServiceTemplate) {
		this.webServiceTemplate = webServiceTemplate;
	}

	public boolean send(String user, String password, String from, String to, String subject, String content) {
		MailRequest request = new MailRequest();
		request.setFrom(from);
		request.setTo(to);
		request.setUser(user);
		request.setPassword(password);
		request.setContent(content);
		request.setSubject(subject);
		return ((MailResponse)webServiceTemplate.marshalSendAndReceive(request)).isResult();
	}

}

The servlet sample is not shown, since it is just for demonstration and not a nice piece of code. However, it exemplifies some Spring antipatterns, because DI is not used and the servlet is not Springified at all. But it’s only a sample showing how to use the Web Service client.
By deploying the servlet (or using mvn jetty:run again), a mail can be send by calling http://server/mailwebapp/Mailer?user=USER&password=PASSWORD&to=RECIPIENT@BLA.DE&from=me&subject=SUBJECT&content=MAILTEXT
But the actual code could be much nicer (also, it is definitely not safe – just a counter and a whitelist is added to prevent immediate abuse :))

Sample Mail Web Service client project:

Instructions:

  1. Download the wsclient-sample
  2. Download the mailwebapp-sample
  3. Download the maildatabinding

If correctly imported as Eclipse projects, the dependencies should be automatically resolved.
The mailwebapp can be deployed as a usual Web application which uses the Web Service client to call the Mail Web Service, which must be already deployed (compare the Spring Web Service post).

Posted in Mail, Maven, Spring Web Services | Tagged: , | 7 Comments »

Mailing with Velocity

Posted by ice09 on July 13, 2008

Download Eclipse project

Java Mailing is also leveraged by Spring, in the usual templating way some optimisations have been added. Therefore, we will write a small mailing application here.
We will add Velocity for mail templating.

MailSample:

package sample.mail;

import java.util.HashMap;
import java.util.Map;

import javax.mail.internet.MimeMessage;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.ui.velocity.VelocityEngineUtils;

public class MailSample {

	private JavaMailSender mailSender;
	private VelocityEngine velocityEngine;

	public void setMailSender(JavaMailSender mailSender) {
		this.mailSender = mailSender;
	}

	public void setVelocityEngine(VelocityEngine velocityEngine) {
		this.velocityEngine = velocityEngine;
	}

	public static void main(String[] args) throws Exception {
		ApplicationContext app = new ClassPathXmlApplicationContext(
				"applicationContext.xml");
		MailSample mailer = (MailSample) app.getBean("mailer");
		mailer.send();
	}

	private void send() {
	      MimeMessagePreparator preparator = new MimeMessagePreparator() {
	          public void prepare(MimeMessage mimeMessage) throws Exception {
	             MimeMessageHelper message = new MimeMessageHelper(mimeMessage);
	             message.setTo("mail.receiver@web.de");
	             message.setFrom("mail.sender@web.de"); // could be parameterized...
	             Map model = new HashMap();
	             model.put("key", "testtext");
	             String text = VelocityEngineUtils.mergeTemplateIntoString(
	                velocityEngine, "test.vm", model);
	             message.setText(text, true);
	          }
	       };
	       this.mailSender.send(preparator);
	}
}
  • the following part is also copied from the Email tutorial.

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

	<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host">
			<value>server.com</value>
		</property>
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
			</props>
		</property>
<property name="username"><value>username</value></property>
<property name="password"><value>password</value></property>
	</bean>

	<bean id="mailer" class="sample.mail.MailSample">
<property name="mailSender" ref="mailSender" />
<property name="velocityEngine" ref="velocityEngine" />
	</bean>

   <bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
<property name="velocityProperties">
         <value>
            resource.loader=class
            class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
         </value>
      </property>
   </bean>

</beans>
  • we just need two more files, of course the POM.

POM:

<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>velocitymail</groupId>
  <artifactId>velocitymail</artifactId>
  <name>velocitymail</name>
  <version>0.0.1-SNAPSHOT</version>
  <description/>
  <dependencies>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring</artifactId>
  		<version>2.5.5</version>
  	</dependency>
  	<dependency>
  		<groupId>javax.mail</groupId>
  		<artifactId>mail</artifactId>
  		<version>1.4</version>
  	</dependency>
  	<dependency>
  		<groupId>velocity</groupId>
  		<artifactId>velocity</artifactId>
  		<version>1.4</version>
  	</dependency>
  </dependencies>
</project>
  • and the velocity template, which is very simple in this case.

test.vm:

hello $key

and finally the project structure:

Posted in Mail, Velocity | Tagged: , , | Leave a Comment »