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() + ",\n\n");
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.\n\n");
txt.append(" http://raibledesigns.com/positions/status.jsp\n\n"); // doesn't really exist ;-)
txt.append("Sincerely, \n\nRaible 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.