Spring Mail & Apache Velocity

Muitas vezes, quando se desenvolve um serviço de e-mail por um aplicativo, existe a necessidade de se trabalhar com templates. Estes templates, são como páginas HTML que são enviadas para os destinatários. O Spring Framework oferece uma implementação da especificação Java, para envio de email, que torna essa ação bem mais simples. Já o Apache Velocity, chamado de Template Engine, é uma biblioteca Java que adiciona dinamicamente objetos Java dentro do conteúdo de uma página HTML. Neste tutorial você vai ver como integrar o Velocity ao Spring e como enviar os dados referentes ao conteúdo do seu e-mail para o template HTML que será enviado por você.

1. Preparando o projeto

As bibliotecas para criar o projeto, serão apresentadas conforme um projeto Maven, e são as seguintes dependências:

<dependencies>
	<dependency>
		 <groupId>org.springframework.integration</groupId>
		 <artifactId>spring-integration-mail</artifactId>
		 <version>4.0.3.RELEASE</version>
	</dependency>
	<dependency>
		 <groupId>javax.mail</groupId>
		 <artifactId>mail</artifactId>
		 <version>1.4.7</version>
	</dependency>
	<dependency>
		 <groupId>org.apache.velocity</groupId>
		 <artifactId>velocity</artifactId>
		 <version>1.7</version>
	</dependency>
</dependencies>

2. Iniciando o projeto

Neste tutorial vamos criar uma classe chamada MailMessage que será a classe de domínio. Os atributos adicionados a esta classe serão aqueles que vão armazenar as informações para o envio de uma mensagem por e-mail.

Listagem 1. Classe MyMessage.
package com.mballem.tutorial.domain;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Marcio Ballem on 05/08/2014.
 */
public class MailMessage {

    private String from;
    private String personal;
    private String text;
    private String subject;
    private List<String> attachments = new ArrayList<String>();
    private String to;

    // gerar os métodos getters e setters.

    //adicionar os anexos da mensagem
    public void addAttachment(String... files) {
        for (String file : files) {
            this.attachments.add(file);
        }
    }
}

Na Listagem 1 você pode ver os atributas da classe MailMessage. Entre estes atributos há o attachments, o qual vai armazenar uma lista de arquivos que podem ser enviados pela mensagem como anexo. O atributo from recebe o e-mail de quem está enviando a mensagem, e o atributo personal, um nome qualquer dado a quem envia a mensagem. O atributo text vai receber o texto a ser enviado na mensagem e o subject é o assunto. Por fim, o atributo to endereça a mensagem a um destinatário.

Antes de apresentar outras classes, veja na Listagem 2 o conteúdo do arquivo spring-config.xml com a configuração do bean mailSender e do bean velocityEngine. O bean mailSender recebe as propriedades necessárias para configuração da conta de e-mail que vai enviar as mensagens.

Listagem 2. Arquivo spring-config.xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.mballem.tutorial" />

    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="smtp.gmail.com" />
        <property name="port" value="587" />
        <property name="username" value="seu_email@gmail.com" />
        <property name="password" value="sua_senha" />
        <property name="javaMailProperties">
            <props>
                <prop key="mail.transport.protocol">smtp</prop>
                <prop key="mail.smtp.auth">true</prop>
                <prop key="mail.smtp.starttls.enable">true</prop>
            </props>
        </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
            </value>
        </property>
    </bean>
</beans>

Já o bean velocityEngine é uma configuração necessária para integrar o Apache Velocity ao Spring Framework. A configuração adicionada a velocityProperties informa ao Velocity onde ele deve buscar o template, neste caso, será no classpath da aplicação.

Na Listagem 3, você pode visualizar o código HTML que será usado como template para o envio dos e-mails. Este arquivo, embora tenha conteúdo HTML, deve ser do tipo .vm e não .html. Observe que entre o código HTML existem algumas instruções como na tag <title>${to}</title>. Estas instruções tem por objetivo adicionar ao código, o conteúdo do objeto MailMessage.

Listagem 3. Arquivo mail.vm.
<!DOCTYPE HTML>
<html>
<head>
    <meta content="text/html" http-equiv="content-type">
    <title>${to}</title>
</head>
<body style="padding-top: 20px; padding-bottom: 40px;">
<div style="background-color: #ebebeb; border: 1px solid #CCC; margin-bottom: 8px;
            margin-top: 10px; margin-right: 8px; margin: 0 auto; max-width: 760px;">

    <div style="background-color:#9ACD32; border: 1px solid #CCC; margin-bottom: 10px;
                margin-top: 10px; margin-left: 8px; margin-right: 8px;
                font-family: Consolas; padding-bottom:1px; padding-top:1px;">
        <h2 style="padding-left: 2%">${subject}</h2>
    </div>

    <div style="background-color:#fff; border: 1px solid #CCC; margin-bottom: 10px; 
                    margin-left: 8px;  margin-right: 8px; font-family:Arial; 
                    font-size:12px; line-height:18px; text-align:left;">
        <h3 style="padding-left: 2%">
            <strong> Date: </strong>${date}
        </h3>

        <h3 style="padding-left: 2%">
            <strong> From: </strong>${personal} - ${from}
        </h3>

        <p style="padding-left: 2%; text-align: left;
                    font-family: 'Courier New', Monospace; font-size: 12pt">
            ${text}
        </p>
    </div>
