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.