Acessando o web service de Nota Fiscal de Servicos Eletronica (NFS-e Nota Carioca) em Java

Depois de bater bastante cabeça para conseguir dar um primeiro passo na integração com o sistema de Nota Fiscal de Serviços Eletrônica (NFS-e) do município do Rio de Janeiro (Nota Carioca), vou colocar aqui o passo a passo que eu segui para contribuir com outras pessoas que possam estar passando pelo mesmo martírio.

Não encontramos muita informação detalhada por aí. A conclusão que cheguei é que quem conseguiu fazer a integração não quer divulgar como fez, pois provavelmente a implementação faz parte de algum produto comercial sendo vendido, e portanto fornecer estas informações gratuitamente vai contra a empresa.

Como este não é o meu caso, pois eu não vendo este serviço, estou apenas integrando para poder emitir NFS-e automaticamente com o Peladeiro.com, então resolvi contribuir. Aí vai.....


1- Gerar as classes Java que representam os XML consumidos pelo Web Service da prefeitura:

Meu primeiro passo foi gerar as classes Java que representam os arquivos XML definidos pela prefeitura. Para isso, baixei o XML Schema (XSD) da versao que a prefeitura usa. Baixei do site da ABRASF em http://www.abrasf.org.br/

Neste site, procurar na página de "Nota Fiscal de Servicos Eletronica" o XSD correspondente. No meu caso foi o tipos_nfse_v01.xsd

Gerar as classes Java a partir do XSD: (http://docs.oracle.com/javase/6/docs/technotes/tools/share/xjc.html)

Para isso, executei o comando:

 xjc -p com.meusistema.nfse.binding -d C:\Users\Eu\projetos\meunfse\src\java\com\meusistema\nfse\binding C:\Users\Eu\projetos\meunfse\web-app\WEB-INF\tipos_nfse_v01.xsd  

Isso gerou um monte de classes Java, cada uma representando os tipos definidos no XSD. Essas classes serão usadas para preencher com os dados a serem passados para o web service (e retornados tbem). Após usar as classes para preencher com os dados, por exemplo setCpf("34234234");, usei o JAXB para gerar a string XML a ser passada para o web service.... mas vamos chegar lá...

2- Gerar as classes Java de Cliente do Web Service através do WSDL da prefeitura.

O passo seguinte foi gerar as classes Java que representam o Client do web service. A prefeitura fornece o WSDL. No meu caso ficava aqui: https://notacarioca.rio.gov.br/WSNacional/nfse.asmx?wsdl

Só que para conseguir acessar este WSDL, foi preciso eu ter o certificado digital. Sem o certificado, nada feito. Então, comprei um certificado da Certisign, do tipo NFS-e A1. É um processo chato, pois você compra no site, mas depois tem que marcar um dia para ir até lá para entregar a documentação da empresa, dos sócios, do representante, etc.... No final, você sai com um número, acesso um site, e com este número faz o download do certificado meucert.pfx (é um certificado com extensão PFX).

Instalei o certificado na minha máquina (windows 7), e através do browser eu consegui acessar o link acima do WSDL. Salvei o WSDL na minha máquina, e gerei as classes Java a partir dele. Para isso, usei o projeto CXF (versão 2.7.8), e com isso usei wsdl2java, No meu DOS executei:

 wsdl2java -p com.meusistema.nfse -frontend jaxws21 -client "C:\Users\Eu\projetos\meunfse\web-app\WEB-INF\notacarioca-nfse.wsdl"  

3- Como conseguir acessar o Web Service através do seu sistema, sua aplicação Java cliente, usando o certificado digital PFX.

Bom, neste ponto eu já conseguia acessar o web service (na verdade o WSDL) pelo browser, mas a minha aplicação sempre recebia um erro 403 - Forbidden: Access is denied.

a) Depois de muito bater cabeça, vi que o meu certificado PFX não possuía toda a cadeia de certificados, ele possuía apenas o certificado da minha empresa. Mas era preciso ter toda a cadeia:
A cadeia na verdade é minha empresa :: autoridade certificadora :: autoridade raiz -> etc

