Arquivo

Archive for the ‘Programação’ Category

Como Gerar um Arquivo XLSX com o Apache POI

No artigo Uma Proposta de Design para Composição e Geração de Arquivos CSV, compartilhei as idéias que tive ao projetar uma solução para escrita de arquivos CSV de acordo com a RFC4180, mas não compartilhei o código porque realmente não havia nada de interessante na minha implementação. Poderia ter utilizado o OpenCSV para fazer o que eu precisava, mas valeu a pena utilizar um problema simples para refletir sobre design.

Nesse artigo, vamos utilizar as abstrações criadas à partir da proposta de design para trabalhar com CSV para gerar uma planilha XLSX com o Apache POI, que é um framework open source Java que possibilita a leitura e a escrita de dados em um documento do Microsoft Office. Uma segunda implementação ajuda a melhorar e ratificar a proposta de design.

Figura 1 – Design da escrita de arquivos

Nossa implementação utiliza o SXSSF, que é uma extensão do XSSF (Excel 2007 OOXML) utilizada para minimizar a utilização de memória nos casos em que planilhas muito grandes devem ser geradas. Primeiro vamos às dependências, que não são poucas:

	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi</artifactId>
	    <version>3.17</version>
	</dependency>

	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi-ooxml</artifactId>
	    <version>3.17</version>
	</dependency>
	
	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi-ooxml-schemas</artifactId>
	    <version>3.17</version>
	</dependency>

	<dependency>
	    <groupId>stax</groupId>
	    <artifactId>stax-api</artifactId>
	    <version>1.0.1</version>
	</dependency>
	
	<dependency>
	    <groupId>xml-apis</groupId>
	    <artifactId>xml-apis</artifactId>
	    <version>1.4.01</version>
	</dependency>

	<dependency>
	    <groupId>org.apache.xmlbeans</groupId>
	    <artifactId>xmlbeans</artifactId>
	    <version>2.6.0</version>
	</dependency>
	
	<dependency>
	    <groupId>commons-codec</groupId>
	    <artifactId>commons-codec</artifactId>
	    <version>1.11</version>
	</dependency>

	<dependency>
	    <groupId>dom4j</groupId>
	    <artifactId>dom4j</artifactId>
	    <version>1.6.1</version>
	</dependency>

Se você estiver trabalhando com HSSF (Excel 97-2007), só precisa utilizar o core do POI, pois não há XML envolvido:

	<dependency>
	    <groupId>org.apache.poi</groupId>
	    <artifactId>poi</artifactId>
	    <version>3.17</version>
	</dependency>

No código abaixo, destaco o construtor new SXSSFWorkbook(1000). O parâmetro do construtor indica a quantidade de linhas que serão mantidas em memória. Se a quantidade de linhas em memória form maior que o parâmetro, o arquivo temporário em disco receberá as linhas que estão em memória:

public abstract class POIWriter {
   private OutputStream out;
   private SXSSFSheet sheet;
   private SXSSFWorkbook workbook;
   private Header[] headers;

   public POIWriter(OutputStream out) throws IOException {
      this.out = out;
      workbook = new SXSSFWorkbook(1000);
      sheet = (SXSSFSheet) workbook.createSheet("Planilha de Teste");
   }

   public void write(Header... headers) throws IOException {
      this.headers = headers;
      int cellIndex = 0;
      org.apache.poi.ss.usermodel.Row poiRow = sheet.createRow(0);
      for (Header value : headers) {
         Cell cell = poiRow.createCell(cellIndex++);
         cell.setCellValue(value.getTitle());
      }
   }

   public void write(Row row) throws IOException {
      int cellIndex = 0;
      org.apache.poi.ss.usermodel.Row 
         poiRow = sheet.createRow(sheet.getPhysicalNumberOfRows());
      for (Field<?> value : row.getFields()) {
         Cell cell = poiRow.createCell(cellIndex++);
         cell.setCellValue(value.getValue());
      }
   }

   public void close() throws IOException {
      for (int columnIndex = 0; columnIndex < headers.length; columnIndex++) {
         sheet.trackColumnForAutoSizing(columnIndex);
         sheet.autoSizeColumn(columnIndex);
      }
      workbook.write(out);
      out.flush();
      out.close();
   }
}

Por fim, criei duas classes concretas: uma que cria um arquivo XLSX em disco e outra que escreve em um OutputStream, o que é útil para fazer download de arquivos via Servlet:

