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.

One of the requirements on my current project is to send Job Applicants an e-mail when they apply for a position. Since we're using Spring, I figured I'd try out its JavaMail and Velocity support to send this e-mail. Below is a short tutorial for setting up Spring's JavaMail support on a PositionManager class, followed by replacing the e-mail's text with a Velocity template. It's possible there's easier ways to do this, but this is what worked for me.

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.

Dig Deeper on Front-end, back-end and middle-tier frameworks