Integración de Spring con el envío de emails: técnicas avanzadas (I)

1
16819

Integración de Spring con el envío de emails: técnicas avanzadas (I).

0. Índice de contenidos.

1. Introducción

Este tutorial es un complemento al publicado recientemente sobre el envío
de email mediante el soporte que proporciona Spring
, de hecho se basa en el ejemplo que se plantea de creación de un servicio de envío de correo electrónico.

Spring nos ofrece un emisor de correo electrónico y una familia de plantillas, que cubren todas nuestras necesidades de envío. En este tutorial vamos a realizar una prueba de concepto
de la plantilla que nos permite incrustar recursos dentro del fuente de nuestros correos electrónicos. Ya hemos visto como podemos adjuntar un documento, ahora vamos a enviar un email con
formato html, incrustando ciertos recursos en línea para la correcta visualización del mensaje.

La implementación del emisor en Spring no es más que un wrapper de javax.mail, si no disponéis de Spring en vuestros proyectos siempre podéis echar un vistazo a este tutorial sobre el envío de correo electrónico mediante javax.mail.

Para comprobar el funcionamiento del servicio añadiremos un método al test de JUnit que levantará el contexto de Spring.

La redacción de este tutorial se realiza dando por hecho que el lector tiene conocimientos suficientes sobre Spring IoC, Maven y JUnit 4.4 y ha realizado la lectura del tutorial en el se basa este.

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.

El pom.xml es el planteado para el ejemplo del primer tutorial,
añadiendo una librería adicional que no es estríctamente necesaria.


...
		
		    org.springframework.batch
		    spring-batch-infrastructure
		    1.1.3.RELEASE-A
		 
...


Spring batch es un proyecto de Spring framework que tiene como objetivo principal proporcionar los servicios e interfaces comúnes que cualquier aplicación batch
puede utilizar.

Nosotros hacemos uso de una clase de utilidades para la lectura de ficheros planos, si bien, podéis hacer uso de las técnicas que estéis utilizando en la actualidad
para realizar dichas lecturas, y prescindir de esta librería.

4. Modificación del servicio de envío de emails.

Vamos a modificar la interfaz MailService que establece cómo deben
ser las clases de servicio para el envío de emails en nuestra aplicación añadiendo un nuevo método.

...
  public void send(String to, EmailMessageProvider messageProvider);
...

La idea es tener una familia de proveedores de mensajes que, en función de la implementación concreta, implementen la lógica de obtención de los recursos para el
envío del email de donde sea necesario. De esta forma desacoplamos la construcción del mensaje del propio envío.

La nueva definición del método en la interfaz nos obliga a implementarlo en nuestra clase de servicio MailServiceImpl:

...

	/** envío de email 
	 * @param to correo electrónico del destinatario
	 * @param messageProvider messageProvider 
	 */
	public void send(String to, EmailMessageProvider messageProvider) {
		// chequeo de parámetros 
		Assert.hasLength(to, "email 'to' needed");
		Assert.notNull(messageProvider);

		// asegurando la trazabilidad
		if (log.isDebugEnabled()) {
			final boolean usingPassword = !"".equals(mailSender.getPassword());
			log.debug("Sending email to: '" + to + "' [through host: '" + mailSender.getHost() + ":"
					+ mailSender.getPort() + "', username: '" + mailSender.getUsername() + "' usingPassword:"
					+ usingPassword + "].");
			log.debug("isActive: " + active);
		}
		// el servicio esta activo?
		if (!active) return;

		// plantilla para el envío de email
		final MimeMessage message = mailSender.createMimeMessage();

		try {
			// el flag a true indica que va a ser multipart
			final MimeMessageHelper helper = new MimeMessageHelper(message,true);
			helper.setTo(to);
			helper.setSubject(messageProvider.getSubject());
			helper.setFrom(getFrom());
			// el flag a true indica que el cuerpo del mensaje es HTML
			helper.setText(messageProvider.getBody(),true);

			// añadiendo los ficheros "en línea"
			if (messageProvider.getInlineFiles() != null) {
				for (String key : messageProvider.getInlineFiles().keySet()) {
					Resource value = messageProvider.getInlineFiles().get(key);
					helper.addInline(key, value);
					if (log.isDebugEnabled()) {
						log.debug("File '" + value + "' added.");
					}
				}
			}

		} catch (MessagingException e) {
			new RuntimeException(e);
		}

		// el envío
		this.mailSender.send(message);
	}
...

El código es similar al método creado, en el primer tutorial, en la misma clase, que envia archivos adjuntos. Está comentado, pero añadimos lo siguiente:

  • línea 33: el método setText acpeta un segundo parámetro que indica si el cuerpo del mensaje es HTML,
  • líneas 35 a 44: recorremos el listado de recursos en línea del proveedor de mensajes para añadirlos con la clave establecida al cuerpo del mensaje,

5. Creación del proveedor de mensajes.

Vamos a ver qué contiene la definición de la interfaz del proveedor de mensajes:

package com.autentia.training.spring.mail;

import java.util.Map;

import org.springframework.core.io.Resource;

public interface EmailMessageProvider {
	
	public String getSubject();
	
	public String getBody();
	
	public Map getInlineFiles();

}

El proveedor de mensajes encapsula el asunto, el cuerpo y un mapa con las claves y los contenidos a añadir al cuerpo del mensaje.

Ahora veremos una clase de implementación de dicha interfaz:

package com.autentia.training.spring.mail;

import java.io.IOException;
import java.util.Map;