public class POIFileWriter extends POIWriter {
   public POIFileWriter(String fileName) throws IOException {
      super(new FileOutputStream(new File(fileName)));
   }
}

public class POIOutputStreamWriter extends POIWriter {
   public POIOutputStreamWriter(OutputStream out) throws IOException {
      super(out);
   }
}

Para a implementação que escreve no OutputStream da HttpServletResponse, o content type pode ser “application/octet-stream” ou algo mais específico das aplicações da Microsoft e a extensão do arquivo presente no header “Content-disposition” tem que ser “.xlsx”.

Referências

1. [https://kodejava.org/how-do-i-format-cell-style-in-excel-document/]
2. [https://stackoverflow.com/questions/11529542/changing-cell-color-using-apache-poi]
3. [https://poi.apache.org/spreadsheet/index.html]
4. [https://www.devmedia.com.br/apache-poi-manipulando-documentos-em-java/31778]

Anúncios

Como Desabilitar Teclas de Atalho no Navegador

Se você precisar desabilitar teclas de atalho no navegador, pode utilizar jQuery. No exemplo abaixo, os atalhos para recortar (Ctrl+X), copiar (Ctrl+C) e colar (Ctrl+V) são desabilitados no $( document ).ready(), ou seja, quando a página estiver pronta para executar JavaScript:

$(document).ready(function () {
   $('body').bind('cut copy paste', function (e) {
      e.preventDefault();
   });
});

A Melhor Forma de Mapear um Relacionamento OneToOne com JPA

Normalmente, em um relacionamento um-para-um anotado com @OneToOne, a entidade principal e a entidade secundária têm seus próprios identificadores, mas a entidade secundária também armazena uma referência para a chave estrangeira da entidade principal.

Figura 1 – Relacionamento de duas tabelas (note que é um M:N e não necessariamente um 1:1)

  CREATE TABLE TBL_PESSOA(  
    PK_PESSOA NUMBER(10) NOT NULL, 
    NM_PESSOA    VARCHAR2(256) NOT NULL, 
   CONSTRAINT PK_PESSOA PRIMARY KEY (PK_PESSOA)
  );

  CREATE TABLE TBL_PET( 
    PK_PET NUMBER(10) NOT NULL, 
    FK_PESSOA NUMBER(10) NOT NULL, 
    NM_PET    VARCHAR2(256) NOT NULL, 
    CONSTRAINT PK_PET PRIMARY KEY (PK_PET),
    CONSTRAINT FK_PET_PESSOA FOREIGN KEY (FK_PESSOA)
    REFERENCES TBL_PESSOA(PK_PESSOA)
  );

Com o modelo tradicional de mapeamento um-para-um, o banco de dados normalmente indexa tanto a chave primária quanto a chave estrangeira, o que é interessante para diminuir o scanning das tabelas, mas isso tem um custo na aplicação: mesmo anotando com FetchType.LAZY sem optional, a tabela A (pai) se comportará como FetchType.EAGER, o que é ruim para performance e uso de memória.

Se é um relacionamento um-para-um, significa que uma linha da tabela B estará relacionada à apenas uma linha da tabela A. Sendo assim, faria mais sentido utilizar a chave estrangeira da tabela A como chave primária da tabela B. Para isso, vamos utilizar a anotação @MapsId. Utilizando essa anotação, você não precisa de um relacionamento bidirecional, mas esse não é nosso propósito: vamos apenas utilizar a chave primária de uma tabela Pessoa em uma tabela Pet – nesse modelo, uma Pessoa só pode ter um Pet:

Figura 2 – Relacionamento entre Pessoa e Pet

A implementação simplificada abaixo desdobra esse relacionamento:

@Entity
@Table(name = "TBL_PESSOA")
public class Pessoa {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "PK_PESSOA")
   private Long id;

   @OneToOne(mappedBy = "pessoa", cascade = CascadeType.ALL, 
       fetch = FetchType.LAZY, optional = true)
   private Pet pet;
}

@Entity
@Table(name = "TBL_PET")
public class Pet {
   @Id
   private Long id;

   @MapsId
   @OneToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "FK_PESSOA")
   private Pessoa pessoa;

}

No banco de dados, o relacionamento é armazenado assim:

  CREATE TABLE TBL_PESSOA(	
    PK_PESSOA NUMBER(10) NOT NULL, 
    NM_PESSOA    VARCHAR2(256) NOT NULL, 
	  CONSTRAINT PK_PESSOA PRIMARY KEY (PK_PESSOA)
  );

  CREATE TABLE TBL_PET( 
    PK_PESSOA NUMBER(10) NOT NULL, 
    NM_PET    VARCHAR2(256) NOT NULL, 
    CONSTRAINT PK_PET PRIMARY KEY (PK_PESSOA),
    CONSTRAINT FK_PET_PESSOA FOREIGN KEY (PK_PESSOA)
    REFERENCES TBL_PESSOA(PK_PESSOA)
  );

Figura 3 – Relacionamento 1:1 com PK compartilhada

Pesquisa Ortográfica Utilizando Oracle e a API de Criteria

No artigo Pesquisa Ortográfica Utilizando Oracle e Criteria, mostrei como fazer uma busca ortográfica utilizando Hibernate Criteria para maximizar as chances de um dado conteúdo ser encontrado no banco de dados Oracle. Um colega do trabalho, o mesmo cara que investigou comigo aquele problema do Bouncy Castle, utilizou a API de Criteria para realizar a mesma pesquisa. Utilizei as indicações dele para refazer o meu exemplo:

private List<Frase> pesquisar(String valor) {
   CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
   CriteriaQuery<Frase> query = builder.createQuery(Frase.class);
   Root<Frase> from = query.from(Frase.class);
   List<Predicate> lp = new ArrayList<>();
   lp.add(builder.like(builder.function("TRANSLATE", String.class, 
     builder.upper(from.<String>get("valor")), 
     builder.literal("ŠŽšžŸÁÇÉÍÓÚÀÈÌÒÙÂÊÎÔÛÃÕËÜÏÖÑÝåáçéíóúàèìòùâêîôûãõëüïöñýÿ"),
     builder.literal("SZszYACEIOUAEIOUAEIOUAOEUIONYaaceiouaeiouaeiouaoeuionyy")),
     "%" + normalizar(valor))
   );
   query.where(lp.toArray(new Predicate[0]));
   query.distinct(true);
   return entityManager.createQuery(query).getResultList();
}

Categorias:Programação Tags:

Como Encontrar Linhas Duplicadas no Banco de Dado

Ultimamente estou trabalhando bastante com consultas SQL. Essa dica pode ser bastante útil para você que deseja descobrir que linhas estão duplicadas no banco de dados de acordo com algum critério. Você precisa, basicamente, fazer um join da tabela analisada com ela mesma. Aqui há várias dicas de como fazer essa consulta, mas o select abaixo funcionou bem para mim:

SELECT *
FROM TABLE A
WHERE EXISTS (
  SELECT 1 FROM TABLE
  WHERE COLUMN_NAME = A.COLUMN_NAME
  AND ROWID < A.ROWID
)
Categorias:Programação Tags:,

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]

Como Habilitar o Ehcache no Shiro

O Shiro oferece um mecanismo de cache para diminuir o tempo de respostas das operações de segurança, mas as implementações disponibilizadas podem ser utilizadas para propósitos fora do escopo do framework de segurança.

Figura 1 – Arquitetura do Shiro

Várias implementações do CacheManager estão disponíveis. Nesse artigo, mostraremos como habilitar o EhCacheManager, o que permite utilizar o Ehcache. Antes de continuar, sugiro que você veja o artigo de introdução ao Shiro que escrevi e um outro artigo sobre a integração do Ehcache ao Spring, onde faço algumas considerações sobre o cache em si.

Comecemos pela configuração das dependências do Maven:

   <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.3.2</version>
   </dependency>
   <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>3.3.1</version>
    </dependency>  
    <dependency>
      <groupId>javax.cache</groupId>
      <artifactId>cache-api</artifactId>
      <version>1.0.0</version>
    </dependency>

Em seguida, vamos configurar o arquivo shiro.ini

[main]
[main]
apprealm = br.com.app.autorizacao.AppRealm
securityManager.realms = $apprealm
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager

Uma vez definido um CacheManager, o Shiro o entrega para as classes que implementam a interface CacheManagerAware. Nosso AppRealm é um SimpleAccountRealm. Sendo assim, já estamos implementando aquela interface.

Referências

1. [https://shiro.apache.org/caching.html]
2. [https://shiro.apache.org/static/1.3.0/apidocs/org/apache/shiro/cache/Cache.html]
3. [https://shiro.apache.org/static/1.3.0/apidocs/org/apache/shiro/cache/ehcache/EhCacheManager.html]
4. [http://www.ehcache.org/]