Tratamento de Mensagens
Mensagens em uma aplicação Uma aplicação bem estruturada, seja na plataforma Web ou Desktop, deve exibir mensagens informativas, de aviso, ou de erro para o usuário após efetuar determinadas tarefas. Por exemplo, após gravar um registro no banco de dados, é aconselhável que a aplicação exiba uma mensagem informativa. Se alguma exceção ocorreu, é preciso exibir uma mensagem de erro. Ou seja, a severidade da mensagem deve ser escolhida de acordo com o resultado da execução. Veja na tabela a seguir a definição de cada um dos níveis de severidade da mensagem em uma aplicação e um exemplo de texto associado: Níveis de severidade em mensagens Severidade Utilização Exemplo de texto Informação Usada quando da ocorrência normal ou esperada no fluxo, com o objetivo de confirmar ao usuário uma situação de sucesso. Aluno incluído com sucesso. Aviso Usada quando uma regra de negócio ou validação qualquer da aplicação tenha desviado o fluxo normal de execução. A turma selecionada já está lotada. Matrícula do aluno não efetuada. Erro Reservado para o caso de uma situação anormal, tal como exceções provenientes de ambiente (falha de conexão na rede, queda de um servidor de banco de dados, etc) tenha impedido a execução. Não foi possível efetuar a modificação do aluno.
Em uma aplicação construída usando-se arquitetura em camadas, as mensagens geralmente são originadas em uma determinada camada e precisam ser transmitidas às demais até chegar ao usuário. Por exemplo, se ocorre um erro de gravação no banco de dados, isto é, na camada de persistência, tal informação precisa ser repassada às camadas subsequentes, ou seja, as camadas de negócio e posteriormente de controle e visão. Este conceito é justamente chamado de tratamento de mensagens, o qual, ao lado do tratamento de exceções, auxilia o fluxo de execuções em uma aplicação bem arquiteturada. Em resumo, é preciso programar a troca de mensagens entre as diversas camadas de uma aplicação orientada a objetos. Veremos na seção a seguir como o Demoiselle Framework pode ajudar o desenvolvedor na tarefa de troca de mensagens em uma aplicação.
Introdução ao mecanismo Ortogonalmente às camadas verticais da aplicação (i.e., apresentação, negócio e persistência), a arquitetura proposta pelo Demoiselle Framework fornece os contextos de transação, segurança e mensagem. Este último é justamente responsável pela troca de mensagens entre as camadas. Tecnicamente falando, o contexto de mensagens no Demoiselle Framework é representado pela interface MessageContext. Esta interface contém métodos destinados a inclusão, recuperação e limpeza de mensagens no contexto. Para obter uma instância do contexto de mensagens, basta injetá-lo na classe: A maneira mais simples de se inserir uma mensagem no contexto é invocando o método add() de MessageContext passando como argumento o texto literal da mensagem: Opcionalmente, pode-se indicar a severidade da mensagem: Quando a severidade não é informada (argumento de tipo SeverityType), será considerado o nível informativo, isto é, INFO. Para a transmissão das mensagens no contexto é também fornecida a interface Message, a qual permite com que os textos sejam obtidos de catálogos especializados, tais como arquivos de propriedades ou banco de dados. Essa interface precisa ser implementada em uma classe customizada pelo desenvolvedor. Sua estrutura é bem simples: A classe DefaultMessage fornecida pelo Demoiselle Framework é uma implementação da interface Message que faz uso dos arquivos de recurso da aplicação para obtenção das descrições das mensagens. Especificamente utiliza o arquivo messages.properties. A unidade básica de manipulação de mensagens no Demoiselle Framework é a interface Message. Basta que ela seja implementada na aplicação para que o contexto de mensagens possa manipulá-la. A classe DefaultMessage é oferecida como implementação padrão dessa interface. Para incluir uma mensagem no contexto usando o tipo Message é preciso invocar o método add() de MessageContext passando o objeto como argumento. Eis um exemplo disso utilizando a classe DefaultMessage: Uma vez inseridas no contexto em determinada camada da aplicação, as mensagens podem ser posteriormente recuperadas. Para tal, é preciso invocar o método getMessages() da interface MessageContext, o qual retornará uma coleção de objetos do tipo Message. A extensão para JavaServer Faces no Demoiselle Framework transfere automaticamente as mensagens incluídas no MessageContext para a apresentação durante a renderização da página pelo JSF. Para remover todas as mensagens existentes no contexto, basta invocar o método clear() da interface MessageContext. Especificamente para aplicações Java Web, o contexto de mensagens é automaticamente reinicializado a cada requisição HTTP. Ou seja, as mensagens incluídas no contexto por uma determinada sessão de usuário não interferem nas demais sessões existentes no servidor de aplicações. Além disso, ao final da requisição as mensagens existentes são automaticamente excluídas do contexto. O contexto de mensagens MessageContext tem o seu ciclo de vida gerenciado pelo CDI e pertence ao escopo de sessão (i.e., @SessionScoped). Ou seja, mensagens incluídas na requisição de um determinado usuário não serão exibidas para um outro usuário, pois cada um possuirá a sua sessão. O contexto de mensagens, representado pela interface MessageContext, é capaz de armazenar diversas mensagens em uma mesma requisição. Ele não é restrito a aplicações do tipo Web, isto é, pode ser usado também para aplicações do tipo desktop (i.e., Swing).
Parametrização das mensagens Um recurso importante no tratamento de mensagens de uma aplicação consiste na parametrização dos textos que elas carregam. Isso é extremamente útil para indicar com detalhes uma situação específica, com o objetivo de melhor informar o usuário. Por exemplo, ao invés de simplesmente exibir Exclusão de disciplina não permitida é preferível mostrar o seguinte texto mais explicativo Exclusão não permitida: disciplina está sendo usada pela turma 301. Para efetuar essa parametrização, é preciso usar a formatação de textos padronizada pela classe java.text.MessageFormat da API do Java, que basicamente considera a notação de chaves para os argumentos. O Demoiselle Framework usa a MessageFormat para efetuar a parametrização e formatação dos valores. Para usar essa funcionalidade, basta incluir as chaves na string da mensagem e ao inseri-la no contexto especificar os parâmetros: Eis um exemplo mais avançado do uso de parametrizações em mensagens: O resultado da execução deste código seria um texto como: Às 13:45:05 do dia 03/01/11 houve falta do professor na turma 502. Ou seja, os argumentos {0}, {1} e {2} são substituídos por seus respectivos elementos na ordem em que são passados no método add(), sendo que ainda podemos formatar esses valores. É possível passar mais de um parâmetro nas mensagens adicionadas no contexto, usando para isso a sintaxe de formatação da classe java.text.MessageFormat. A ordem dos argumentos passados no método add() reflete nos elementos substituídos na string.
Internacionalização das mensagens A DefaultMessage oferece a funcionalidade de internacionalização da aplicação, ou seja, a disponibilização dos textos em diversos idiomas. Numa aplicação do tipo Web, o mecanismo de internacionalização faz com que as mensagens e textos sejam exibidos de acordo com o idioma selecionado pelo usuário na configuração do navegador. Como já foi citado anteriormente, a implementação DefaultMessage busca os textos das mensagens no arquivo de recursos messages.properties, o qual possui entradas no conhecido formato chave=valor. Todavia, para fazer uso desses textos, é preciso passar a chave da mensagem desejada como argumento na invocação do construtor da classe. Este identificador precisa estar entre sinais de chaves, tal como no exemplo a seguir: Ou seja, ao invés de usar a string literal, passamos o identificador da chave presente no arquivo de propriedades. O símbolo de chaves {} na string do construtor da classe DefaultMessage indica se o texto da mensagem será recuperado de um arquivo de recursos ou considerado o texto literal. Eis um exemplo de conteúdo do arquivo messages.properties destinado a manter por padrão os textos no idioma português brasileiro (i.e., pt-BR). Veja que ele contém a chave ALUNO_INSERIR_OK usada no exemplo anterior: A fim de prover internacionalização para o idioma inglês americano (i.e., en-US) no exemplo em questão, eis o conteúdo do arquivo messages_en_US.properties: Similarmente, o arquivo messages_fr.properties se destina a prover as mensagens no idioma francês (independente do país), contendo as linhas a seguir: As chaves das mensagens (ex: ALUNO_INSERIR_OK) devem ser as mesmas em todos os arquivos de propriedades. São elas que identificam unicamente uma mensagem, que ao final do processo tem o seu texto automaticamente traduzido para o idioma preferido do usuário. É possível ainda configurar de modo genérico o idioma, especificando apenas a língua. Por exemplo, ao invés de inglês britânico (en_GB) e inglês americano (en_US), podemos simplesmente definir o idioma inglês (en). Neste caso, o arquivo deverá ser o messages_en.properties. Internamente o Demoiselle Framework usa a classe java.util.Locale presente na API do Java para manipular os textos das mensages durante a internacionalização. A seleção de idioma na internacionalização de uma aplicação consiste na definição de: língua: códigos formados por duas letras em minúsculo listados na ISO-639 (ISO Language Code) - exemplos: pt, en, fr, es, de; país: códigos formados por duas letras em maiúsculo listados na ISO-3166 (ISO Country Code) - exemplos: BR, PT, US, GB.
Exemplos de implementação A fim de auxiliar a manutenção das descrições das mensagens em uma aplicação diversas soluções podem ser escolhidas pelos arquitetos e empregadas pelos desenvolvedores. Nesta seção serão mostradas duas sugestões para essa questão, usando interfaces e enumeradores. A vantagem em se utilizar ambas as soluções é que diversas mensagens relacionadas podem ser agrupadas, reduzindo assim a quantidade de arquivos a serem mantidos e centralizando a configuração das mensagens. Sendo assim, uma possível solução é criar uma interface destinada a armazenar todos os modelos de mensagens de uma determinada severidade a ser usada na aplicação. Nesta interface são declarados campos do tipo Message com o modificador final (implicitamente também será public e static). Veja o exemplo de código para a interface InfoMessages: No exemplo em questão, o texto das mensagens será recuperado do arquivo de recursos messages.properties presente no diretório /src/main/resources/. Eis o conteúdo desse arquivo: Recomenda-se criar uma interface para cada tipo de severidade (ex: InfoMessages, WarningMessages, ErrorMessages e FatalMessages), agrupando nestas as mensagens usadas exclusivamente para o mesmo fim. Já a segunda abordagem consiste no uso de enumerações para centralizar os modelos de mensagem. É utilizado o mesmo arquivo de recursos messages.properties ilustrado na abordagem anterior. Porém, neste caso cada enumerador da enumeração corresponderá a uma chave no arquivo de propriedades. A fim de adicionar mensagens ao contexto, eis um exemplo de código que faz uso do artefato InfoMessages, que funciona não importa qual abordagem escolhida (interface ou enumeração): No ponto contendo @Inject será injetado via CDI o contexto de mensagens presente na aplicação, ou seja, uma instância da interface MessageContext que poderá ser utilizada em qualquer método nessa classe. Aqui os métodos insert(), update() e delete() na classe BookmarkBC manipulam o contexto de mensagens em cada invocação destes. O método add() de MessageContext faz com que a mensagem passada como parâmetro seja adicionada ao contexto, que ao final é exibida para o usuário na camada de apresentação.