Sending Velocity-based E-mail with Spring
See how easy setting up Spring's JavaMail support on a PositionManager class, followed by replacing the e-mail's text with a Velocity template can be.
Step 1: Configure the JavaMailSenderImpl
The first step is to setup a MailSender for the PositionManager. To do this, you need to configure a JavaMailSenderImpl with a host or a session. For a host, it's rather simple. Add the following to your applicationContext.xml file:
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="host"><value>localhost</value></property> <!-- If you don't want to hardcode "localhost", load it from a mail.properties file with PropertyPlaceholderConfigurer --> </bean>
Optionally, you can also configure it with a Session from JNDI when you're running in a servlet container:
<bean id="mailSession" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"><value>java:comp/env/mail/Session</value></property> </bean>
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="session"><ref bean="mailSession"/></property> </bean>
I use the first option for JUnit tests, and the 2nd when running in Tomcat. Thanks to Juergen for showing me how easy the JNDI setup is. ;-)
Step 2: Configure a SimpleMailMessage with default values
Next you can configure a SimpleMailMessage with some default values in your applicationContext.xml file:
<bean id="mailMessage" class="org.springframework.mail.SimpleMailMessage"> <property name="from"><value><![CDATA[Human Resources <[email protected]>]]></value></property> <property name="subject"><value>Your application has been received</value></property> </bean>
Step 3: Configure dependencies in PositionManagerImpl
Then in traditional Spring-style, you need to add variables and setters to the PositionManagerImpl class:
private MailSender mailSender; private SimpleMailMessage message; public void setMailSender(MailSender mailSender) { this.mailSender = mailSender; } public void setMessage(SimpleMailMessage message) { this.message = message; }
Then configure this class's definition in applicationContext.xml so Spring will inject its dependencies:
<bean id="positionManagerTarget" class="org.appfuse.service.PositionManagerImpl"> ... <property name="mailSender"><ref bean="mailSender"/></property> <property name="message"><ref bean="mailMessage"/></property> ... </bean>
Now you should be able to easily send an e-mail in a method of this class:
// user and position objects looked up... SimpleMailMessage msg = new SimpleMailMessage(this.message); msg.setTo(user.getFullName() + "<" + user.getEmail() + ">"); StringBuffer txt = new StringBuffer(); txt.append("Dear " + user.getFullName() + ",nn"); txt.append("Thank you for application for our "); txt.append(position.getName() + " position. You can check "); txt.append(" on the status of this position at the URL below.nn"); txt.append(" https://raibledesigns.com/positions/status.jspnn"); // doesn't really exist ;-) txt.append("Sincerely, nnRaible Designs Human Resources"); msg.setText(txt.toString()); try { mailSender.send(msg); } catch (MailException ex) { log.error(ex.getMessage()); }
The only problem with this is that the e-mail message is hard-coded into our Java code - so let's refactor it to use a Velocity template for the text.
Step 4: Configuring Velocity in applicationContext.xml
The next step is to use Spring's Velocity support classes to configure a VelocityEngine. For this, add the following to your applicationContext.xml file:
<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean"> <property name="velocityProperties"> <props> <prop key="resource.loader">class</prop> <prop key="class.resource.loader.class"> org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader </prop> </props> </property> </bean>
NOTE: You can also use <property name="configLocation">velocity.properties</property> if velocity.properties file is in your classpath. However, my velocity.properties file has a webapp.loader defined in it, and since this depends on javax.servlet.ServletContext, I didn't want to use it in my business logic layer. You could also load velocity.properties with PropertyPlaceholderConfigurer and then refer to ${class.resource.loader.class}.
Step 5: Configure Velocity dependency in PositionManagerImpl
In order to use this nice little velocityEngine you just configured, you'll need to add a variable and setter to PositionManagerImpl:
private VelocityEngine velocityEngine; public void setVelocityEngine(VelocityEngine velocityEngine) { this.velocityEngine = velocityEngine; }
And configure it's dependency in applicationContext.xml:
<bean id="positionManagerTarget" class="org.appfuse.service.PositionManagerImpl"> ... <property name="velocityEngine"><ref bean="velocityEngine"/></property> ... </bean>
Now you can refactor the text part of the previous e-mail sending logic to use a template.
Map model = new HashMap(); model.put("user", user); model.put("position", position); String result = null; try { // notificationTemplate.vm must be in your classpath result = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, "notificationTemplate.vm", model); } catch (VelocityException e) { e.printStackTrace(); } msg.setText(result);
Pretty slick huh? A further configuration option is to use Spring to set the name of the template. If you know of any better ways to do JavaMail and e-mail templates with Spring, or find errors in my code - please let me know.
One thing that seems to wrong with this is that when I run my PositionManagerTest JUnit test - it initializes Velocity a number of times. This is because the PersonManagerImpl is re-initialized each time in my setUp() method. This is a JUnit issue, not a Spring issue. I could probably do something so the PositionManagerImpl is only created once for the entire Test run. Either that, or figure out a way to initialize Velocity for only one test. Hints would be great.
Another issue is that I'd like to use Velocity's DataSourceResourceLoader, but it only accepts a JNDI DataSource name. It'd be nice if there was an alternative version that would allow setting of the DataSource via IoC.
About the author
Matt Raible [email protected]
Blog: http://www.raibledesigns.com/page/rd
Matt currently resides in Denver where he consults as a J2EE Developer for Raible Designs and is always striving to find the easiest solutions for web applications. His current favorite technologies can be found within his open source AppFuse application. He is actively involved in the Open Source community and loves Java.