</div>
</body>
</html>

Observe ainda o restante do código e verá as instruções ${subject}, ${from}, ${date}, ${personal}, e ${text}. É desta forma que o Velocity adiciona o conteúdo do objeto MailMessage ao código HTML. Para que os valores do objeto MailMessage chegue a estas instruções, é necessário usar o método mergeTemplateIntoString() da classe VelocityEngineUtils. Na classe VelocityServiceListagem 4 – o bean velocityEngine é injetado pelo Spring via método construtor.

Nesta mesma classe, a propriedade model, do tipo java.util.Map é adiciona para que os valores do objeto MailMessage sejam atribuidos ao método mergeTemplateIntoString(). As atribuições necessárias a este método são, o obejto velocityEngine, o nome do arquivo .vm, o encoding que o HTML será enviado, e o objeto model.

Listagem 4. Classe Teste.
package com.mballem.tutorial.service;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ui.velocity.VelocityEngineUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Marcio Ballem on 05/08/2014.
 */
@Service
public class VelocityService {

    private static final String ENCODING = "UTF-8";
    private static final String TEMPLATE_URL = "velocity/mail.vm";

    private VelocityEngine velocityEngine;

    @Autowired
    public VelocityService(VelocityEngine velocityEngine) {
        this.velocityEngine = velocityEngine;
    }

    private Map<String, Object> model = new HashMap<String, Object>();

    public void setModel(Map<String, Object> model) {
        this.model = model;
    }

    public String getVelocityBody() {
        return VelocityEngineUtils.mergeTemplateIntoString(
                this.velocityEngine,
                TEMPLATE_URL,
                ENCODING,
                this.model
        );
    }
}

O método mergeTemplateIntoString() retorna um objeto do tipo java.lang.String que deverá ser atribuído ao corpo da mensagem de e-mail que será enviada ao destinatário. Para enviar a mensagem de e-mail, veja a classe MailService na Listagem 5.

Listagem 5. Classe MailService.
package com.mballem.tutorial.service;

import com.mballem.tutorial.domain.MailMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Marcio Ballem on 05/08/2014.
 */
@Service
public class MailService {
    @Autowired
    private JavaMailSender javaMailSender;
    @Autowired
    private VelocityService velocityService;

    public void sendMail(MailMessage message) {
        Date date = new Date();

        message.setFrom( ((JavaMailSenderImpl) javaMailSender).getUsername() );

        //objeto que recebe os dados a serem enviados na mensagem.
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        //quando trabalhamos com anexos, devemos usar um objeto
        // do tipo MimeMessageHelper.
        MimeMessageHelper helper;

        //adiciona ao template do velocity
        Map<String, Object> model = new HashMap<String, Object>();
        model.put("subject", message.getSubject());
        model.put("text", message.getText());
        model.put("to", message.getTo());
        model.put("personal", message.getPersonal());
        model.put("from", message.getFrom());
        model.put("date", date);
        velocityService.setModel(model);

        try {
            //inicializamos o obejto helper.
            helper = new MimeMessageHelper(mimeMessage, true);
            //inserimos dados de quem está enviando a mensagem.
            helper.setFrom(message.getFrom(), message.getPersonal());
            //inserimos o destinatario
            helper.setTo(message.getTo());
            //inserimos a data de envio
            helper.setSentDate(date);
            //inserimos o texto da mensagem,
            // o atributo true indica que será em html 
            // e adicionamos o velocity no corpo da mensagem.
            helper.setText(velocityService.getVelocityBody(), true);
            //inserimos o assunto da mensagem.
            helper.setSubject(message.getSubject());
            //inserimos os anexos adicionados a lista de anexos.
            // Fazemos um for() na lista para adicionar um anexo por vez.
            for (String anexo : message.getAttachments()) {
                File attach = new File(anexo);
                helper.addAttachment("Attachment: " + attach.getName(), attach);
            }
            //objeto de envio da mensagem.
            javaMailSender.send(mimeMessage);
            System.out.println("Envio com Sucesso!");
        } catch (MailException e) {
            System.out.println("Email não pode ser eviado!n" + e.getMessage());
        } catch (MessagingException e) {
            System.out.println("Email não pode ser eviado.n" + e.getMessage());
        } catch (IOException e) {
            System.out.println("Anexo não encontradon" + e.getMessage());
        }
    }
}