Então, segui um passo a passo que me salvou. Obrigado ao alexegidio em http://www.guj.com.br/java/148620-nfe-erro-4037---forbidden-ao-acessar-webservices-asmx-resolvido

O passo a passo é:


       1- Importe o certificado .PFX para o IE >>> FERRAMENTAS/OPÇÕES DA INTERNET/ ABA CONTEÚDO / BOTÃO CERTIFICADOS.   
      2- Selecione a aba "Pessoal".   
      3- Clique em importar.   
      4- O Sistema vai abrir um assistente. Selecione o Certificado e clique em avançar.   
      5- Na próxima tela MARQUE O CHECBOX "Marcar esta chave como exportável. Isso possibilitará o backup e transporte das chaves posteriormente". Como o texto diz se você não marcar esta opção o certificado não poderá ser exportado   
      6- Após ser importado seu certificado vai para a aba Pessoal. Clique sobre ele e clique em "exportar"   
      7- Clique em avançar   
      8- Na próxima tela marque a opção: "Sim, exportar a chave privada". Clique em avançar.   
      9- Na janela seguinte marque o checkbox "Incluir todos os certificados no caminho de certificação, se possível". <<ESTA OPÇÃO É CRUCIAL NÃO ESQUEÇA DELA !!!   
      10- Informe a senha e a seguir o local onde o arquivo será salvo.   
      PRONTO Feito isso está pronto você já pode utilizar o certificado no seu projeto.  


b) Teve um outro pulo do gato que foi juntar este meu certificado, agora completo com toda a cadeia de certificados, com os demais certificados aceitos para acesso HTTPS. O java da sua máquina vem com um arquivo cacerts que dentro possui as autoridades certificadoras confiáveis. É assim que sua aplicação java pode acessar um web service em HTTPS. Mas agora, além de aceitar os certificados do HTTPS, nossa aplicação tem que passar um certificado privado, como forma de autenticação. Então, eu fiz o import do meu certificado PFX pra dentro de um keystore existente do java instalado na minha máquina. Para isso, copiei o cacerts original da Sun em C:\Program Files\Java\jdk1.6.0_30\jre\lib\security\cacerts para outro diretório para não quebrar o arquivo original. E então executei o seguinte comando neste arquivo cópia:

 keytool -importkeystore -srckeystore meu-certificado.pfx -destkeystore cacerts -srcstoretype PKCS12 -deststoretype JKS  

A senha do cacerts original da Sun é "changeit".

Com isso, agora eu tenho um keystore que é o arquivo cacerts, que possui também o meu próprio certificado, além dos certificados aceitos para HTTPS.

Pronto. Agora é só executar....


package com.peladeiro.nfse;

import java.io.File;
import java.io.StringWriter;

import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;

import com.peladeiro.factory.DadosNfseFactory;
import com.peladeiro.nfse.binding.EnviarLoteRpsEnvio;


public class ClienteTeste {
  
  private static final QName SERVICE_NAME = new QName("http://notacarioca.rio.gov.br/", "Nfse");

