Início > Programação > Como Adicionar Eventos ao Outlook Programaticamente

Como Adicionar Eventos ao Outlook Programaticamente

A RFC5546 especifica o iTIP (iCalendar Transport-Independent Interoperability Protocol), que é um protocolo que oferece interoperabilidade de agendamentos entre diferentes sistemas de calendários, pois não é feita referência à um protocolo de transporte específico, como o SMTP.

Nesse artigo, vamos desenvolver um exemplo em Java que utiliza templates do Velocity para formatar os eventos que serão adicionado ao Outlook.

Figura 1 – Calendário do Outlook

Adicione a dependência do Xerces e do Velocity ao seu pom:

<dependency>
   <groupId>xerces</groupId>
   <artifactId>xercesImpl</artifactId>
   <version>2.11.0</version>
</dependency>
<dependency>
   <groupId>org.apache.velocity</groupId>
   <artifactId>velocity</artifactId>
   <version>1.7</version>
</dependency>

Vamos criar uma classe para enviar um e-mail com o evento para o destinatário. O mais importante dessa classe é o tipo “text/calendar” passado para o ByteArrayDataSource empacotado em um DataHandler. É esse tipo que fará com que o Outlook interprete o e-mail como um evento que deve ser adicionado ao calendário.

import java.io.IOException;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource;

public class RFC5546MailSender{
   private Properties properties;
   private String servidorEmail;
   public RFC5546MailSender() {
      this.servidorEmail = /*Seu servidor de e-mail*/
      properties = new Properties();
      properties.put("mail.smtp.host", this.servidorEmail);
      properties.put("mail.smtp.connectiontimeout", "10000");
      properties.put("mail.smtp.timeout", "15000");
      properties.put("mail.smtp.allow8bitmime", "false");
   }

   public void enviar(String remetente, String destinatario, 
     String assunto, String mensagem) throws MessagingException {
      Session session = Session.getDefaultInstance(properties);
      MimeMessage message = new MimeMessage(session);
      message.setFrom(new InternetAddress(remetente));
      message.addRecipient(Message.RecipientType.TO, new InternetAddress(destinatario));
      message.setSubject(assunto);
      BodyPart partBody = new MimeBodyPart();
      Transport tr = null;
      try {
         partBody.setHeader("Content-Class", "urn:content-classes:calendarmessage");
         partBody.setHeader("Content-ID", "calendar_message");
         partBody.setDataHandler(
           new DataHandler(new ByteArrayDataSource(mensagem, "text/calendar")));
         Multipart multipart = new MimeMultipart();
         multipart.addBodyPart(partBody);
         message.setContent(multipart);
         tr = session.getTransport("smtp");
         Transport.send(message, message.getAllRecipients());
      } catch (IOException e) {
         throw new MessagingException(e.getMessage());
      } catch (MessagingException e) {
         throw e;
      } finally {
         if (tr != null) {
            try {
               tr.close();
            } catch (MessagingException e) {
               throw e;
            }
         }
      }
   }
}

Poderíamos concatenar strings separando a propriedade e seu valor por “\n”, mas a manutenibilidade desse código seria bem complexa. Vamos utilizar o Velocity para carregar um template com as configurações padrão da solicitação de evento:

import java.io.StringWriter;
import java.util.Map;
import java.util.Properties;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.FileResourceLoader;

public class Velocity {
   private VelocityEngine engine;
   public Velocity() {
      Properties properties = new Properties();
      properties.put("file.resource.loader.path", "TEMPLATES_DIR");
      properties.put("file.resource.loader.cache", "false");
      properties.put("file.resource.loader.modificationCheckInterval", "10");
      properties.put("file.resource.loader.class", FileResourceLoader.class.getName());
      this.engine = new VelocityEngine(properties);
   }

   public String formatar(Map<String, Object> parameters, String nomeTemplete) {
      StringWriter sw = new StringWriter();
      Template template = engine.getTemplate(nomeTemplete, "UTF-8");
      VelocityContext context = new VelocityContext();
      for (String key : parameters.keySet()) {
         context.put(key, parameters.get(key));
      }
      template.merge(context, sw);
      return sw.toString();
   }
}