Dois beans são injetados na classe MailService, um deles é o bean mailSender, do arquivo spring-confi.xml, e ou outro é referente a classe VelocityService. O objeto mailSender vai fornecer acesso ao método send(), para enviar o e-mail. Nesta parte do envio, leia os comentários adicionados ao código fonte.

O que tem de mais importante nesta classe é o método setText() do objeto helper. Este método recebe como valor o objeto velocityService.getVelocityBody(), o qual tem como retorno a String do método mergeTemplateIntoString(). Deste modo, o código HTML do arquivo mail.vm será adicionado ao corpo da mensagem, e nele já teremos os valores do objeto MailMessage adicionados via método velocityService.setModel().

As chaves do objeto model são as instruções (${}), presentes no código HTML. Assim, a instrução no código HTML deve ter o mesmo nome da chave do objeto model.

Veja na Figura 1, você pode ver a estrutura do projeto apresentado:

Figura 1 - Estrutura do projeto.

Figura 1 – Estrutura do projeto.

Analisando a imagem da Figura 1, veja que o arquivo de configuração spring-config.xml foi adicionado no diretório resources do projeto Maven. O arquivo template mail.vm, está inserido no diretório resources/velocity. Na Listagem 4, a variável TEMPLATE_URL é atribuída com o valor velocity/mail.vm. O resources não precisa ser atribuído como valor porque no arquivo spring-config.xml foi informado ao Velocity que ele deveria procurar pelo template lá no resources (classpath).

As classes SpringAccess e Application são necessárias para executar a aplicação. Na Listagem 6 temos a classe SpringAccess que inicializa os serviços do Spring Framework.

Listagem 6. Classe SpringAccess.
package com.mballem.tutorial;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Created by Marcio Ballem on 05/08/2014.
 */
public class SpringAccess {

    private static ApplicationContext springContext;

    private SpringAccess() {
    }

    public static synchronized ApplicationContext getSpringContext() {
        if (springContext == null) {
            springContext = new FileSystemXmlApplicationContext(
                    "classpath:spring-config.xml"
            );
        }
        return springContext;
    }
}

Já a classe Application tem como objetivo testar o envio de e-mail. Para isso, um código simples via modo console foi adicionado para receber os dados referentes ao envio da mensagem. Veja mais na Listagem 7:

Listagem 7. Classe Application.
package com.mballem.tutorial;

import com.mballem.tutorial.domain.MailMessage;
import com.mballem.tutorial.service.MailService;
import org.springframework.context.ApplicationContext;

import javax.swing.*;
import java.util.Scanner;

/**
 * Created by Marcio Ballem on 05/08/2014.
 */
public class Application {

    private MailService mailService;

    private JFileChooser chooser;

    public Application(MailService mailService) {
        this.mailService = mailService;
    }

    public static void main(String[] args) {
        ApplicationContext ctx = SpringAccess.getSpringContext();

        Application app = new Application(ctx.getBean(MailService.class));

        app.sendMail();
    }

    private void sendMail() {
        Scanner scanner = new Scanner(System.in);
        MailMessage message;
        int option;
        do {
            System.out.println("1. Send mail");
            System.out.println("0. Exit");
            System.out.print("> ");
            option = Integer.parseInt(scanner.nextLine());
            if (option == 1) {
                message = new MailMessage();
                System.out.print("Sender name: ");
                message.setPersonal(scanner.nextLine());

                System.out.print("To: ");
                message.setTo(scanner.nextLine());

                System.out.print("Subject: ");
                message.setSubject(scanner.nextLine());

                System.out.print("Message: ");
                message.setText(scanner.nextLine());

                System.out.print("Attachments (Y or N): ");
                if (scanner.nextLine().equalsIgnoreCase("Y")) {
                    String[] attachments = addAttachemts();
                    message.addAttachment(attachments);
                }

                mailService.sendMail(message);
            }
        } while (option != 0);
    }

    private String[] addAttachemts() {
        chooser = new JFileChooser();
        chooser.setMultiSelectionEnabled(true);

        String[] paths = new String[chooser.getSelectedFiles().length];

        int retorno = chooser.showSaveDialog(null);
        if (retorno == JFileChooser.APPROVE_OPTION) {
            paths = chooser.getSelectedFile().getAbsolutePath().split(",");
        }
        return paths;
    }
}

Na Figura 2 você pode visualizar o processo de envio de uma mensagem por meio da classe Application e na Figura 3, veja como esse e-mail é recebido pelo destinatário.

Figura 2 - Exemplo do envio de e-mail.

Figura 2 – Exemplo do envio de e-mail.

Na Figura 3 os campos em destaque mostram de onde os dados foram adicionados a partir do template mail.vm.

Figura 3 - Exemplo de e-mail recebido.

Figura 3 – Exemplo de e-mail recebido.

Este tutorial abordou o básico para a configuração e envio de mensagens com Spring Mail e o Apache Velocity. Em relação ao Velocity, você pode ainda utilizar outras instruções, como lógicas (if/else) ou mesmo fazer loop em listas uti

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...