  public static void main(String[] args) throws JAXBException {
    URL wsdlURL = Nfse.WSDL_LOCATION;
        if (args.length > 0 && args[0] != null && !"".equals(args[0])) { 
            File wsdlFile = new File(args[0]);
            try {
                if (wsdlFile.exists()) {
                    wsdlURL = wsdlFile.toURI().toURL();
                } else {
                    wsdlURL = new URL(args[0]);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
        
    ClienteTeste c = new ClienteTeste();
    c.autentica();
    c.chamaWebService(wsdlURL);

  }
  
  private void autentica() {

    String caminhoDoCertificadoDoCliente = "C:\\Users\\Eu\\meu-certificado-final.pfx";
    String senhaDoCertificadoDoCliente = "minhaSenhaPrivada";
    String caminhoDoKeyStore = "C:\\Users\\Eu\\cacerts";
    String senhaDoKeyStore = "changeit";

    System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
    
    System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");
    System.setProperty("javax.net.ssl.keyStore", caminhoDoCertificadoDoCliente);
    System.setProperty("javax.net.ssl.keyStorePassword", senhaDoCertificadoDoCliente);

    System.setProperty("javax.net.ssl.trustStoreType", "JKS");
    System.setProperty("javax.net.ssl.trustStore", caminhoDoKeyStore);
    System.setProperty("javax.net.ssl.trustStorePassword", senhaDoKeyStore);
    
  }

  public void chamaWebService(URL wsdlURL ) throws JAXBException{
    
      
        Nfse ss = new Nfse(wsdlURL, SERVICE_NAME);
        NfseSoap port = ss.getNfseSoap();  
        
        // Esta Factory instancia os objetos com os dados a serem passados para o web service
        /*
        Exemplo:
        TcIdentificacaoRps idrps = new TcIdentificacaoRps();
    idrps.setNumero(new BigInteger("4321"));
    idrps.setSerie("1");
    idrps.setTipo(TIPO_RPS);
         * */
        EnviarLoteRpsEnvio envio = DadosNfseFactory.criaEnviarLoteRpsEnvio();
        
        JAXBContext jc = JAXBContext.newInstance(EnviarLoteRpsEnvio.class);
    Marshaller m = jc.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        
        StringWriter sw = new StringWriter();
    m.marshal(envio, sw);
        
        RecepcionarLoteRpsRequest recepcionarLoteRpsRequest = new RecepcionarLoteRpsRequest();
    recepcionarLoteRpsRequest.setInputXML(sw.toString());
    RecepcionarLoteRpsResponse recepcionarLoteRpsResponse = port.recepcionarLoteRps(recepcionarLoteRpsRequest);
    System.out.println("recepcionarLoteRps.result=" + recepcionarLoteRpsResponse.outputXML);
  }

}


E é isso!
Abcs



Artigo interessante do pessoal do Financial Times mostrando detalhes da Web App para dispositivos móveis:
http://coding.smashingmagazine.com/2013/05/23/building-the-new-financial-times-web-app/

Algumas bibliotecas que eu não conhecia, ou que eles criaram:
https://github.com/ftlabs/ftellipsis
https://github.com/ftlabs/fruitmachine
https://github.com/ftlabs/ftscroller
https://github.com/davidaurelio/TouchScroll

Além disso, usaram LocalStorage para guardar JavaScript e CSS para a aplicação poder funcionar Offline, e guardarm as imagens em IndexedDB ou WebSQL.

Cada um atacando os desafios mobile de formas diferentes....

primeira iPhone App - Novo Nono Dígito para Celulares de São Paulo

Em parceria com meu amigo JP, fizemos uma aplicação para iPhone que resolve o problema de atualizar todos os telefones de celulares de São Paulo da nossa agenda do iPhone.

O problema é que em 29 de julho agora, os números de telefone celular de São Paulo ganharão um nono dígito 9. Então, se o telefone do paulista era xxxx-xxxx, agora será 9xxxx-xxxx.

E o aplicativo 9D9 tá aí pra resolver isso:
www.9d9.com.br

Colocamos no ar essa semana, mas a Apple ainda não aprovou.... aguardemos mais uns dias....

Vendo corretamente os caracteres especiais com UTF8 no Putty

Quando uso o Putty para me conectar com servidores linux remotos, e lá dentro uso caracteres especiais (como acentos da língua portuguesa). muitas vezes tenho problemas para visualizar estes caracteres.
Por exemplo, quando faço um select no BD Postgres, e no resultado da query tem caracteres especiais codificados em UTF8, fica tudo feio....
Para resolver isso, abra o Putty. Na janela inicial, lá no menu Window > Translation, defina o item Remote Character Set para "UTF-8". Só isso.
Abcs

Google Calendar feito com Grails

Eu não escrevo há algum tempo, eu sei....sinto muito...... mas o tempo tem estado muito curto. Mas hoje vi um post que não pude deixar de repassar por aqui, pois vale a pena....

O post é de Craig Burke mostrando uma aplicação Grails, desde a modelagem de domínio até plugins usados, que simula o Google Calendar. Bem interessante:

Parte 1: http://www.craigburke.com/blog/2012/02/09/creating-google-calendar-in-grails-part-1-the-model/
Parte 2: http://www.craigburke.com/blog/2012/02/16/creating-google-calendar-in-grails-part-2-displaying-the-calendar/

Abcs a todos.

Montando um super PC de alta performance

Um amigo me mandou um email me dando dicas para montagem de um PC de alta performance. Ele escreveu o que fez para montar a máquina dele, que disse ter ficado um verdadeiro avião. Ficou tão bom o email que aqui vai ele. Valeu Paulo Goes......:

"Felipe,


O coração do sistema é um processador i7 2600K. É o processador mais potente para uso pessoal (os outros i7 já são workstation ou servidor). Este processador incorpora um conjunto de instruções gráficas, de modo que dispensa o uso de processador gráfico, mesmo na placa mãe. O i7 2600 (sem o K) é um pouco mais barato, possui a mesmíssima capacidade de processamento, só não é “overclockable” e tem uma versão anterior do driver gráfico Intel (é uma excelente opção para reduzir um pouco o custo). Os i5 (acho que o i5 equivalente é o 2500/2500K) têm potência equivalente (e a mesma relação K/sem K dos i7), mas não suportam hiperthreading. Com hiperthreading as 4 CPUs do processador dual core aparecem como oito para o sistema operacional, proporcionando um ganho de paralelismo (se e somente se o seu mix de aplicações demandar tantos processos concorrentes). Assim os i5 são também excelentes opções para baratear o custo.

O componente seguinte é a placa mãe. Atualmente estou bem exigente neste quesito, e só estou comprando ASUS ou Intel. As Intel estão mais baratas, e bem interessantes. No entanto, eu tinha alguns requisitos adicionais:

- queria 16G de memória – isto hoje só é economicamente viável com placas que tenham 4 bancos de memória. A maioria das placas mais baratas só estão vindo com 2 bancos, mas as memórias de 8G estão ainda proibitivamente caras;
- tenho alguns dispositivos IDE, e as placas mais modernas e baratas só têm SATA
- meu adorado monitor ainda é VGA, e as placas mais modernas e baratas têm apenas DVI/HDMI, e eu não queria usar conversores
- SATA 6GB/s – as placas mais modernas têm interface SATA de 6GB/s. Na prática, não faz a menor diferença, porque os HDs não conseguem entregar nem os 3GB/s do SATA “II”. No entanto (ver abaixo), uma das principais “turbinas” do meu “avião” é um disco SSD que atinge com facilidade este throughput, de modo que SATA 6GB/s era mandatório.
- gosto de placas ATX – nada de micro-ATX ou mini-ATX (embora, na prática, sejam quase equivalentes)
- mouse, teclado, cam, impressora, scanner, iPhone, disco externo, pen-drives, tudo lá em casa é USB. Desejo uma placa mãe com o maior número possível de USBs externas.

No mais, escolher uma placa é uma combinação de marca, com os slots/dispositivos que você necessita, mais aqueles que você acha que vai precisar no futuro. Neste sentido, achei que umas portas USB 3 seriam altamente desejáveis, e que um firewire não seria demais. Aí, é entrar no Boadica, filtrar por fornecedor, sortar por valor, e ir olhando as configurações até achar a primeira que te satisfaça. Escolhendo uma, entrar no Tomshardware, para ver se há algum comentário negativo sobre esta combinação processador/placa. Acabei escolhendo uma Asus, mas nem lembro o modelo agora.

O terceiro componente importante é um SSD de 120G da OCZ (Vertex 3). Este é um componente ainda complicadinho. Memórias SSD têm um burnout depois de alguns milhões de escritas (ou seja, têm uma vida útil limitada). As controladoras mais modernas (Sandforce) maximizam a vida útil distribuindo igualmente as atividades de escrita por setores diferentes do disco, evitando assim, por exemplo, que o começo do disco estrague antes do final. Uma boa controladora também é essencial para conseguir os tais 6GB/s de throughput. Os OCZ são as Ferrari dos SSD. Tem uma outra linha (que não é Vertex) que é um pouco mais barata, mais ainda muito boa. Me aconselharam a evitar os Kingston (bem mais baratos) e me disseram que os Corsair talvez sejam bons. Li alguns artigos dizendo que os Samsung estão causando sensação por serem mais baratos e tecnologicamente competentes, mas não quis arriscar. A idéia é instalar o Windows neste SSD, conseguindo assim uma baita velocidade nos boots e na carga de qualquer programa/OS component. Configurar corretamente o Windows para maximizar o benefício e a vida útil do SSD é uma arte obscura, na qual ainda estou me iniciando (deixa o paging ou não? temporary IE files? temp files? como tirar os dumps do Windows do SSD? etc.).

O gabinete é outro aspecto que eu tomo cuidado. Gabinetes devem ser silenciosos, práticos para montar (por exemplo, não necessitam de parafusos para fixar os HDs) e bem ventilados. Já a alguns anos que venho usando os gabinetes CoolerMaster. Sempre usei a linha básica (Elite), mas este eu comprei da linha ultra-silenciosa (Sileo), que vem também com duas ventoinhas (os Elite vêm com uma só – a gente sempre pode acrescentar ventoinhas, mas as originais da CoolerMarster são hiper-silenciosas).

A fonte é um componente fundamental para a saúde do equipamento. A qualidade das fontes caiu, e fontes anteriormente de boa qualidade (como as Seventeen) hoje são consideradas suspeitas. Comprei uma Coolermaster 600W reais, meio overkill para o meu equipamento (uma de 450W já bastava) porque não tenho nenhum periférico/placa no meu ambiente que não seja um “array” de HDs (que consomem bem pouca energia), mas, pensando no futuro...

A memória, comprei Corsair 4x4GB. Eu sempre usei Kingston (que custa metade do preço) sem problemas, mas meu filho me convenceu a comprar as Corsair... provavelmente bobagem.

O componente final é um HD de 1TB (ainda o menor custo por byte armazenado), para se juntar aos meus atuais 2,5TB.

Um aspecto interessante é que eu não jogo games e não faço processamento gráfico, de modo que não tenho placa de vídeo há anos (uso onboard ou, agora, inprocessor).

Finalmente, como comprei: Primeiro, eu uso o Boadica para estabelecer referência de preços. Os preços do Boadica são muito bons, e normalmente não consigo estes preços nem na Santa Efigênia aqui em sampa. Como estava sem tempo, minha estratégia desta vez foi adquirir na Internet. Para começar, identifiquei no Buscapé os fornecedores web mais confiáveis para componentes (nenhum é muito confiável, nem inspira muita confiança, de modo que é preciso ser um pouco tolerante e aceitar algum risco aqui). Pré-selecionei uma meia dúzia, fiz uma análise de preços, e acabei ficando com 3 (Atera, Superbalão, e Kabum). Comprar em loja, e ainda mais parcelado (12x) se afasta um pouco dos preços do Boadica, mas é uma forma de diminuir o risco e o impacto imediato no bolso). Você não precisa fazer todas as compras na mesma loja, mas também não deseja comprar um componente em cada loja (maior risco de problemas e despesas de entrega), assim, tem que fazer algumas simulações para ver qual fornecedor consegue o menor preço médio dos componentes combinados. Comprei o gabinete, a fonte e as memórias na Kabum, e o processador, a placa mãe, e o HD no Superbalão. O SSD arrisquei um pouco mais e comprei no mercado livre.

A compra no mercado livre foi a mais bem sucedida, embora o mercado pago tenha feito uma bobagem e faturado duas vezes o meu cartão. Escrevi para o vendedor, e ele em algumas horas estornou a venda (o mercado pago não ajuda você, você tem que se virar), e em menos de 24h após a compra o SSD estava lá em casa. Com a Kabum tive vários problemas por conta da operadora American Express (nunca tinha usado e nunca mais usarei). Cancelei a compra, refiz pelo Visa, e 48h depois recebi a mercadoria, sem problemas. No geral, fiquei muito satisfeito com a Kabum, por causa do comportamento deles nestes dificuldades com a AMEX. O pior foi o Superbalão. A entrega levou uma semana (após meu cartão ter autorizado), e o HD chegou sem lacre e arranhado (parece que já havia sido instalado em algum PC antes). Deu algum trabalho para trocar (eles queriam que eu pagasse o frete, e eu tive que chiar muito), e ainda estou aguardando receber a troca.

That’s it. Isso tudo requereu muita pesquisa, conversa, visitas a fóruns e o escambau. A parte mais chata são sempre estas relações comercias selvagens do Brasil, combinadas com a estupidez da Amex. Por uns 10K reais faço tudo de novo prá você..."

Isso é que é dica..... Sensacional....

Grails de alta performance - parte 2

Este post faz parte de uma série que estou escrevendo sobre técnicas para fazer uma aplicação grails de alta performance. Estou usando uma aplicação de exemplo, para demonstrar estas técnicas. Esta aplicação se chama GPerform, e está disponível no GitHub, e é uma espécie de Twitter + Instagram (em menor escala, naturalmente), onde usuários enviam posts de texto curto + foto.

Na parte 1 desta série, eu mostrei a primeira prática para se desenvolver uma aplicação grails de alta performance: Não usar Collections para as relações hasMany.

Neste post vou mostrar os plugins que usei.

1) Spring-events plugin:

Este plugin dá a possibilidade de publicar eventos via Spring, de forma assíncrona.

Para instalar o plugin, basta executar "grails install-plugin spring-events".

Eu usei este plugin para lançar um evento indicando que alguém enviou um novo post no sistema, e que, portanto, o sistema precisa processar a foto (criando uma foto com tamanho (kb) menor, e outras fotos thumbnail).

Mas, para não deixar o usuário esperando enquanto o sistema processa as fotos, e assim dar uma impressão de muita velocidade de resposta para o usuário, eu quis fazer este processamento de forma assíncrona, dando uma resposta para o usuário quase que imediata após o envio do seu post.

A action que recebe o envio do post do usuário, com texto + foto, apenas grava a foto num diretório, salva a entidade Foto no banco de dados, e lança este evento assíncrono no Spring, retornando a resposta imediatamente para o usuário (sem processar a foto neste momento). Veja:

    def compartilhar = {
        
        String msgErro = ""
        
        if(!session.usuario){
            redirect(controller:'home')
            return
        }
        
        // Pega a foto do formulario e salva no diretorio para ser processada
        MultipartFile fotoOriginal = request.getFile('fotoOriginal')
        String extensao = identificaExtensao(fotoOriginal)
        
        String nomeArquivo = RandomStringUtils.random(10).encodeAsSHA256() + extensao
        
        try {
            File destino = new File(ConfigurationHolder.config.gp.fotos.originais.folder + File.separator + nomeArquivo)
            fotoOriginal.transferTo(destino)
            
            // Salva uma isntancia do objeto de metadados Foto
            Foto foto = new Foto(params)
            foto.autor = Usuario.get(session.usuario.id)
            foto.nomeArquivo = nomeArquivo
            if(foto.save(flush:true)){
                
                // metodo adicionado pelo spring-events Plugin
                publishEvent(new FotoSubidaEvent(new Expando(id:foto.id)))
                flash.message = "Foto enviada com sucesso. Aguarde processamento."
                redirect(action:index)
            }
            else{
                if(destino.exists()) destino.delete()
                msgErro = "Erro ao salvar os dados da foto."
                render(view:'index', model:[msgErro:msgErro, foto:foto])
            }
        } catch (IOException ioe) {
            msgErro = "Erro ao fazer upload do arquivo."
        } catch (IllegalStateException ie) {
            msgErro = "Erro ao fazer upload do arquivo."
        }
    }

Veja que o método importante aqui é o "publishEvent()", que publica o evento FotoSubidaEvent (eu sei que o nome ficou feio, mas ...).  Pronto, o evento foi publicado.

Agora, precisamos criar uma classe que vai ser o Listener deste evento. A maneira mais fácil que encontrei foi criar uma classe de serviço que implementa "ApplicationListener", ou seja, ela será um Listener para os eventos do tipo FotoSubidaEvent. Ao implemetar esta interface, sua classe de Service terá que ter um método "void onApplicationEvent(FotoSubidaEvent event)". Este método será chamado pelo Spring automaticamente quando um evento do tipo FotoSubidaEvent for lançado, de forma assíncrona.

2) Springcache plugin:

Este plugin faz uso do projeto Spring Cache,  e permite que se faça:
- Cache de métodos dos beans do Spring; por exemplo, para fazer cache do retorno de métodos das classes de serviços. 
- Fragmentos das páginas, ou páginas inteiras, geradas pelos controllers; por exemplo, para fazer o cache de uma determinada action de um controller; 

No caso da aplicação de exemplo, GPerform, usei o Springcache para fazer cache da action "HomeController.recentes()" , ou seja, eu coloquei em cache os posts de fotos mais recentes. Assim, não é preciso acessar o banco de dados o tempo todo para pegar os posts mais recentes, pois eles estão na memória do cache.

Para usar este plugin, basta instalar usando o comando "grails install-plugin springcache". Em seguida, coloquei uma anotação na action do meu controller (na action que quero que a resposta fique em cache):

    @Cacheable("recentesCache")
    def recentes = {
        log.info "Executando action 'recentes' e por isso nao ta usando o cache agora."
        def recentes = buscaFotosMaisRecentes(20)
        [fotosMaisRecentes:recentes]
    }

Veja que eu coloquei um nome neste cache ("recentesCache"), pois eu posso criar vários caches distintos, e controlar cada um da forma que eu quiser. Esta anotação @Cacheable("recentesCache") é que indica para o plugin que o retorno desta action precisa ser guardado em memória no cache. Só isso.