import org.springframework.batch.item.file.separator.ResourceLineReader;
import org.springframework.core.io.Resource;

public class SimpleEmailMessageProviderImpl implements EmailMessageProvider {

	private Map inlineFiles;
	
	private String subject;
	
	private String body;
	
	@SuppressWarnings("unused")
	private Resource template;
	
	public String getSubject(){
		return subject;
	}
	
	public void setSubject(String subject){
		this.subject = subject;
	}
	
	public String getBody() {
		return body;
	}
	
	public Map getInlineFiles() {
		return inlineFiles;
	}
	
	public void setInlineFiles(Map inlineFiles) {
		this.inlineFiles = inlineFiles;
	}

	public void setTemplate(Resource template) throws IOException {
		StringBuffer fileContent = new StringBuffer();
		ResourceLineReader reader = new ResourceLineReader(template);
		String line;
		while ((line = (String) reader.read()) != null) {
			fileContent.append(line);
		}
		this.body = fileContent.toString();
	}
}

No es más que un POJO con un método extra: setTemplate que recibe como parámetro un Resource
(la interfaz Resource de Spring Framework no es más que un wrapper de File o de URL, según el caso concreto). Se apoya en la clase de utilidades
ResourceLineReader de Spring Batch para realiar la lectura del contenido del recurso y asignárselo al cuerpo del mensaje.

¿De donde obtenemos el contenido del mensaje?, nos vendrá dado, inyectado, por la configuración del contenedor de Spring.

6. Configuración del applicationContext.xml.

Vamos a añadir la definición de nuestro proveedor de mensajes dentro del applicationContext.xml:


...
	
	
	  
	  
	   
		
			
				
					id001
				
				classpath:generic.css
			
			
				
					id002
				
				classpath:autentia.gif
			
		
	  
	
...	


Destacamos lo siguiente:

  • línea 6: el asunto lo obtenemos añadíendolo al fichero de propiedades que teníamos definido: app.properties
  • línea 7: la plantilla la obtenemos de un fichero html creado al efecto dentro de la carpeta de recursos
  • líneas 8 a 23:los recursos a incrustar en el cuerpo del mensaje los pasamos dentro de un mapa de clave:valor, donde la clave es un identificador único y el valor la obtención de un recurso de classpath.
    Vamos a probar a añadir dos recursos: una css y una imagen.

El código de nuestro fichero de propiedades app.properties ahora tiene ademas esta clave:

email.subject.trainingConfirmation=Confirmación de celebración de la charla sobre 'Spring Framework'

7. La plantilla HTML.

La carpeta de recursos de nuestro proyecto tendrá la siguiente estructura de directorios:

La plantilla trainingConfirmation.html, puede tener un contenido parecido al que sigue:





Confirmación de la celebración de la charla sobre "Spring Framework"


	
	

Hola,

Te confirmamos la celebración de la charla sobre "Spring Framework" del 22 de enero de 2009.

Un saludo.

El equipo de Autentia.

Las claves únicas que habíamos declarado en el mapa de recursos, en la definición del proveedor de mensajes, dentro del applicationContext.xml, las tenemos aquí asignadas:

  • en la línea 5: en la importación de la hoja de estilos css,
  • en la línea 11: en el fuente de la imagen a mostrar en la cabecera del mensaje.

La imagen se corresponde con el logo de la empresa y la css puede tener un contenido parecido al siguiente:

body {
	font-family:Verdana;
}
div {
	margin:20px;
}
#header {
	font-size:1.2em;
	padding-bottom:10px;
	border-bottom:1px solid #123456;
}
#content {
	font-size:0.9em;
}
#footer {
	font-size:0.7em;
	padding-top:10px;
	border-top:1px solid #123456;
}

8. Creación de un nuevo testCase.

Vamos a probar su funcionamiento añadiendo al test:

  • la inyección del proveedor de mensajes (línea 4), y
  • un nuevo método que realice la invocación (líneas 11 a 20).
...
	
  @Resource
	private EmailMessageProvider emailMessageProvider;
	
	/**
	 * Probamos el envío con imagenes incrustadas
	 * @throws MessagingException 
	 */
	@Test
	public void testInlineResources() throws MessagingException {		

		try {
			mailService.send("jmsanchez@autentia.com", 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 hecho de tener desacoplado tanto el proveedor de mensajes como el servicio de envío nos permitiría simular su
funcionamiento, desde el entorno de test, creándo nuestros propios Mock Objects.


10. 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:

Tanto la visualización en formato HTML como la de los estilos dependerá del cliente de correo que estemos usando, con gmail
no parece que se puedan incrustar css, de ahí que quizás la maquetación del cuerpo del mensaje en HTML no podamos dejarla en manos de una hoja de estilos externa.
Para probar la funcionalidad que nos ocupa nos basta.


9. Conclusiones.

Ampliamos la funcionalidad básica que planteábamos en el primer tutorial y comprobamos que, si tenemos todo bien desacoplado, nos resultará
sencillo extender.

Tratamos de sentar las bases para cumplir con el principio: «Open for extension, closed for modifications«,
de modo que nuestro código sea cada vez más mantenible al estar menos acoplado, por ende más reusable, más fácilmente configurable y más escalable.

Un saludo.

Jose

mailto:jmsanchez@autentia.com

1 COMENTARIO

  1. Hola, lo que no veo en que momento introduces las imágenes o los archivos css en el \\\»helper\\\». Otra cuestión, se conoce como funciona con la hoja de estilos externa con web-outlook ? Cuando hablas de este problema… funciona si los estilos están en el propio html ?

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad