Tuesday, May 7, 2013

Email Notification using Spring and Velocity

Velocity

Velocity is a java based template engine. It can be used to generate dynamic web pages by accessing the java objects at runtime.

Why Velocity Templates?


1.  Very useful to create the dynamic email templates depending on the data at runtime.
2.   Allows you to specify the email style using html <span style=”...”>.
3.   Can have multiple templates for sending notification emails in different languages
 

Email Notification using Velocity and Spring Framework

            Spring eases the email notification implementation is easy to integrate with Velocity.

     Velocity Template for email Creation

The email html message body and subject can be created on the fly using Velocity Templates. We can have two template files (.vm) for email subject and message.

            emailSubject.vm
User  ${userDTO.userId} login acknowledgement

emailBody.vm
<html>

<body>

<span style="font-size:10pt; font-family:Trebuchet MS, Helvetica, sans-serif;">

This email is to acknowledge the login of User: ${userDTO.userName} to the application.

</span>

</body>

</html>
In that the dynamic data will be represented by the placeholders (for example, ${UserDTO.userId} and ${UserDTO.userName}) andat runtime they will be replaced with the data from the java object (UserDTO) .

     Prerequisites

         Jars:
1.  velocity-1.5.jar
- Velocity engine to load velocity templates
2.  spring-context-support-3.0.5.RELEASE.jar
- Provides implementation for mail sender, which is used to send email using SMTP protocol.
3.  mail-1.4.1.jar
- Provides implementation to create the mime style email message
4.  spring-core-3.0.5.RELEASE.jar
- It’s the core module which requires for all spring modules.
5.  commons-logging-1.1.1.jar
6.  log4j-1.2.14.jar
- for logging purpose
7.  spring-beans-3.0.5.RELEASE.jar
- Spring's core beans and bean factory support
8.   spring-context-3.0.5.RELEASE.jar
- Provides implementation for the spring application context.
9.   spring-asm-3.0.5.RELEASE.jar
10. spring-expression-3.0.5.RELEASE.jar
11. commons-collections-3.2.jar
12. commons-lang-2.6.jar

            SMTP Mail HostName: Need to have a SMTP host name to send and retrieve email.

      Configuration

notification-config.xml

1.       Define the mailsender spring bean, which is used to send email using SMTP protocol.

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
                                <!-- Set the mail server host, typically an SMTP host.  -->
                                <property name="host" value="${hostName}"></property>
</bean>

-          Where host is the SMTP mail host name.

The place holder is replaced with the value from the notification.properties (hostname=<your SMTP mail host name>)

Note: By default SMTP will use the default port 25. If your mail host port is different then provide the port number using the property port in the mailSender bean configuration.

If you want to have authentication before sending email, provide the user credentials for the mail account (for example, user account details in GMAIL mail host) define the mailSender bean with the following properties.

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="javaMailProperties">
<props>
<prop key="mail.smtp.auth">true</prop>
<prop key="mail.smtp.starttls.enable">true</prop>
</props>
</property>
<property name="host" value="smtp.gmail.com"></property>
<property name="port" value="587"></property>
<property name="username"value="username"/>
<property name="password"value="password"/>
</bean>
Where javaMailProperties is a properties object used to set the JavaMail properties in the current JavaMail session. Within that properties object provide all the required properties

1.  mail.smtp.auth: specifies whether you need to provide auth. details (usename and password for the email account).
2.  mail.smtp.starttls.enable: states to use the TLS encrypted connection.
Note: If you don’t provide these two properties then the specified username and password will not be sent to the mail server by the JavaMail runtime for authentication; so will not be able to send email from application.
3.  Username- username for the account at the mail host.
4.  Password- password for the user account.
5.  Host – SMTP mail host.
6.  Port – port used to send email.
2.   Define the velocityEngine bean, which is used to load the velocity templates defined for your notification email creation.

<bean id="velocityEngine"
                                class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
                                <property name="resourceLoaderPath" value="classpath:/" />
                                <property name="preferFileSystemAccess" value="false" />
</bean>
Note: If it is a web application, the templates should be placed under a specific folder in WEB-INF/classes folder (for example,/WEB-INF/classes/mailtemplates/).
3.   Define a notificationService bean, which is used to create and send the notification using java mail, Velocity and Spring APIs.

<bean id="iNotificationService" class="com.felix.service.NotificationService">
                                <property name="mailSender">
                                                <ref bean="mailSender"/>
                                </property>
                                <property name="velocityEngine">
                                                <ref bean="velocityEngine"/>
                                </property>                         
</bean>

4.    Define the mailTemplate bean which has two properties which hold mail velocity templates (emailSubject.vm and emailBody.vm).


<bean id="mailTemplate" class="com.felix.dto.MailTemplateDTO">

                                <property name="mailSubjectTemplateFile" value="emailSubject.vm" />

                                <property name="mailBodyTemplateFile" value="emailBody.vm" />

</bean>


      Implementation

NotificationService.java

1.   First you need to create theJavaMail MimeMessage(which represents the email that you need to send) by invoking the createMimemessage () method on the JavaMailSenderImpl.
MimeMessage message = mailSender.createMimeMessage();

2.   Create a new instance of the MimeMessageHelper for the MimeMessage instance that you have created.

