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.
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.
<?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
.
<!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 VelocityService
– Listagem 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
.
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.
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.
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.
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:
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.
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.
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