Agora o problema passa a ser como renovar o cache quando um novo post for enviado por um usuário do site. Para isso, basta limpar o cache (matar o cache) quando um novo post for inserido. No meu caso, o que fiz foi o seguinte: quando um usuário envia um novo post (que é um texto + foto), o controller recebe esta requisição, faz algumas verificações, salva a foto num diretório para ser processada posteriormente (de forma assíncrona -- falo sobre isso mais tarde), e grava os dados da entidade Foto no banco de dados (que são os metadados sobre a foto e o texto do post do usuário). Quando o sistema for processar a foto de forma assíncrona, aí sim a foto está pronta para ser mostrada no site, e é neste momento que eu preciso limpar o cache. O método que processa a foto e limpa o cache é um método de uma classe de Serviço (FotoService). Veja abaixo:


    @CacheFlush("recentesCache")
    void onApplicationEvent(FotoSubidaEvent event) {
        if(log.isInfoEnabled()) log.info "Executando evento FotoSubidaEvent..."
        
        Foto foto = Foto.get(event.source.id)
        if(foto){
            // processa a imagem da foto
        }
        
    }

Assim que este método for executado pela aplicação, o cache será limpado (jogado fora), pois este método tem a anotação @CacheFlush("recentesCache"). Veja que usei o mesmo nome "recentesCache" para indicar qual cache quero limpar.

Perceba também que este é aquele método "onApplicationEvent()", citado no item 1 acima, usado pelo Spring quando o framework detecta o lançamento do evento do tipo FotoSubidaEvent.

Pronto. Da próxima vez que a action HomeController.recentes() for executada, o Springcache vai ver que o "recentesCache" não existe mais, aí vai executar a action, pegar seu resultado e colocar em cache novamente. E enquanto este cache existir, a action não é executada, e ao invés disso, o conteúdo do cache é enviado para o usuário.

Um pequeno detalhe deste plugin é que ele tem uma configuração padrão que indica que o cache deve existir por apenas 120 segundos. Como era muito pouco para o meu caso, então configurei para que o cache vivesse por 3600 segundos. Veja como:

springcache {
    defaults {
        eternal = false
        diskPersistent = false
        timeToLive= 86400
    }
    caches {
        recentesCache {
            timeToLive= 3600
        }
    }
}



Ficamos por aqui com este artigo que já tá longo. Ainda falta coisa pra falar, como resources plugin, cache-headers plugin, cached-resources plugin e zipped-resources plugin. Até a próxima.

Lembre-se que você é bem vindo para contribuir com esta aplicação de exemplo GPerform via o GitHub. Abcs a todos.