MimeMessageHelper messageHelper = new MimeMessageHelper(message);

Note: MimeMessageHelper is a helper class for easy population of the given javax.mail.internet.MimeMessage. It provides setter methods to set the underlying MimeMessage properties. For example, setter methods for to, cc, bcc, subject etc.

3   Set the MimeMessage properties using the MimeMessageHelper instance, using the details from the NotificationDTO.

messageHelper.setTo((notificationDTO.getTo()));
messageHelper.setSubject(VelocityEngineUtils.mergeTemplateIntoString(velocityEngine,                  mailTemplateDTO.getMailSubjectTemplateFile(), model));

String text = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine,
mailTemplateDTO.getMailBodyTemplateFile(), model);

VelocityEngineUtils is a utility class to merge the velocity template with the model (map) to generate the email subject and message dynamically at runtime. The model is a map with key as the string and value as the model object. The velocity template location will get from the velocityEngine instance.

The place holders in the template files should be {model key name}.{property names of the model object}. For example:

Model object
Map<String, Object> model = newHashMap<String, Object>();
model.put("userDTO", userDTO);
model.put("notificationDTO", notificationDTO);

emailSubject.vm
User  ${UserDTO.userId} login acknowledgement

After merging it will replace the placeholders with the actual value and generate the subject and message string. For example, it generates the subject string asUser 1234 login acknowledgement, where 1234 is the userId.

4.   Send the MimeMessage using the send() method of JavaMailSenderImpl

this.mailSender.send(message);

     Notification Client

If it is a web application, you can invoke the Notification Service from any of your service classes or controller class.
A standalone sample client to invoke the NotificationService to send notification:


public static void main(String[] args) {

                 Logger log = Logger.getLogger(NotificationDriver.class);
                 URL url = org.apache.log4j.helpers.Loader
                                                                .getResource("log4j.properties");
                 PropertyConfigurator.configure(url);
                 log.info("Log4j using URL = " + url);

                 ApplicationContextapplicationContext = new ClassPathXmlApplicationContext(
                                                                "notification-config.xml");

             INotificationServiceiNotificationService = (INotificationService)          applicationContext.getBean("iNotificationService");
                 
                      MailTemplateDTO mailTemplate = (MailTemplateDTO) applicationContext
                                                                .getBean("mailTemplate");
                               
MailTemplateDTOmailTemplateSpanish = (MailTemplateDTO) applicationContext.getBean("mailTemplateSpanish");

                                String[] toArray = { "test@gmail.com" };

                                NotificationDTO notificationDTO = new NotificationDTO();

                                notificationDTO.setFrom("admin@gmail.com");
                                notificationDTO.setTo(toArray);
                                UserDTO userDTO = new UserDTO();

                                userDTO.setUserID("1234");
                                userDTO.setUserName("Felix");
                                userDTO.setUserAddress("Bangalore");

                                Map<String, Object> model = newHashMap<String, Object>();

                                model.put("userDTO", userDTO);
                                model.put("notificationDTO", notificationDTO);


                                try {
                                   iNotificationService.sendEmail(notificationDTO, mailTemplate, model);

                                                //To send a email notification on spanish
                          /*iNotificationService.
                                               sendEmail(notificationDTO,mailTemplateSpanish, model);*/
                                } catch (NotificationServiceExceptionnotifException) {

                                   log.error("Error occured while sending email notification",

                                                                                notifException);
                                }
                }

      Issues Faced

1. Exception in thread "main" java.lang.NoClassDefFoundError: 
                                                                                                org/springframework/asm/ClassVisitor

Solution: Include spring-asm-3.0.5.RELEASE.jar in the classpath.

2.       Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/expression/PropertyAccessor

Solution: Include spring-expression-3.0.5.RELEASE.jar

3.       Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'velocityEngine' defined in class path resource [notification-config.xml]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/collections/ExtendedProperties

Solution: Include commons-collections-3.2.jar

4.       org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'emailSubject.vm'

Solution: As the exception says it cannot find the template file using the ClasspathResourceLoader. So replace the velocityProperties with two properties resourceLoaderPath and preferFileSystemAccess. It solves me the issue.

<beanid="velocityEngine"
                                class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
                                <propertyname="resourceLoaderPath"value="classpath:/"/>
                                <propertyname="preferFileSystemAccess"value="false"/>
                                <!--
                                                <property name="velocityProperties"><value>
                                                resource.loader=classpath                                                class.resource.loader.class=org.apache.velocity.runtime.resource
                                                .loader.ClasspathResourceLoader</value></property>
                                -->
</bean>

5.       Exception in thread "main" org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException

Solution: This is because you haven’t provided the correct email user account credentials (username and password) for authentication. If the SMTP host used is a Gmail SMTP host, then provide a proper Gmail user account details for authenticating it against the Gmail host before sending the email, for example, <name>@gmail.com and its password.
6.   Email is not delivered to the provided Gmail account. 
      Soultion:
·   Provide mail.smtp.auth and mail.smtp.starttls.enable as true.
·   Provide proper hostname and port (smtp.gmail.com, 587)
·   Provide the correct user account credentials for authentication
   Provide the proper email address in the ‘to’ email address field.