Integración de Spring con el envío de emails: técnicas avanzadas (II).
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Maven: el pom.xml.
- 4. Creación de un nuevo proveedor de mensajes.
- 5. Configuración del applicationContext.xml.
- 6. La plantilla de Velocity.
- 7. Creación de un nuevo testCase.
- 8. El resultado.
- 9. Conclusiones.
1. Introducción
Este tutorial es el tercero de una serie que analiza el soporte que proporciona Spring para el envío de correo electrónico:
- Integración de Spring con el envío de emails: en el que mostramos el servicio de envío y la posibilidad de adjuntar archivos fácilmente,
- Integración de Spring con el envío de emails: técnicas avanzadas (I): en el que mostramos el envío de correo en formato HTML y la posibilidad de incrustar recursos (imágenes, css,..) en el propio fuente del mensaje,
- Integración de Spring con el envío de emails: técnicas avanzadas (II): el que nos ocupa ahora, en el que veremos como hacer uso de una plantilla de Velocity para imprimir dinamismo a la construcción del cuerpo del mensaje.
Velocity es un motor de plantillas basado en Java. Con Velocity se pueden crear páginas web, SQL, clases java y cualquier otro tipo de salida de plantillas.
Se puede utilizar como una aplicación independiente para generar código fuente e informes, o como un componente integrado en otros sistemas.
Spring nos proporciona un motor de Velocity para generar, de forma dinámica, el cuerpo de nuestros mensajes de correo electrónico.
En este tutorial, basándonos en la arquitectura definida en el tutorial anterior:
- un servicio de envío de correo electrónico, y
- una familia de proveedores de mensajes,
vamos a extender la funcionalidad de nuestra aplicación para incluir un nuevo proveedor de mensajes, basado en el motor de Velocity.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil Asus G1 (Core 2 Duo a 2.1 GHz, 2048 MB RAM, 120 GB HD).
- Sistema operativo: Windows Vista Ultimate.
- JDK 1.5.0_15
- Eclipse 3.4, con q4e.
3. Maven: el pom.xml.
Añadimos al pom.xml las siguientes dependencias:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<project ... > ... <dependency> <groupId>velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>org.apache.velocity.tools</groupId> <artifactId>velocity-tools</artifactId> <version>2.0-alpha1</version> </dependency> ... </project> |
La librería velocity es estrictamente necesaria y la librería velocity-tools la incluimos porque vamos a hacer uso de
una clase de utilidades que contiene para el formateo de fechas dentro de la plantilla.
4. Creación de un nuevo proveedor de mensajes.
Vamos a crear un nuevo proveedor de mensajes basado en el motor de Velocity que proporciona Spring:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
package com.autentia.training.spring.mail; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.tools.generic.DateTool; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.ui.velocity.VelocityEngineUtils; import com.autentia.training.spring.model.User; public class VelocityEmailMessageProviderImpl implements EmailMessageProvider { private Map<String,Resource> inlineFiles; private String subject; private String body; private String template; @Autowired private VelocityEngine velocityEngine; public String getSubject(){ return subject; } public void setSubject(String subject){ this.subject = subject; } public String getBody() { return body; } public Map<String, Resource> getInlineFiles() { return inlineFiles; } public void setInlineFiles(Map<String, Resource> inlineFiles) { this.inlineFiles = inlineFiles; } public void setTemplate(String template){ this.template = template; } public void fillblanks(String courseName, Date date, User user) throws IOException { Map<String,Object> model = new HashMap<String,Object>(); model.put("date", new DateTool()); model.put("user", user); model.put("courseDate", date); model.put("courseName", courseName); this.body = VelocityEngineUtils.mergeTemplateIntoString( velocityEngine, template, model); } } |
La clase cumple la intefaz definida para los proveedores mensajes de correo electrónico.
En la línea 27 solicitamos al contexto de Spring el motor de Velocity para llevar a cabo la generación en base a la plantilla.
El método setTemplate(), que queda fuera de la interfaz, recibe un String con el path de la plantilla de Velocity.
Mediante el método fillBlanks, damos contenido al modelo que tendremos disponible en la plantilla de Velocity, de este modo tendremos accesibles las siguientes variables:
- user: con la instancia de un objeto de tipo User, que tiene las propiedades básicas de un usuario destinatario de un correo electrónico,
- courseDate: con una fecha que mostraremos en el cuerpo del correo,
- date: es una instancia de la clase de utilidades DateTool de Velocity que nos permitirá formatear la fecha,
- courseName: con un cadena (el nombre del curso) que también mostraremos en el cuerpo del correo.
El objeto de tipo User no es más que un POJO, que podría ser una entidad dentro de una capa de persistencia:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package com.autentia.training.spring.model; public class User { private String name; private String email; public User(String name, String email){ this.name = name; this.email = email; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setEmail(String email) { this.email = email; } public String getEmail() { return email; } } |
5. Configuración del applicationContext.xml.
Añadimos la definición de nuestro proveedor de mensajes dentro del applicationContext.xml (cuidado con tener dos beans con el mismo id!!!):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<beans ... > ... <bean id="emailMessageProviderImpl" class="com.autentia.training.spring.mail.VelocityEmailMessageProviderImpl"> <property name="subject" value="${email.subject.trainingConfirmation}"/> <property name="template" value="email/trainingConfirmation.vm"/> <property name="inlineFiles"> <map> <entry> <key> <value>id001</value> </key> <value>classpath:generic.css</value> </entry> <entry> <key> <value>id002</value> </key> <value>classpath:autentia.gif</value> </entry> </map> </property> </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 dateToolAttribute=org.apache.velocity.tools.generic.DateTool </value> </property> </bean> ... </beans> |
Dos beans:
- el proveedor de mensajes: la definición es similar a la ya planteada solo que, en este caso, recibe la ruta a la plantilla de Velocity,
- VelocityEngineFactoryBean: que nos va a permitir acceder a motor de plantillas de Velocity que provee Spring.
Podemos modificar el valor de la propiedad del fichero de propiedades app.properties para que tenga un mensaje más genérico:
1 |
email.subject.trainingConfirmation=Confirmación de celebración de charla |
6. La plantilla de Velocity.
La carpeta de recursos de nuestro proyecto tendrá la siguiente estructura de directorios, ahora con la plantilla de Velocity en el mismo directorio que tenemos la de HTML estático:
La plantilla trainingConfirmation.vm, puede tener un contenido parecido al que sigue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <style type="text/css" media="all"> @import url(cid:id001); </style> <title>Confirmación de la celebración de la charla sobre "{courseName}"</title> </head> <body> <div id="header"> <img src="cid:id002" /> </div> <div id="content"> <p>Hola ${user.name},</p> <p>Te confirmamos la celebración de la charla sobre "${courseName}" del $date.format("dd/MM/yyyy", $courseDate).</p> <p>Un saludo.</p> <p>El equipo de Autentia.</p> </div> <div id="footer"> <p>De conformidad con lo establecido en la legislación vigente en materia de Protección de Datos de Carácter Personal (Ley Orgánica de Protección de Datos, 15/1999 de 13 de Diciembre), la empresa Autentia Real Business Solutions S.L. comunica al receptor de éste correo electrónico que sus datos personales forman parte de un fichero automatizado titularidad de la empresa, cuya finalidad es el mantenimiento de la relación empresarial y comercial con usted o con su empresa: envío de información y publicidad sobre ofertas de servicio, promociones, recomendaciones y convocatorias de cursos y seminarios.</p> <p>Usted podrá ejercitar los derechos de acceso, rectificación, oposición y cancelación de sus datos, dirigiéndose por escrito o correo electrónico a Autentia Real Business Solutions S.L. email@example.com. Avda. Castilla, 2, Edificio Italia, 1ª Planta Parque Empresarial San Fernando 28830 San Fernando de Henares. Madrid.</p> </div> </body> </html> |
Destacamos:
- línea 14: ahora el saludo está personalizado hacía el usuario que recibe el correo, con ${user.name} se imprime el user.getName(),
- línea 15: imprimimos el nombre del curso y formateamos la fecha del mismo.
Básicamente, imprimimos el contenido asignado al modelo, si bien el lenguaje VTL de Velocity permite hacer uso de estructuras de control más complejas.
7. Creación de un nuevo testCase.
Vamos a probar su funcionamiento añadiendo al test un nuevo método que realice la invocación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
... /** * Probamos el envío en base a una plantilla de Velocity. * * @throws MessagingException */ @Test public void testVelocityTemplateResources() throws MessagingException { Calendar calendar = Calendar.getInstance(Locale.getDefault()); calendar.set(Calendar.DAY_OF_MONTH, 22); calendar.set(Calendar.MONTH, 0); calendar.set(Calendar.YEAR, 2009); try { User user = new User("Jose Manuel Sánchez", "firstname.lastname@example.org"); ((VelocityEmailMessageProviderImpl) emailMessageProvider) .fillblanks("Spring Framework", calendar.getTime(), user); mailService.send(user.getEmail(), emailMessageProvider); } catch (Exception e) { final String msg = "Excepción en el envío de emails con recursos incrustados."; log.warn(msg, e); Assert.fail(msg); } } ... |
El método fillBlanks recibe:
- el nombre del curso,
- la fecha del mismo,
- una instancia del objeto usuario, a quien va dirigido el correo.
8. El resultado.
Tras la ejecución del test deberíamos tener en la bandeja de entrada de nuestro cliente de correo un mensaje como el que sigue:
9. Conclusiones.
Ahora podemos generar el contenido de nuestros correos electrónicos, de forma dinámica, basándonos en plantillas de Velocity.
Parece que nuestro código está lo suficientemente modularizado y desacoplado, puesto que hemos modificado el objeto de nuestros correos,
extendiendo su funcionalidad, sin modificar la implementación existente:
- solo hemos incluido una nueva clase, el nuevo proveedor de mensajes, y
- modificado la configuración del contenedor de Spring, para que ahora se haga uso del mismo.
Podeis descargaros el proyecto configurado para Maven con el código de los tres tutoriales.
Un saludo.
Jose