Agora, vamos criar um template do Velocity com as configurações do evento chamado “solicitacao_reuniao.vm”. Esse template deve ficar no diretório “TEMPLATES_DIR” que você configurou na classe utilitária do Velocity.

BEGIN:VCALENDAR
VERSION:2.0
METHOD:REQUEST
BEGIN:VEVENT
UID:$id
SUMMARY:$titulo
DTSTART:$inicio
DTEND:$fim
LOCATION:$localizacao
DESCRIPTION:$descricao
STATUS:CONFIRMED
SEQUENCE:${sequencial}
ORGANIZER:MAILTO:$remetente
ATTENDEE;ROLE=CHAIR;ROLE=REQ-PARTICIPANT;RSVP=FALSE;PARTSTAT=ACCEPTED:MAILTO:$destinatario
CLASS:PRIVATE
BEGIN:VALARM
TRIGGER:-PT15M
ACTION:DISPLAY
END:VALARM
END:VEVENT
END:VCALENDAR

Algumas coisas merecem destaque na estrutura acima. A primeira delas é o método REQUEST, que é uma função complexa que permite a configuração de várias informações, como quais serão as pessoas convidadas para o evento. O UID é o identificador único da mensagem. É através desse campo que o sistema de calendário vincula um evento recebido a outro existente. O campo SEQUENCE não é importante para criação de eventos, mas é essencial para a atualização deles. Ele permite definir a sequência de atualizações que um evento existente deve sofrer. O alarme, que no caso será disparado 15 minutos antes do início do evento (-PT15M), é opcional.

Vamos utilizar os artefatos criados até agora para enviar a solicitação de uma reunião para o usuário que tem o e-mail “eu@empresa.com.br”:

private void solicitarReuniao() throws MessagingException{
   RFC5546MailSender rfcMailSender = new RFC5546MailSender();
   SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
   dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
   Map<String, Object> parameters = new HashMap<String, Object>();
   parameters.put("id", "123");
   parameters.put("titulo", "Solicitação de Reunião");
   parameters.put("localizacao", "2° andar, Sala 03");
   parameters.put("inicio", dateFormatter.format(new Date()));
   parameters.put("fim", dateFormatter.format(new Date()));
   parameters.put("descricao", "Solicitação de Reunião");
   parameters.put("remetente", "sistema@empresa.com.br");
   parameters.put("destinatario", "eu@empresa.com.br");
   parameters.put("sequencial", "0");
   Velocity format = new Velocity();
   String corpoEmail = format.formatar(parameters, "br/com/mail/solicitacao_reuniao.vm");
   rfcMailSender.enviar("sistema@empresa.com.br", "eu@empresa.com.br", "Solicitação de Reunião", corpoEmail);
}

Em seguida, vamos enviar uma atualização da reunião identificada por “123” utilizando o mesmo template de criação “solicitacao_reuniao.vm”. Para atualizações, é necessário informar a ordem sequencial:

private void atualizarReuniao() throws MessagingException{
   RFC5546MailSender rfcMailSender = new RFC5546MailSender();
   SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
   dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
   Map<String, Object> parameters = new HashMap<String, Object>();
   parameters.put("id", "123");
   parameters.put("titulo", "Atualização do Horário da Reunião");
   parameters.put("localizacao", "2° andar, Sala 03");
   parameters.put("inicio", dateFormatter.format(new Date()));
   parameters.put("fim", dateFormatter.format(new Date()));
   parameters.put("descricao", "Atualização do Horário da Reunião");
   parameters.put("remetente", "sistema@empresa.com.br");
   parameters.put("destinatario", "eu@empresa.com.br");
   parameters.put("sequencial", "1");
   Velocity format = new Velocity();
   String corpoEmail = format.formatar(parameters, "br/com/mail/solicitacao_reuniao.vm");
   rfcMailSender.enviar("sistema@empresa.com.br", "eu@empresa.com.br", "Atualização do Horário da Reunião", corpoEmail);
}

Por fim, vamos definir o template de cancelamento de evento “cancelamento_reuniao.vm”:

BEGIN:VCALENDAR
VERSION:2.0
METHOD:CANCEL
BEGIN:VEVENT
UID:$id
SUMMARY:$titulo
DTSTART:$inicio
DTEND:$fim
LOCATION:$localizacao
DESCRIPTION:$descricao
STATUS:CANCELLED
ORGANIZER:MAILTO:$remetente
ATTENDEE;ROLE=CHAIR;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:$destinatario
CLASS:PRIVATE
END:VEVENT
END:VCALENDAR

Nessa estrutura, destacam-se o método CANCEL e o status CANCELLED.

private void cancelarReuniao() throws MessagingException{
   RFC5546MailSender rfcMailSender = new RFC5546MailSender();
   SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
   dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
   Map<String, Object> parameters = new HashMap<String, Object>();
   parameters.put("id", "123");
   parameters.put("titulo", "Cancelamento da Reunião");
   parameters.put("localizacao", "2° andar, Sala 03");
   parameters.put("inicio", dateFormatter.format(new Date()));
   parameters.put("fim", dateFormatter.format(new Date()));
   parameters.put("descricao", "Cancelamento da Reunião");
   parameters.put("remetente", "sistema@empresa.com.br");
   parameters.put("destinatario", "eu@empresa.com.br");
   parameters.put("sequencial", "2");
   Velocity format = new Velocity();
   String corpoEmail = format.formatar(parameters, "br/com/mail/cancelamento_reuniao.vm");
   rfcMailSender.enviar("sistema@empresa.com.br", "eu@empresa.com.br", "Cancelamento da Reunião", corpoEmail);
}

Calendário Compartilhado

Em nosso exemplo, os eventos serão adicionados ao próprio calendário do usuário. Se você enviar a estrutura do evento como um anexo de extensão .ics, ele será exibido no Outlook como um calendário compartilhado ao lado do calendário do usuário:

Figura 2 – Calendário compartilhado

Mais Customizações

A própria especificação [2] afirma que oferece uma “forma padrão para fazer coisas que não são padronizadas”. A Microsoft tem um documento [3,4,5,6] que mostra como utilizar as propriedades específicas do Outlook para formatar a descrição do evento, a prioridade, cores associadas ao evento e várias outras propriedades.

Referências

1. [https://tools.ietf.org/html/rfc5546]
2. [https://tools.ietf.org/html/rfc5545#section-3.8.8.2]
3. [https://stackoverflow.com/questions/41304898/how-do-i-create-an-html-formatted-ics-message-body-using-ical-net]
4. [https://msdn.microsoft.com/en-us/library/ee624921(v=exchg.80).aspx]
5. [https://msdn.microsoft.com/en-us/library/ee625053(v=exchg.80).aspx]
6. [https://msdn.microsoft.com/en-us/library/cc463911(v=exchg.80).aspx]

Anúncios
  1. 10/08/2017 às 9:26 AM

    Parabéns pelo tutorial. Muito bem exemplificado. Era justamente o que eu estava procurando, vou tentar a implementação do mesmo ainda hoje.

    Você saberia me dizer se apos o evento criado a interação de confirmação ou recusa ao evento feita no outlook pelos convidados, se seria possível ler essa resposta com alguma api java e alimentar um sistema automaticamente?

  2. 10/08/2017 às 9:36 AM

    Muito bom o post. Bem exemplificado.

    Gostaria de saber se é possível capturar as respostas das pessoas que foram marcadas no evento através de alguma api java. Confirmação ou recusa e assim alimentar automaticamente um sistema.

    • 12/08/2017 às 12:11 PM

      Muito obrigado, desculpe pelo comentário duplicado, o browser tinha travado ai achei que não tinha dado certo..

  3. 14/08/2017 às 7:48 AM

    Olá Neto. Sem problemas.

    Depois que implementar o cliente, se você quiser, compartilha aí.

    Abs.

  1. No trackbacks yet.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: