Download Java Magazine - Edição 039.pdf...
AJAX Avançado com GWT Criação de componentes e acesso a dados
l a i c e p s E
A Revista da Comunidade Java Brasileira
Edição 39 - Ano V - R$ 9,90
JGoodies Binding Ligue componentes gráficos a objetos de negócio mantendo a abstração OO
JavaMail Aplicado Criando uma mala direta em HTML com anexos, fazendo personalização através de expressões regulares
RIA com OpenLaszlo Crie Rich Internet Applications e traga interatividade de desktop às suas aplicações web, com Flash, XML e Java J ava
Dicas na We Webb Recapitulando técnicas essenciais na programação com servlets e JSPs
SWT com Visual XP Torne suas aplicações gráficas mais integradas ao sistema operacional
JAVA EE 5 JA jm39.indb 1
Explorando a Plataforma
Javaa Persistence Jav Persistence API
Conheça a fundo todas as novidades, de motivações a efeitos práticos – com um tutorial no GlassFish
A nova API de persistência do EJB 3.0 que muda as bases do mapeamento objeto-relacional em Java 15/8/2006 18:11:24
jm39.indb 2
15/8/2006 18:11:30
jm39.indb 2
15/8/2006 18:11:30
Conteúdo CAFEÍNA News & Bits
LEONARDO GALVÃO Novos produtos wireles s, TestNG TestNG 5, repositório open source do Google, Subversive 1.0
06
SWT com visual nativo do XP
FERNANDO LOZANO Faça o Eclipse e outras aplicações SWT assumirem o visual do Windows XP
s e õ ç e S
RECAPITULANDO: DICAS NA WEB FELIPE LEME Obtendo as versões suportadas de JSP e servlets, usando forward e redirect, manipulando JavaBeans em taglibs e alterando o nome de arquivos para download
10
JAVA EE 5 OSVALDO PINALI DOEDERLEIN Explorando a nova versão do Java corporativo: anotações, injeção de dependências, novas JSRs e um exemplo prático usando o servidor open source GlassFish
a p a C
16
PERSISTÊNCIA NO JAV AVA A EE 5 ANDRÉ DANTAS ROCHA E SÉRGIO OLIVEIRA KUBOTA Aprenda a utilizar a nova Java Persistence API, de conceitos de mapeamento objeto-relacional, a um exemplo completo usando Toplink Toplink Essentials e MySQL
28
AJAX AVANÇADO COM GWT ARI DIAS NETO Explore a API do Google Web Toolkit e crie novos componentes, desenvolvendo aplicações altamente interativas com AJAX e Java
38
UMA MALA DIRETA COM JAVAMAIL YARA SENGER E ANA ABRANTES Utilize a API JavaMail para enviar e-mails em HTML com anexos para múltiplos destinatários, e personalize mensagens usando expressões regulares
b e W p o t k s e D jm39.indb 3
62
RIA COM OPEN LASZLO ANDRÉ LUÍS MONTEIRO Turbine suas interfaces gráficas e maximize a interatividade com o usuário utilizando uma plataforma open source baseada em Flash, XML e Java
70
INTERFACES GRÁFICAS COM QUALIDADE – PARTE 2 HUGO VIDAL TEIXEIRA Descubra como aplicar o pattern Presentation Model e a API Binding do JGoodies para construir GUIs com produtividade, favorecendo os testes unitários
50
15/8/2006 18:12:15
Esp
Ano V • Edição 39 • 2006 • ISSN 1676-8361
Direção Diretor Editorial Leonardo Galvão Diretor de Marketing Gladstone Matos Diretor Comercial Casseano Filho
Edição Publisher e Editor-Chefe
Leonardo Galvão (
[email protected] ) Editores-Adjuntos
Fernando Lozano (
[email protected] ) Osvaldo Doederlein (
[email protected] ) Colaboraram nesta edição
Ana Abrantes, André Dantas Rocha, André Luís Monteiro, Ari Dias Neto, Fernando Lozano, Hugo Vidal, Leonardo Galvão, Osvaldo Doederlein, Sérgio Kubota, Yara Senger
Arte Diretor de Arte Tarcísio Bannwart (
[email protected] ) Diagramação Jaime Peters Junior, Lais Pancote e Tersis Zonato Ilustrações Felipe Machado e Francisco Peixoto Capa Felipe Machado
Produção Gerência de Marketing Kaline Dolabella Distribuição Fernando Chinaglia Distribuidora S.A. Rua Teodoro da Silva, 907, Grajaú - RJ CEP 20563-900, (21) 3879-7766 - (21) 2577-6362
Atendimento ao leitor A DevMedia possui uma Central de Atendimento on-line, onde você pode tirar suas dúvidas sobre serviços, enviar críticas e sugestões e falar com um de nossos atendentes.Através da nossa central t ambém é possível alterar dados cadastrais, consultar o status de assinaturas e conferir a data de envio de suas revistas.Acesse www.devmedia.com.br/central, ou se preferir entre em contato conosco através do telefone 21 2283-9012.
Edições anteriores Adquira as edições anteriores da revista Java Magazine ou de qualquer outra publicação do Grupo DevMedia de forma prática e segura, em www.devmedia.com.br/anteriores.
Publicidade
[email protected] , 21 2213-0940
Anúncios – Anunciando nas publicações e nos si tes do Grupo DevMedia,
você divulga sua marca ou produto para mais de 100 mil desenvolvedores de todo o Brasil, em mais de 200 cidades.Solicite nossos Media Kits, com detalhes sobre preços e formatos de anúncios. Reprints Editoriais – Se foi publicado na Java Magazine um artigo que possa alavancar as suas vendas, multiplique essa oportunidade! Solicite a reimpressão da matéria junto com a capa da edição em que s aiu, e distribua esse reprint personalizado entre seus clientes. Encarte de CDs – Faça como nossos maiores anunciantes.Encarte um CD com uma amostra de seus produtos na Java Magazine e atinja um público segmentado e formador de opinião. Realização
Apoio
C
om o Java Enterprise Edition 5.0, entramos em numa nova e importante geração do desenvolvimento corporativo. O foco na facilidade de uso, a absorção de técnicas utilizadas em produtos open source, uma participação sem precedentes da comunidade e uma implementação de referência que já rivaliza com os melhores servidores de aplicações... Talvez pela primeira vez na história do Java corporativo vemos lançada uma versão que é sucesso entre as mais diversas castas de desenvolvedores Java. O Java EE 5.0 faz grandes correções de rumo, criando novas tecnologias que tornam obsoletas áreas problemáticas da especificação como os tão polêmicos Entity Beans, incorpora mais do que nunca os web services, e já olha adiante integrando-se com várias novidades que virão no Java SE 6.0 (Mustang). Quem diria que uma especificação do JCP, envolvendo gigantes do mercado com visões e perfis tão distintos como Sun, IBM, Oracle, BEA, Motorola, Cisco e SAP, chegaria a um consenso de tal solidez, criando uma especificação tão eficaz e alin hada às necessidades do mercado? Mas foi o que aconteceu, em mais uma demonstração da efetividade do Java Community Process e da força da comunidade Java. Nesta edição, o Java EE 5 é tratado em dois artigos aprofundados. Em uma visão geral da plataforma, você conhece todas as grandes novidades, com diversos exemplos funcionais, e ainda um tutorial mostrando como criar uma aplicação usando uma seleção dos novos recursos da especificação, além do GlassFish, o servidor que é a base da implementação de referência do novo Java EE. Em um segundo artigo, é vista em detalhes a API que tem chamado mais atenção no Java EE 5, a Java Persistence API (JPA). Parte da especificação do EJB 3.0, mas planejada como especificação independente na sua próxima versão, a JPA se inspira fortemente em ferramentas de mapeamento objeto-relacional como Hibernate e iBatis, padronizando uma área fundamental do desenvolvimento corporativo. Você vai conhecer os fundamentos da JPA, e criar um exemplo completo usando vários tipos de associações e herança. Ainda nesta edição, confira um artigo sobre desenvolvimento AJAX que explora a API do Google Web Toolkit para criar componentes altamente interativos e integrálos em uma aplicação sofisticada com acesso a dados. Veja também como melhorar suas aplicações desktop usando o pattern Presentation Model e a API JGoodies Binding, que permite vincular os seus modelos OO à interface gráfica de forma eficaz e organizada. Em outra matéria passo a passo, é mostrado como criar uma mala direta completa usando a API JavaMail e expressões regulares. E mais um artigo voltado à internet mostra como usar o OpenLaszlo para criar Rich Internet Applications, baseadas em Flash, XML e Java. Finalmente, nesta edição retomamos a seção “Recapitulando”, dessa vez trazendo dicas sobre o desenvolvimento com JSP e servlets; também incluímos um artigo especial no Cafeína mostrando como fazer sua aplicação SWT se integrar perfeitamente ao visual do Windows XP. Boa leitura!
Parceiros
Leonardo Galvão
jm39.indb 4
15/8/2006 18:12:21
aço do Leitor Diálogos modais para a área de trabalho
E
stou desenvolvendo uma aplicação Swing usando NetBeans 5 e JDK 1.5, a qual solicita login e senha. Ela deveria bloquear todas as janelas da área de trabalho, só liberando-as após a autenticação. Mas não consigo descobrir como configurar um diálogo modal para a área de trabalho como um todo, como seria possível usando Delphi ou Visual Basic.
diálogos modais apenas com respeito à própria aplicação que o criou. No Java 6 (Mustang), haverá um controle mais fino sobre diálogos modais, mas ainda assim o máximo será restringir todas as aplicações Java na área de trabalho, não afetando as aplicações nativas. Veja mais sobre as mudanças em diálogos modais no Java 6 em
Eloir Cortes
java.s un.com /develo per/te chnical Artic les/J2 SE/ Desktop/mustang/modality .
Infelizmente nem o Swing nem o AWT fornecem o recurso conhecido como “system modal” no Windows, que permite bloquear todas as aplicações na área de trabalho até que o diálogo seja fechado. Até o Java SE 5.0, é possível criar
É possível, pelo menos no Windows, configurar diálogos Swing como “system modal” mas isto envolve o uso da tecnologia JNI (Java Native Interface) para chamar funções da API nativa do Windows (Win32 API). Lembrando que uma aplicação usando este recurso, é claro, não seria
SOA
importantes novidades do Java EE 5: a Java Persistence API.
Sou assinante da Java Magazine e gostaria primeiramente de parabenizá-los pela excelente qualidade do material apresentado. Também gostaria de saber de vocês se há possibilidade de publicar na revista um artigo falando sobre SOA e web services, visto que o assunto é cada vez mais comentado na comunidade Java.
Michel Pires da Silva Web Services e SOA (Service-Oriented Architecture) foi um dos assuntos de capa da Java Magazine 34. O artigo de Osvaldo Doederlein cobriu desde as tecnologias precursoras dos web services como RPC, até o atual enfoque na criação de arquiteturas orientadas a serviços.
Java EE 5 Gostaria de ver as novidades do Java EE 5 nas próximas edições. Afinal de contas, a especificação já foi aprovada , e a Java Magazine sempre nos apresenta as novidades em primeira mão.
Michel Zanini Esta edição atende ao seu pedido! Além de um artigo fornecendo uma visão geral da nova plataforma destacando as prin cipais mudanças, você verá uma matéria sobre uma das mais
Ferramentas Parabenizo a Java Magazine pelas excelentes edições. Sou iniciante em Java e uma das grandes dificuldades para um iniciante é, além de aprender a linguagem, saber utilizar as ferramentas existentes. E a Java Magazine sabe nos mostrar como utilizar essas ferramentas de forma muito didática, o que facilita bastante a vida dos iniciantes.
Rubens Renato Lima
Autenticação integrada no Java EE Tenho várias aplicações Java EE e preciso que elas trabalhem com um único login. Ou seja, quando o usuário efetuar o login em uma das aplicações, será necessário que todas as outras trabalhem com essa autenticação. Existe alguma forma de compartilhar as informações de autenticação entre as aplicações?
Everton Trindade Os dois artigos “Segurança no J2EE”, publicados nas Edições 22 e 23 respondem em detalhes à sua pergunta. De forma resumida, se sua aplicação utiliza os recursos de autenticação e controle de acesso baseados em roles definidos pelo Java EE, uma
portável. O site para desenvolvedores da Borland tem um bom artigo explicando como fazer isso: community.borland.com/article/0,1410,20679,00. html .
Por outro lado, aplicações SWT têm o recurso de “system modal” disponível: basta configurar o estilo SWT.SYSTEM_MODAL
na criação de um Dialog ou de um Shell. simples configuração do container web (no caso do Tomcat,a ativação da válvula “SingleSignOn”) faz com que o login em uma aplicação seja automaticamente repassado para todas as outras aplicações dentro do mesmo servidor de aplicações, ou do mesmo cluster de servidores de aplicações. As dificuldades surgem quando o desenvolvedor, em vez de adaptar seus programas aos recursos de segurança previstos pelo Java EE, cria sua própria solução “personalizada” de autenticação. Mas não há motivos para se fugir aos recursos padrões do Java EE, pois qualquer servidor de aplicações com um mínimo de qualidade é capaz de usar senhas armazenadas em vários locais, como bancos de dados, diretórios LDAP ou mesmo as senhas de rede em Windows, Linux e Netware.
Participe! Envie sua dúvida, comentário, correção ou sugestão, com nome completo, cidade e estado, para:
[email protected]
Cartas publicadas podem ser editadas por motivos de clareza ou extensão.
Edição 39 • Java Magazine 5 jm39.indb 5
15/8/2006 18:12:31
Cafeín News & Bits
Nokia Carbide.j 1.5 O
Carbide.j é um conjunto de ferramentas integradas voltado à criação, depuração e testes de aplicações Java para dispositivos Nokia, que pode ser executado independentemente ou de forma integrada a um IDE (são suportados o Eclipse 3.0 e 3.1, JBuilder Mobile Edition, NetBeans 4.x e 5.0, e IBM WebSphere Studio Device Developer 5.6 e 5.7). Com o Carbide.j, você pode criar aplicações baseadas nos perfis MIDP e Personal Profile, assinar pacotes de aplicações MIDP, configurar e gerenciar SDKs da
Nokia (há um SDK da empresa para cada série de dispositivos), e realizar a instalação/deployment usando conexões RS232 (serial), IrDA (infra-vermelho) e Bluetooth, assim como FTP. Há ainda um designer de interfaces gráficas com suporte a dez tamanhos de telas, entre outras funcionalidades. A novidade mais importante da versão 1.5 é o suporte ao chamado “on-device debugging”, que permite realizar a depuração de aplicações em execução no próprio dispositivo. Há também suporte a novos dispositivos e várias melhorias, especialmente na integração com o Eclipse e no designer de interfaces. forum.nokia. com/main/resources/tools_and_sdks/carbide .
SNAP Mobile SDK 1.2 A
plataforma Scalable Network Application Package (SNAP) da Nokia suporta a criação de jogos móveis conectados e comunidades de jogadores, através de uma API cliente e uma infra-estrutura de serviços. Para comunidades, são oferecidos recursos para conversas on-line e criação de listas de amigos, indicadores de presença (online, offline, jogando) e rankings, para estimular a competição. Há funcionalidades para jogos “ponto a ponto”, nos quais dois adversários jogam um contra o outro do começo ao fim, e também a possibilidade de pesquisar e escolher adversários e montar salas virtuais. Outro ponto forte são os recursos para a criação de sites com notícias e eventos, quadros de mensagens moderados e páginas de apresentação dos jogos. A plataforma SNAP roda sobre o MIDP 2.0 e é baseada na plataforma Arena criada para o N-Gage (os primeiros celulares da Nokia com suporte especial a jogos), que já conta com mais de 500 mil usuários cadastrados. Um atestado da importância do SNAP é a sua inclusão no novo Wireless Toolkit 2.5 da Sun. snapmobile.nokia.com.
6 Java Magazine • Edição 39 jm39.indb 6
15/8/2006 18:12:40
a LEONARDO GALVÃO
TestNG 5.0 O framework de testes TestNG é o principal concorrente do JUnit, e se posiciona como uma alternativa mais capaz e mais moderna (NG é uma abreviação de “New Generation”). Primeiro framework de testes a suportar anotações (mesmo antes do Java 5), o TestNG inclui suporte a métodos dependentes e parâmetros, distribuição de testes e testes orientados a dados. O TestNG segue um modelo de execução que dispensa o uso do tradicional TestSuite e traz o BeanShell embutido. Há também plug-ins para várias ferramentas, entre elas o Eclipse e o Apache Maven. A versão 5.0 tem melhorias importantes nos relatórios de testes gerados, com uma estrutura mais organizada e mudanças visuais. Outro destaque é o trabalho com stack traces, que agora podem ser mostrados interativamente nos relatórios HTML, sendo possível também filtrá-los. testng.org
Subversive 1.0.0 Um dos mais populares plug-ins Eclipse para integração com o sistema de controle de versões Subversion, o Subversive chegou à versão 1.0.0. O Subversion é considerado em muitos pontos melhor e mais moderno que o CVS e hoje é adotado como sistema de controle de versões oficial da Fundação Apache, entre outras organizações. O objetivo do plug-in Subversive é trazer para o Eclipse o mesmo
nível de suporte oferecido no IDE para o CVS. polari on.org .
LimpidLog A nova API de logging open source LimpidLog tem uma proposta radical, que pode simplificar o log de informações sobre classes durante a execução. Em vez de codificar chamadas à API de logging, você registra a classe a ser monitorada e o LimpidLog acompanha e loga informações importantes sobre o que está sendo feito na classe (isso vale para mensagens do tipo TRACE e ERROR; para outros casos, ainda será necessário chamar uma API de logging comum). A API usa a classe java. lang .ins trume nt.In stru menta tion para instrumentar classes e é capaz de logar eventos de chamada/entrada/saída de métodos, atribuição de variáveis e levantamento de exceções. acelet.com/limpidlog.
Jahia Community Edition Mais um produto importante liberado como open source, o Jahia Community Edition torna disponível sob uma licença CDDL modificada, 80% do código do produto. São mais de 2.200 classes e arquivos. O Jahia é uma “plataforma web unificada”, que integra tecnologias como ECM ( Enterprise Content Management) e portlets, bem como um gerenciador de documentos baseado no Apache Slide e um mecanismo de buscas
construído sobre o Apache Lucene. Recursos enterprise não disponíveis na versão Community, como Single Sign-On e suporte a clusters estão acessíveis sob uma licença baseada na idéia de “contribuição ou pagamento”, pela qual empresas ou indivíduos podem escolher pagar royalties pelos recursos adicionais, ou trocá-los por contribuições para o projeto. jahia.o rg.
Google lança repositório open source O Google lançou um novo repositório de projetos open source, baseado no sistema de controle de versões Subversion, e subordinado ao portal “Google Code” (que hospeda pro jetos como a Go ogle Search API). O cadastro de novos projetos é simples e há recursos completos de busca, além de navegação por palavras-chave. Para registrar um projeto, é necessário ter uma conta no Google, o que abre a possibilidade de se usar diversos outros serviços da empresa juntamente com o repositório. Um detalhe interessante é que apenas algumas licenças open source são acei tas. Diz o FAQ do Google, “A comunidade open source está cheia de licenças praticamente iguais. Gostaríamos que projetos fossem padronizados com as licenças mais populares e maduras”. O repositório inclui ainda recursos de issue tracking, em um módulo que foi criado inteiramente pelo Google. code.google.com/hosting.
Edição 39 • Java Magazine 7 jm39.indb 7
15/8/2006 18:12:41
Cafeína • Especial
SWT com visual nativo do XP
FERNANDO LOZANO
M
uito se fala que uma das grandes vantagens do SWT sobre o Swing é a aderência ao visual nativo da plataforma, obtido graças ao uso dos componentes gráficos nativos. Entretanto, desenvolvedores de aplicações SWT e usuários do Eclipse em geral devem ter percebido que, no Windows XP, é assumido o visual nativo do Windows 98 e 2000, em vez do visual mais moderno do Windows XP. Veja na Figura 1, por exemplo, o exemplo EnderecoTerrestre.java publicado originalmente no artigo da Edição 31 sobre desenvolvimento SWT. A Figura 2 ilustra como deveria ser a aparência da aplicação com o visual nativo do XP. A boa notícia é que as duas figuras foram geradas pela mesma aplicação. Não foi necessário modificar uma única linha do código Java, nem recompilar a aplicação contra uma versão diferente do SWT. A solução exige que o arquivo javaw.exe. manifest , apresentado na Listagem 1, seja copiado para a mesma pasta que contém o
executável javaw.exe utilizado para rodar as aplicações SWT em seu computador. Os programas javaw.exe e java.exe são ambos alternativas para a execução de uma máquina virtual Java (JVM). A única diferença entre eles é que o cabeçalho do javaw.exe diz ao Windows que ele é uma aplicação gráfica, e assim sua execução não provoca a abertura de uma janela de console ou prompt do MS-DOS.
manifest à instalação padrão do JRE ou JDK.
Assim sendo, o arquivo deve ser instalado manualmente pelos próprios usuários. Do lado da Microsoft, este arquivo tem sua razão de ser, pois ele permite que aplicações criadas para versões anteriores do Windows executem sem modificações no XP. Ao mesmo tempo, ele torna fácil para os desenvolvedores adequarem suas aplicações ao novo visual, sem esforço adicional de programação. Em versões anteriores do Windows, a Microsoft simplesmente forçava todas as aplicações a assumirem o novo visual (por exemplo, do Windows 3.1 para o Windows 95). Entretanto, isto criava problemas quando a aplicação utilizava componentes customizados que não se adequavam bem ao novo visual. Por isso, no XP, qualquer aplicação pode ser configurada para o visual “antigo” ou para o novo.
Uma vez criado (ou copiado) o arquivo, a linha de comando a seguir executa o exemplo conforme visto na Figura 2 : javaw EnderecoTerrestre
A criação do arquivo irá afetar todas as apli cações SWT executadas neste computador, inclusive o próprio Eclipse. As Figuras 3 e 4 mostram o próprio Eclipse antes e depois da criação do javaw.exe.manifest . Fica a pergunta: por que essa “mágica” não é parte da instalação padrão do Eclipse para Windows? Afinal o arquivo javaw.exe. manifest seria simplesmente ignorado em versões mais antigas do Windows, e assim não causaria prejuízo algum. A impossibilidade de o arquivo ser instalado pelo próprio Eclipse decorre da licença do Java. A redistribuição do Java da Sun deve ser feita de modo inalterado, assim não é possível para o Eclipse (ou para qualquer outro fornecedor que não tenha adquirido uma licença especial junto à Sun) acrescentar o javaw.exe.
Figura 1. Exemplo EnderecoTerrestre.java como exibido na
instalação padrão do SWT no Windows XP, assumindo a aparência padrão do Windows 98 e 2000.
Figura 2. Exemplo EnderecoTerrestre.java com a aparência
nativa do Windows XP.
8 Java Magazine • Edição 39 jm39.indb 8
15/8/2006 18:12:48
Listagem 1. rqu vo javaw.exe.manifest que
l
xml vers on= . encod ng assem ly xmlns= urn:sc emas assem lyIdent ty vers on= processorArchitecture=”X name= . avaw type=”win32”/> Standard Widg
Figura 3. Eclipse com sua aparência padrão no Windows XP
Figura 4. Eclipse depois de criado o arquivo javaw.exe.manifest
Edição 39 • Java Magazine 9 jm39.indb 9
15/8/2006 18:12:56
Recapi Dicas na
Web N
este artigo são apresentadas dicas úteis sobre JSP e servlets, soluções para problemas recorrentes no desenvolvimento web, e esclarecimentos sobre alguns procedimentos comuns.
Obtendo as versões de JSP e Servlets
Nesta edição, recapitulamos um dos assuntos mais populares na Java Magazine: o desenvolvimento com JSP e servlets. Foram selecionadas quatro dicas, ainda totalmente atuais, do artigo “Dicas na Web” publicado na Edição 12, que resolvem dúvidas comuns e exploram detalhes importantes da programação web com Java.
As tecnologias JSP e servlets evoluíram muito desde suas primeiras versões. Por exemplo, a API de servlets 2.3 introduziu o conceito de filtros (interface java x.sevle t.Fi lter ), e com o JSP 2.0, veio o suporte a uso da Expression Language dentro das páginas.
Para garantir a compatibilidade de suas aplicações web, portanto, é sempre importante saber as versões das APIs suportadas nos containers web utilizados. Os métodos getMajorVersion() e getMinorVersion() da classe javax.servlet.ServletContext permitem obter a versão da API de servlets. E sta classe fornece ainda o método getServerInfo(), que descreve o nome do servidor e sua versão. Já para a versão da especificação JSP, é preciso obter um objeto javax.servlet.jsp.JspEngine e chamar seu método getSpecificationVersion() . A Listagem 1 contém uma página JSP que pode ser usada para obter essas informações.
Definindo o nome do arquivo em downloads Um uso comum para servlets (e mesmo para páginas JSP) é a geração dinâmica de arquivos, tais como planilhas ou documentos PDF. Nessas situações, ao enviar o arquivo gerado, é preciso que o servlet especifique o tipo do conteúdo através do método response.setContentType(String tipo) . O tipo segue no cabeçalho da resposta HTTP retornada pelo servlet e, de acordo com ele, o navegador poderá decidir o que fazer com o arquivo. Normalmente o arquivo é exibido através de um plug-in, ou é oferecida a opção de salvá-lo no disco, mas nos casos onde a in-
Listagem 1. versoes.jsp , obtendo a versão das APIs web suportadas Servidor: Servlet: JSP:
10 Java Magazine • Edição 39 jm39.indb 10
15/8/2006 18:13:03
tulando tenção é que o usuário faça o download, esse procedimento traz alguns problemas: Não é garantido que o usuário tenha a opção de salvar o arquivo em vez de o navegador abrir automaticamente um plug-in. Mesmo que o navegador web ofereça a opção de salvamento, o nome sugerido será o do servlet ou o da página JSP (por exemplo, “contador.jsp”), sendo que o ideal seria um nome mais amigável, ou ao menos com a extensão correta (como "contador-10.txt"). •
•
Para resolver esses problemas, a solução é definir a propriedade content-disposition do cabeçalho da resposta HTTP, com o valor attachment;filename= nome. extensão, como no trecho de código a seguir: response.setHeader( “content-disposition”, “attachment;filename =” + nomeArquivo );
A Listagem 2 inclui uma página JSP que imprime um contador, de 1 até 10 (ou até o valor definido no parâmetro limite). Note que o navegador não exibe o conteúdo da página; em vez disso abre a janela com a op ção de salvar o arquivo, com nome sugerido “contador- XX .txt” (sendo XX o limite). A propriedade content-disposition é definida no protocolo HTTP (RFC 2183 1), e não na API de servlets/JSP. Assim o funcionamento dessa dica depende da compatibilidade do navegador com os padrões W3C, e pode não funcionar em navegadores mais antigos (nesses casos, o nome sugerido continuará sendo o nome do JSP). Outra opção é definir no descritor web.xml um mapeamento com o nome de1 RFCs (Requests for Comments) são documentos que definem protocolos, modelos ou especificações para os sistemas da internet. De forma ampla, representam para a internet o que os JSRs (Java Specification Requests) são para Java.
sejado (como “relatorio.pdf” ou “contador. txt”) para o servlet ou página JSP responsável pela geração do arquivo, o que funcionará independente do navegador utilizado. No entanto essa solução não tem a flexibilidade de permitir a definição do nome no momento de execução do ser vlet/JSP.
Forward ou redirect? Uma necessidade muito comum na programação web é passar o fluxo de execução
FELIPE LEME
para outra página. Existem duas formas de s e fazer isso – forward (encadeamento) e redirect (redirecionamento). Embora o resultado final pareça muitas vezes ser o mesmo, o funcionamento é bem diferente sob o ponto de vista do navegador web. Forward A primeira forma usa o método forward() da classe RequestDispatcher, como mostra o código da Listagem 3.
Listagem 2. contador.jsp , exemplo simples para demonstrar mudança de nome do download ”); out.flush(); }
String tipo = request.getParameter(“tipo”); if (tipo != null) { request.setAttribute(“atributo”, “teste”); try { if (tipo.equals(“forward”)) { RequestDispa tcher dispatcher = pageContext.getServletContext(). getRequestDispatcher(“/novaPagina.jsp”); dispatcher.forward(request, response); } if (tipo.equals (“sendRedire ct”)) { response.sendRedirect(“/jm12/novaPagina.jsp”); } } catch(Throwa ble t) { out.println(“Exceção: “ + t.getMessage() + “”); } } %>
12 Java Magazine • Edição 39 jm39.indb 12
15/8/2006 18:13:06
Edição 39 • Java Magazine 13 jm39.indb 13
15/8/2006 18:13:12
Recapitulando · Dicas na Web
Listagem 5. usoCorreto.jsp, uso correto das tags e Exemplo do uso correto das tags: Nome:
Email:
class=”jm.dicas.EntradaDadosBean” scope=”session”> Nome:
Email:
Listagem 7. EntradaDadosBean.java, JavaBean utilizado nas Listagens 5 e 6 package jm.dicas;
Listagem 6. usoIncorreto.jsp, uso incorreto das tags e Exemplo do uso incorreto das tags (valores dos textfields nunca mudam): Server Manager > Add Server e criar um novo registro do tipo Sun Java System Application Server, entrando com seus parâmetros de instalação (diretório, login de administrador etc.). (Este mesmo tipo de servidor funciona com o Java EE SDK 5.0 / SJSAS 9 e com o Glassfish; par a simplificar, nos referiremos ao servidor como apenas “GlassFish”.) Agora vá na janela Runtime do NetBeans e, sob Servers , encontre o servidor GlassFish e comande Start in Debug Mode . Em File | New Project , selecione Enterprise / Enter prise App lication na primeira página, e preencha a segunda conforme a Figura Q1 (modificando é claro os drives e diretórios), selecionando o GlassFish e criando um módulo EJB e um módulo web. O NetBeans criará três projetos, para os módulos EJB, WAR e EAR. No projeto EJB, acione New > Session Bean, e crie um EJB com nome “Alo”, tipo Stateless e interface apenas Remote. O assistente irá criar os fontes da interface AloRemote e da implementação AloBean. Após customizarmos estes fontes, eles deverão ficar como nas Listagens Q1 e Q2 . Examinando o projeto, você verá que temos apenas estes dois arquivos .java , e além disso dois descritores – sun-ejb-jar.xml e MANIFEST.MF . Mas ambos estão vazios; foram criados apenas por conveniência. Apague estes dois arquivos do projeto, para comprovar o que dissemos sobre serem opcionais. No projeto do EAR, acione Deploy Project . O NetBeans irá executar a compilação dos módulos dependentes, o empacotamento do EAR, e sua
Figura Q1. Criando o projeto de exemplo.
20 Java Magazine • Edição 39 jm39.indb 20
15/8/2006 18:15:03
instalação no servidor GlassFish que já havíamos deixado executando. Vamos agora à interface web com a JSF. Nas propriedades do projeto WAR, na categoria Frameworks, comande Add / Java Server Faces, aceitando as opções default; isto configura o projeto para suportar a JSF. Além disso, como o projeto WAR precisará utilizar a interface AloRemote do projeto EJB, você deve configurar o classpath do projeto WAR: em Libraries / Add Project , selecione o diretório do projeto EJB. Nossa GUI terá apenas uma página, que invoca o Session Bean e exibe a mensagem retornada. Para fazer esta invocação, vamos usar um managed bean. Na JSF, “managed beans” são classes equivalentes às Forms do Struts (mas com a vantagem de serem POJOs), normalmente contendo dados (modelo) manipulados pelas telas (views). O nosso managed bean só terá uma propriedade “virtual” cujo getter invoca o Session Bean. Crie o managed bean com New > File/Folder > Web > JSF Managed Bean. Modifique o nome da classe para “Alo”, e aceite os defaults para as demais opções. Finalmente, edite o código gerado para ficar como a Listagem Q3. Observe o uso de Injeção de Dependências para conectar com o Session Bean. (O assistente de criação de managed beans é útil principalmente por registrar o bean na configuração da JSF, o facesconfig.xml ). A última etapa é criar a página index.jsp , conforme a Listagem Q4. Páginas JSF são basicamente JSPs que utilizam as taglibs da JSF; neste caso, utilizamos as tags ( ou componentes) view, que deve englobar todo o conteúdo da página gerenciado
pela JSF, e outputText, que exibe dados (equivalente à da JSTL). O argumento da nossa outputText é uma expressão EL que obtém a propriedade alo do managed bean. Para executar o exemplo, faça o deploy do projeto principal (o EAR), e acesse com o browser o endereço: http://localhost:8080/AloEE5-war/ faces/index.jsp Sendo que 8080 é a porta HTTP default do GlassFish, AloEE5-war é o nome do projeto do módulo Web, faces/ é o padrão de URL default para acio nar o controller da JSF (equivale ao “ *.do ” da Struts), e index.jsp é a nossa página. O resultado do teste
desta funcionalidade. Entre o que utilizamos, está a arquitetura de componentes distribuídos do EJB: seria possível fazer o deploy dos módulos EJB e web em servidores separados e a aplicação funcionaria da mesma forma. A Figura Q2 mostra o ambiente de desenvolvimento do NetBeans 5.5, onde podemos ver a edição de JSP/JSF, o runtime e os logs do servidor Glassfish, e a estrutura dos projetos AloEE . Como a especificação e as implementações de Java EE 5 ainda são muito recentes, o suporte de IDEs ainda é preliminar (o NetBeans 5.5, no momento em que escrevo, ainda está em beta). Mas até em função das simplifica ções do Java EE 5, a tendência é dependermos cada vez menos de ferramentas elaboradas.
não será surpreendente – a mensagem “Alô, Java EE 5!” exibida no seu browser. O surpreendente, em comparação com versões anteriores do J2EE, é a simplicidade do código, com menos de 40 linhas (ou menos de 50 se contarmos o faces-config.xml ) . Pode não parecer assim tão pouco para um programa “alô mundo”, mas note que o programa executa num container Java EE 5, que disponibiliza às aplicações um formidável volume de funcionalidades – ainda que isso possa ser difícil de perceber para o iniciante, pois Figura Q2. O NetBeans 5.5, durante o desenvolvimento de uma aplicação Java EE 5. o exemplo usa pouco
Listagem Q1.Interface remota do stateless session bean “Alo”.
import javax.ejb.EJB;
package aloee;
public class Alo { private @EJB AloRemote aloServer;
public String getAlo () { return aloServer.alo (); }
import javax.ejb.Remote; @Remote public interface AloRemote { String alo (); }
}
Listagem Q2.Implementação do stateless session bean “Alo”.
Listagem Q4.Página de teste da JSF.
package aloee;
import javax.ejb.Stateless; @Stateless public class AloBean implements aloee.Alo { public String alo () { return “Alô, Java EE 5!”; } }
Listagem Q3.Managed Bean para a tela de entrada. package aloee;
Alô, Mundo
Edição 39 • Java Magazine 21 jm39.indb 21
15/8/2006 18:15:11
Java EE 5
É para resolver esses problemas que surgiram design patterns como o Service Locator, que já comentamos. Estes patterns reduzem o problema – podemos ter todos os lookups de EJBs numa única classe, e não em dezenas de classes Action – mas não eliminam o problema totalmente. A solução definitiva é proposta pelo sistema de Injeção de Dependências, cuja implementação no Java EE 5 nos permite escrever código como o da Listagem 2, na qual foram destacadas as mudanças em relação à versão anterior. Observe que não precisamos mais nos preocupar com JNDI, lookup, exceções, interfaces Home, conversões de interfaces remotas, ou mesmo otimizações para reduzir os lookups. Só precisamos declarar uma variável (local ou atributo) que seja do tipo da interface de Session Bean desejada, e complementar esta declaração com a anotação @EJB. Esta anotação é simples: basta informar o nome JNDI do bean desejado, e o runtime de ID faz o resto. O código resultante é muito menor. Até mesmo o método doLogin() foi simplificado. O funcionamento básico da ID é simples: quando o artefato que contém a dependência (no caso, a LoginAction3) for criado, o container fará o lookup, a invocação do create() etc. Se o lookup falhar, ocorrerá um erro impedindo a inicialização da Action, e este erro será tratado pelo framework web. Existem variantes neste comportamento, que recuperam muito da flexibilidade que você poderia imaginar ter perdido na transição da Listagem 1 para a Listagem 2 : Ao invés de anotar com @EJB o atributo loginServer, poderíamos utilizar esta anotação num setter ( setLoginServer(LoginServer)), que seria invocado pelo container. Isto nos daria maior grau de controle sobre sua inicialização. A anotação @EJB suporta outras pro•
•
priedades, permitindo escolher a interface local ou remota, ou o nome lógico ( java: comp/env ) ou o nome proprietário do container EJB (mapped name). Há anotações para interagir com o ciclo de vida do objeto sujeito à Injeção de Dependências. Por exemplo, poderíamos criar um método com a anotação @InjectionComplete , que seria então invocado uma única vez, após a inicialização de todos os recursos que usam Injeção de Dependências na sua classe. •
A Injeção de Dependências no Java EE 5
Quem já conhece a Injeção de Dependências em frameworks como Spring, PicoContainer e outros, poderá se perguntar se o suporte a ID do Java EE 5 é melhor ou pior. As anotações de ID utilizadas pelo Java EE 5, especif icadas pela JSR-250 (Anotações Comuns para a Plataforma Java), são também incluídas no Java SE 6. Isso abre a possibilidade de melhorias de APIs fundamentais e até otimizações da JVM, para tirar proveito da Injeção de Dependências, ou torná-la mais eficiente e poderosa. Por outro lado, sistemas de ID mais maduros como o Spring são mais poderosos. Por exemplo, o Spring pode “injetar” ob jetos arbitrários com construção complexa (algo que pode exigir arquivos XML bem grandes...), enquanto o mecanismo do Java EE 5 é mais orientado a objetos publicados na JNDI. Entretanto, a nova especificação torna obsoletos os sistemas anteriores de ID, pelo menos para as funcionalidades em comum. Uma maneira de ver essa diferença é que a JSR-250 oferece um suporte a ID menos radical, mais orientado ao desacoplamento entre código de aplicação e recursos de deployment (como DataSources, filas etc.), e entre módulos numa arquitetura com-
Listagem 2.Injeção de Dependências no Java EE 5. public class LoginAction extends DispatchAction // usando Struts @EJB(beanName=”ejb/session/Login”) protected LoginServer loginServer;
public ActionForward doLogin (final ActionMapping mapping, final ActionForm form, final HttpServletR equest request, final HttpServletR esponse response) { LoginForm f = (LoginForm)fo rm; boolean ok = loginServer.login(f.getUser(), f.getPassword()); // ... etc. } }
ponentizada (neste caso, a ID configura referências para EJBs ou web services). Já framework s esp ecial izados como o Spring incentivam e permitem o uso de ID intenso, de alta granularidade. Neste estilo, praticamente nenhum recurso é inicializado em código, coisa que para alguns desenvolvedores parece ser uma maravilha e para outros um excesso ou abuso da técnica. Assim, podemos arriscar que o Java EE 5 satisfará às necessidades de ID da maioria dos desenvolvedores, mas os adeptos mais radicais da técnica terão que avaliar se preferem continuar usa ndo algo como o Spring4, ou se preferem “moderar” para ter os benefícios de escrever aplicações “puro-Java EE”.
Os componentes do Java EE 5 Nada menos que 21 JSRs separadas, que podemos ver na Tabela 1, contribuem diretamente para o Java EE 5. Novas funcionalidades são poucas, mas importantes: a Java Persistence API (que sucede os EJB/ CMP) é a única API totalmente nova. Além disso, temos a incorporação de APIs anteriormente independentes, como a StAX e a JAXB, e atualizações de muitas outras. A mudança mais sensível ao programador, que é a introdução de anotações para facilitar o uso de muitas APIs, dá uma “cara” de novidade, mas por si só não cria novas funcionalidades – só permite fazer com anotações as mesmas coisas que podíamos e continuamos podendo fazer invocando as APIs tradicionais. Versões do Java EE e SE sempre compartilham algumas novas APIs, e cada versão do Java EE depende da versão anterior mais recente do Java SE, ambas tradicionalmente compartilhando o mesmo número. O Java EE 5 depende do Java SE 5. Mas as versões subseqüentes do Java SE sempre incorporam algumas APIs introduzidas inicialmente no Java EE. O Mustang ( Java SE 6) é um exemplo disso, integrando as 3 Este exemplo é fictício, pois a ID só pode ser usada em classes que são gerenciadas pelo container, o que incluiria uma Servlet, mas não uma Action da Struts (que não faz parte do Java EE). Mas confiamos que versões próximas de frameworks populares como a Struts suportarão integração com o Java EE, dando suporte à ID nos seus artefatos. 4 Note que estes frameworks podem oferecer outras funcionalidades além de ID – por exemplo, no Spring, temos os módulos de AOP e Web Flow.
22 Java Magazine • Edição 39 jm39.indb 22
15/8/2006 18:15:12
APIs de web services e XML agora incorporadas ao Java EE 5. Um fenômeno importante que podemos observar no Java EE 5 é o “refactoring” de algumas especificações. Primeiro foi a EL (Expression Language). Embora o padrão JSP já possuísse uma linguagem de expressões, a JSF criou uma nova e mais poderosa, parecida mas não totalmente compatível com a da JSP. Isso confundiu os desenvolvedores e causou problemas para a mistura de ambas as tecnologias em uma mesma aplicação. A JSF também tinha outras incompatibilidades com a especificação JSP, gerando páginas incorretas para certas combinações de funcionalidades. Estes problemas agora foram resolvidos pelas últimas versões: temos uma nova EL unificada que deverá servir igualmente às necessidades da JSP e da JSF, e talvez de outros frameworks. (Minha opinião é que esta funcionalidade é fundamental, e com as melhorias introduzidas pela nova EL, deveríamos pensar em abandonar alternativas como a OGNL, usada no Tapestry e no WebWork.) O segundo caso é a Java Persistence API (JPA). Além do suporte a POJOs e anotações, é igualmente importante o fato de a nova API de persistência do Java EE 5 ser independente da tecnologia EJB. A JPA é definida num documento isolado e num pacote próprio ( javax.persistence), e pode ser utilizada também na plataforma Java SE (o artigo “Persistência no Java EE” nesta edição mostra um exemplo completo). É um grande contraste com a especificação EJB/CMP, que obriga a aplicação a suportar o peso de um container EJB e dos Entity Beans. EJB 3.0
Se existe um patinho feio na história do J2EE, sem dúvida é a tecnologia EJB, em especial os Entity Beans, acusados de baixa eficiência, alta complexidade e dificuldade de uso. Por outro lado, os Session Beans foram um sucesso. Com eles, obteve-se um excelente equilíbrio entre a sofisticação e o desempenho do CORBA, a simplicidade do RMI, e as vantagens de baixo acoplamento do COM/COM+. Os EJBs também implementam facilidades de controle declarativo de segurança e de transações, alta integração entre todo o “stack” de servidor
JSR
Descrição
Java SE?
JSR-244: Java EE 5
Especificação “guarda-chuva” da plataforma Java EE 5.
-
JSR-250: Anotações Comuns para a Plataforma Java
Anotações compartilhadas pelo Java SE 6 e Java EE 5.
6
JSR-181: Anotações para Web Services
Especifica as anotações utilizadas pela JAX-WS 2.0.
6
JSR-224: JAX-WS 2.0
Novo agrupamento de APIs para web services, sucedendo a antiga JAX-RPC. Suporta padrões de web ser vices como SOAP 1.2 e WSDL 1.2, e binding pela JAXB 2.0.
6
JSR-222: JAXB 2.0
Mapeamento automático entre objetos Java e XML. Agora dirigido por anotações, otimizado com o uso da StA X (JSR-173), e com um runtime portável.
6
JSR-173: StAX
Nova API para parsing de XML no estilo “streaming” (ou “pull”), mais eficiente que SAX (“push”) ou DOM.
6
JSR-220: EJB 3.0
Arquitetura de componentes com funcionalidades “enterprise” (distribuição, segurança, transações etc.). A persistência agora é uma seção em separado, e introduz a Java Persistence API 1.0 (versões futuras terão JSR própria).
Não
JSR-252: Java Server Faces 1.2
Toolkit orientado a componentes para criação de GUIs para web. Não possui muitas novas funcionalidades, mas traz um alinhamento com a JSP, especialmente através da nova linguagem de expressões (EL) unificada.
Não
JSR-245: JSP 2.1
Não possui muitas novas funcionalidades, exceto pela nova Expression Language, que agora é especificada em separado (na mesma JSR, mas versões futuras terão JSR própria).
Não
JSR-52: JSTL 1.2
É o Maintenance Release 2 da mesma JSR da JSTL 1.0 e 1.1, contendo só correções.
Não
JSR-154: Servlet 2.5
É o Maintenance Release 1 da mesma JSR da Servlet 2.4, contendo só correções.
Não
JSR-54: JDBC 3.0
Mesma versão do J2EE 1.4. O plano era incluir no Java EE 5 a JDBC 4.0 (JSR-221), mas devido a atrasos, a nova JDBC foi reagendada para o Java SE 6 e versões futuras do Java EE.
5
JSR-919: JavaMail 1.4
Atualização da API para e-mail, com muitas novas funcionalidades e melhorias de desempenho.
Pequena atualização da API que estende os JavaBeans com JSR-925: JavaBeans Activation suporte a handlers ativados conforme tipos M IME. Usada tradiFramework 1.1 cionalmente pela API JavaMail, e agora também pela JAX-WS.
Não
6
JSR-115: JACC 1.1
Maintenance Release 1 da API para provedores de autorização.
Não
JSR-45: Debugging para Outras Linguagens
Mesma versão do J2EE 1.4. Permite a depuração de códigos não-Java que gerem bytecode, por exemplo JSP e SQLJ.
Não
JSR-914: JMS 1.1
Mesma versão do J2EE 1.4.
Não
JSR-907: JTA 1.1
Maintenance Release 1 da API para transações.
Não
JSR-112: JCA 1.5
Arquitetura de conectores para sistemas de informação (EIS) não-Java. Mesma versão do J2EE 1.4.
Não
JSR-88: Java EE Application Deployment 1.2
Maintenance Release 2 do padrão de APIs e descritores que permitem o deployment portável de aplicações Enterprise.
Não
JSR-77: Java EE Management 1.1 Maintenance Release 1 da API para gerenciamento de servidores.
Não
Tabela 1. JSRs componentes do Java EE 5.
Edição 39 • Java Magazine 23 jm39.indb 23
15/8/2006 18:15:12
Java EE 5
de aplicações (ex.: uma mensagem SOAP chega num Message-Driven Bean, onde é automaticamente mapeada, verificações de segurança são feitas, uma transação XA é criada etc.). Este artigo não teria espaço para tratar igualmente todas as funcionalidades do Java EE 5, mas basta dizer aqui que outros recursos do EJB também são “envenenados” por Injeção de Dependências e por anotações como @SecurityRoles, @Interceptor , @MessageDriven etc. A Java Persistence API (JPA)
Qual é a nova prática recomendada para aplicações Java EE que acessam bancos de dados? A mesma que muita gente, fugindo dos Entity Beans, já vinha praticando há anos: usar só Session Beans (para a comunicação remota, controle de transações, segurança etc.), criar uma camada de POJOs para as entidades, e persisti-los com uma ferramenta como o Hiber nate. A diferença é que agora você pode substituir a API do Hibernate pela JPA, que funciona da mesma maneira, mas é um padrão do Java EE. E pode esquecer dos DAOs, pois a persistência direta de POJOs dispensa códigos de conversão de/para objetos exigidos pela persistência, e o código de persistência que você terá que escrever é tão pouco que não justifica encapsulá-los numa camada de DAOs. A Listagem 3 apresenta um exemplo completo de EJB 3.0 e da Persistence API. O código é simples, e além disso familiar para quem já usou o Hibernate. A classe EntityManager faz as vezes da classe Session do Hibernate, sendo até mais fácil de usar – não precisamos implementar uma Factory para ler a configuração, instanciar a Session, garantir que cada thread use sempre a mesma Session etc. São as vantagens da integração com o container Java EE, e é claro, da Injeção de Dependências. Note que o método EntityManager.find() é genérico, retornando o objeto já com o tipo correto, o que dispensa o typecast que precisaríamos com o Session.get() do Hibernate. O código da Listagem 3 só não constitui uma aplicação completa porque também precisamos de um descritor de “unidade de persistência” ( persistence.xml). Bastaria então reunir estas classes e o descritor num arquivo .ear, e instalá-lo no container.
Aproveite para observar, neste exemplo concreto, outras simplificações do EJB 3.0 que já mencionamos: nada de interfaces Home, nem exceções RemoteException (mesmo ao usar a interface remota), nem herança de interfaces do framework, e como veremos, um empacotamento mais simples. As especificações CMP e CMR são agora desa provadas (deprecated); não haverá mais evolução destas tecnologias, e implementações futuras (talvez a partir do Java EE 6) não terão que suportá-las para obter certificação. Note que o pacote javax.ejb define anotações para Session Beans ( @Stateful e @Stateless ) e MDBs ( @MessageDriven ), mas nenhuma anotação para Entity Beans. A minha interpretação é que além dos CMPs, os próprios Entity Beans estão “marcados para morrer” num release futuro do Java EE. Alguns podem ficar confusos, pois a Persistence API possui uma anotação @Entity que denota entidades persistentes; mas esta anotação não tem qualquer relação com Entity Beans. Resumindo, se você está na fase de prospecção
de um novo projeto que precisa de persistência objeto-relacional, recomendo pensar bem antes de decidir usar os já obsoletos Entity Beans, seja com BMP ou com CMP.
Qual é a relevância da JPA para os adeptos de outras soluções (não-EJB) de persistência? Nenhum sistema O/R será ideal para todas as aplicações e a concorrência deve continuar existindo, mas creio que a JPA tende a se impor como um forte padrão, ao unificar o status de API oficial do Java EE 5 com qualidades de facilidade de uso e compatibilidade com o Java SE (antes exclusividades do JDO, Hibernate e outras soluções). Até mesmo a API do popular Hibernate deve perder terreno; o projeto já oferece uma implementação (hoje em beta) da Persistence API, o Hibernate Entity Manager (HEM). O HEM é uma “casca” que implementa a nova API padronizada pela JSR-220, mas que possui no núcleo o runtime tradicional do Hibernate 5. Pode
Listagem 3.Uma aplicação EJB 3.0 completa, com persistência e Session Bean. // Entidade persistente simples import import import import import
java.io.Serializable; javax.persistence.Entity; javax.persistence.GeneratedValue; javax.persistence.GenerationType; javax.persistence.Id;
@Entity public class Usuario implements Serializable {
private Long id; private String nome; private String senha; @Id @GeneratedValue(strategy=GenerationType.AUTO)
public public public public public public }
Long getId () void setId (Long id) String getNome () void setNome (String nome) String getSenha() void setSenha (String senha)
{ { { { { {
return id; } this.id = id; } return nome; } this.nome = nome; } return senha; } this.senha = senha; }
// Interface remota do Session Bean import javax.ejb.Remote; @Remote public interface LoginServer {
Usuario find (Long id); } // Session Bean que utiliza a entidade persistente import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Stateless public class LoginServerBean implements LoginServer { @PersistenceContext EntityManager em;
public Usuario find (Long id) { return em.find(Usuario.class, id); } }
24 Java Magazine • Edição 39 jm39.indb 24
15/8/2006 18:15:14
Edição 39 • Java Magazine 25 jm39.indb 25
15/8/2006 18:15:18
Java EE 5
ser usado em qualquer servidor Java EE 5 que suporte EJB 3.0 6 e até sem servidor algum, em aplicações Java SE 5. Empacotamento
Uma das melhorias de facilidade de uso mais discretas do Java EE 5, mas nem por isso pouco importante, está nos seus padrões de pacotes para deployment, os arquivos EAR, WAR e outros, que tradicionalmente exigiam uma estrutura bastante complexa. A estrutura básica continua existindo. Por exemplo, uma “enterprise application” continua sendo empacotada num arquivo EAR, dentro do qual pode-se ter arquivos JAR com os EJBs, um folder META-INF com descritores e assim por diante. As melhorias são duas. Primeiro, os descritores tendem a ser bem menores (e até opcionais), graças ao novo modelo de anotações + POJOs + defaults. Segundo, não é mais preciso declarar obviedades ou informar a mesma coisa duas vezes – o Java EE aprendeu a seguir o princípio DRY (don’t repeat yourself ). Um bom exemplo disso é a configuração de JARs utilitários, que são colocados na pasta /lib de um EAR, mas antigamente isso não era suficiente: era preciso declarar todos os JARs no META-INF/MANIFEST. MF, na diretiva Classpath. Agora nada disso é necessário – o container irá detectar todos os arquivos lib/*.jar e incluí-los no classpath do módulo EAR. Aplicações web (WAR) que usem somente JSP e tecnologias relacionadas como JSTL, mas não servlets, não precisam de nenhum descritor (WEB-INF/web.xml). O mesmo vale para WARs expondo web services (não precisam mais de um webservices.xml , nem de arquivos da JAX-RPC). Além disso, os EARs não precisam mais do application.xml. Os application clients também dispensam o application-client.xml; basta ter no META-INF/MANIFEST.MF o Main-Class apontando para a classe principal da aplicação. Finalmente, módulos EJB também não precisam obrigatoriamente do ejb-jar.xml, bastando que o arquivo JAR do módulo possua anotações como @Stateless. Tudo isso não quer dizer que os descritores tenham desaparecido completamente. Continuam sendo suportados, e às vezes podem ser necessários. A diferença é que agora, só é preciso criar descritores quando são realmente necessários.
JavaServer Faces
A inclusão da JSF 1.2 é a única grande novidade de funcionalidade do Java EE 5 7. Este anúncio de “grande novidade” pode parecer uma surpresa para os já adeptos da JSF, comumente utilizada em servidores J2EE 1.4. Mas o fato é que até então, nenhuma versão da JSF fez parte, oficialmente, da plataforma J2EE (a JSF 1.0, JSR-127 (março de 2004) é posterior à J2EE 1.4 (novembro de 2003)). Mas isto talvez tenha sido bom, pois a JSF é uma tecnologia complexa que teve um período relativamente longo de amadurecimento – de reclamações sobre o desempenho das primeiras implementações, a trapalhadas como problemas de compati bilidade com as especificações JSP e EL, que só recentemente foram resolvidos. Assim, pode ser uma boa coisa que somente agora, já na sua terceira revisão e com muitas dessas “dores de crescimento” superadas, a JSF tenha sido integrada ao complexo conjunto de especificações e tecnologias que forma a plataforma Java EE. A JSF é um framework orientado a componentes para geração de GUIs web. Isto se insere melhor na arquitetura geral do Java EE, que já possui modelos de componentes para regras de negócio, serviços, integração com recursos e sistemas externos etc. Faltava suportar o mesmo paradigma no lado do cliente, até então servido apenas por opções “força-bruta” (Servlets) ou de templates (JSP – mesmo com extensões como EL, JSTL, Struts etc.). Por “orientado a componentes” queremos dizer que a JSF permite construir GUIs através da composição de objetos e (em se tratando de GUI) do tratamento de eventos gerados por estes objetos. Um benefício desta arquitetura é a separação entre o comportamento da GUI e a sua apresentação. Outro é a facilidade para a construção de ferramentas puramente visuais. Web services e XML: JAXB 2.0, JAX-WS 2.0, StAX
O Java EE 5 incorpora uma segunda geração de APIs para web services, integradas inicialmente ao J2EE 1.4. Não há novidades revolucionárias, mas temos muitas atualizações no suporte às últimas especificações do W3C, WS-I e OASIS. Melhorias de desempenho críticas, como o suporte a StAX e Fast Infoset (codificação binária de XML); aperfeiçoamentos de arquitetura, como o
runtime portável da JAXB 2.0; e é claro, facilidade de programação com anotações e suporte a POJOs. Quanto aos web services, seu principal atrativo sobre soluções mais maduras e eficientes – segundo as más línguas, qualquer outra coisa – sempre foi a interoperabilidade. Um importante anúncio no JavaOne 2006 foi o Projeto Tango 8, um esforço conjunto da Sun e da Microsoft para alinhar as implementações de web services – de um lado, as APIs do Java Web Services Developer Pack (JWSDP) e do Java EE 5; do outro, o Windows Communication Framework (parte do .NET 3.0). O projeto Tango foi focado não só na interoperabilidade, mas também em eficiência, facilidade de uso, segurança, suporte a transações e outras características. Pode parecer estranho que uma tecnologia criada sob medida para a interopera bilidade, baixo acoplamento etc., precise de esforços contínuos para funcionar corretamente entre as duas principais plataformas de mercado que a implementam. Mas o problema é que as especificações de web services continuam se multiplicando e evoluindo a uma velocidade tão grande que as implementações estão sempre correndo atrás, e tendo que corrigir dificuldades causadas por bugs de implementação ou por especificação insuficiente. Para mais sobre o assunto, veja meu artigo “Web services e SOA”, na Edição 34.
Conclusões Neste artigo demos as primeiras pinceladas no Java EE 5, a plataforma que todos usaremos em algum ponto para aplicações 5 A API proprietária do Hibernate possui algumas funcionalidades que ainda não existem na JPA (como as queries “Criteria”), mas isso só afeta usuários mais avançados. A maioria pode migrar facilmente para a JPA, ganhando com isso as vantagens de padrões do Java EE, como implementações concorrentes, maior suporte de IDEs etc. 6 É o caso do JBoss 4.0, cujos últimos releases (compatíveis com o J2EE 1.4) suportam também o EJB 3.0, embora o JBoss ainda não possua (no momento em que escrevo) uma versão que suporte a plataforma Java EE como um todo. 7 A JPA é uma nova API , mas não uma nova funcionalidade (a não ser qualitativamente), já que as versões anteriores do J2EE já suportavam persistência O/R com os Entity Beans. 8 O codinome “Tango” é específico ao lado da Sun do projeto, e achei ótimo. Afinal, o Tango é uma dança lenta, formal e sombria, e das mais difíceis de executar sem escorregar para o ridículo (pelo menos no estereótipo que aprendi no cinema: ver Quanto Mais Quente Melhor com Jack Lemmon, e O Baile de Ettore Scola).
26 Java Magazine • Edição 39 jm39.indb 26
15/8/2006 18:15:21
Java server-side. Como vimos, a principal promessa desta versão é a mesma do Java SE 5: facilidade de desenvolvimento. E não é sem tempo, pois a escalada dos requisitos das aplicações que temos que construir, e das tecnologias em voga (SOA, Web 2.0...) já torna nosso trabalho difícil mesmo quando nossas ferramentas são simples e racionais. Com o objetivo de cumprir esta promessa, o grupo de experts que defin iu o Java EE 5 (e suas muitas APIs componentes) tomou decisões corajosas, como a substituição total da malfadada especificação de persistência do EJB, e decisões humildes como a introdução de técnicas popularizadas por projetos open source, que acabaram conquistando a comunidade de desenvolvedores Java. Procuramos exibir um pouco destes avanços na facilidade de desenvolvimento com o tutorial no quadro “Glassfish: O Java EE 5 open source”. Algumas vantagens só serão sentidas de fato em aplicações bem mais complexas do que os limites impostos por um artigo (mesmo com um tutorial mais extenso). Mas é transparente o esforço que o JCP fez, nesta última revisão do Java EE, para renovar o velho J2EE, reduzindo muito sua complexidade. jcp.org/en/jsr/detail?id=244
JSR-244, padrão do JCP que define a plataforma JEE 5. java.sun.com/javaee
JEESDK 5 / Sun Java System Application Server 9. glassfish.dev.java.net
Projeto GlassFish. javamagazine.com.br/downloads/jm39/ jm-jee5.zip Osvaldo Pinali Doederlein
(
[email protected]) é Mestre em Engenharia de Software Orientado a Objetos, membro individual do Java Community Process (tendo participado do Expert Group da JSR-170: Tiger/J2SE 5.0), e trabalha na Visionnaire Informática como arquiteto e desenvolvedor.
Edição 39 • Java Magazine 27 jm39.indb 27
15/8/2006 18:15:26
Persistência no Java Iniciando com a Java Persistence API
O
principal foco da versão 5 do Java EE é a facilidade de uso. O novo Java EE é bem mais simples que sua versão anterior e suas novas APIs aumentam a produtividade dos desenvolvedores, exigindo menos esforço de codificação. Uma das principais novidades do Java EE 5 é a sua nova API de persistência. Há muito tempo esperada pela comunidade Java, a Java Persistence API (JPA) padroniza as operações de persistência sobre entidades Java, definindo uma especificação para mapeamento objeto-relacional. Neste artigo apresentaremos a JPA através da criação de um exemplo completo.
Persistência e mapeamento O/R Como sabemos, a tecnologia de banco de dados relacionais existe há décadas, e hoje os SGBDs são robustos e confiáveis. Os principais problemas relacionados ao armazenamento e recuperação de dados já foram solucionados, e o investimento das empresas nesses sistemas também é imenso, o que torna a sua utilização uma regra. O uso dos bancos de dados relacionais, no entanto, traz alguns inconvenientes ao programador de linguagens orientadas a objetos como Java. As tecnologias OO e relacional são bastante diferentes, e seu uso conjunto normalmente implica em enfatizar uma tecnologia em sacrifício da outra. Como os bancos de dados OO ainda estão muito menos disseminados que os bancos de dados relacionais, o desafio atual dos desenvolvedores é unir dois mundos completamente distintos, utilizando a tecnologia relacional para armazenar objetos. O armazenamento de objetos de uma aplicação é denominado persistência de ob jetos. Essa técnica permite que as instâncias existentes no sistema sejam armazenadas e posteriormente recuperadas, conservandose o seu estado mesmo após a aplicação ter sido finalizada.
Abordagens atuais de mapeamento
Desde as suas primeiras versões, a plataforma Java oferece acesso a bancos de dados através da API JDBC, que tra balha no mesmo nível do banco, sendo o acesso às informações armazenadas feito através de comandos SQL. Em muitos aspectos, a JDBC é uma API de baixo nível, que muitas vezes exige do desenvolvedor o conhecimento das “nuances” do banco de dados. Apesar de ser uma maneira eficiente de acessar dados em SGBDs relacionais, e a opção que normalmente oferece melhor performance, a JDBC oferece uma abstração OO bastante limitada (trabalha-se com tabelas, registros e resultsets, ao invés de objetos). Para usar os recursos de bancos de dados relacionais em Java e ainda assim aproveitar os conceitos do paradigma OO, é necessário fazer o que se conhece como mapeamento objeto-relacional (ou simplesmente mapeamento O/R). No mapeamento O/R as classes e os atributos do sistema são mapeados para tabelas e campos/colunas, e a persistência é feita de forma transparente pela aplicação. Assim, objetos em memória são armazenados no banco, e objetos do banco são trazidos para a memória sempre que necessário. Com paradigmas tão diferentes, diversas questões surgem durante o mapeamento: Como mapear herança? E agregação? Cada classe deve virar uma tabela? Como aproveitar os recursos do banco sem perder a abstração de objetos? Para suprir essas necessidades, surgiram diversos frameworks e tecnologias de persistência, a exemplo do Hibernate e do iBatis. Essas ferramentas facilitam o trabalho do desenvolvedor e aumentam sua produtividade, fornecendo poderosas APIs de manipulação de dados. Apesar de cada ferramenta
28 Java Magazine • Edição 39 jm39.indb 28
15/8/2006 18:16:30
EE 5
Aprenda a utilizar a nova API de persistência do Java através de um exemplo completo, com foco no uso em aplicações Java SE
ANDRÉ DANTAS ROCHA E SÉRGIO OLIVEIRA K UBOTA possuir uma forma distinta de efetuar o mapeamento O/R, os conceitos são semelhantes e relativamente simples, baseandose em POJOs (Plain Old Java Objects). O termo Plain Java Old Object (ou simplesmente POJO) foi criado por Mar tin Fowler, Rebecca Parsons e Josh MacKenzie em 2000. A tradução é algo como “velho e bom objeto Java” e refere-se a objetos/classes Java simples, não atrelados a ferramentas ou frameworks.
A Java Persistence API Até o J2EE 1.4 a plataforma Java não possuía uma forma simples de mapear objetos no banco de dados. A única opção era a utilização de Entity Beans, que exigem um container EJB e possuem uma complexidade razoável 1. Aplicações cuja arquitetura não envolvia EJBs precisavam utilizar ferramentas não padronizadas como o Hibernate para fazer a persistência, ou fazer a implementação de persistência manualmente. A Java Persistence API, definida na JSR-220 (Enterprise JavaBeans,Version 3.0), padroniza o mapeamento objeto-relacional na plataforma Java. Apesar de descrita na especificação do novo EJB, a JPA não depende de um container para funcionar, sendo possível usar e testar soluções apenas com o Java SE 2. A JPA é uma especificação baseada no conceito de POJOs, que incorpora idéias de renomados frameworks de persistência para padronizar o mapeamento O/R em Java. A API oferece uma solução completa para mapeamento e persistência de objetos: Um modo declarativo de descrever mapeamentos O/R Uma linguagem de consulta •
•
Um conjunto de ferramentas para manipular entidades •
Em se tratando de um padrão do Java Community Process, a JPA traz diversos benefícios. O uso de um padrão para a persistência de objetos permite que diversos fabricantes trabalhem sobre os mesmos conceitos e que o desenvolvedor escolha a implementação de sua preferência. A padronização também traz outra importante vantagem: pode-se alternar entre implementações de fabricantes distintos, que estejam em conformidade com a JSR-220, sem nenhum esforço adicional. Dessa forma, uma aplicação codificada de acordo com o novo padrão irá funcionar com qualquer implementação da JPA, não havendo necessidade de se conhecer (a princípio) qual tecnologia será utilizada para essa implementação. Conceitos básicos
Na JPA os objetos persistentes são denominados entidades ( entities). Uma entidade é um objeto simples (POJO), que representa um conjunto de dados persistido no banco. Como entidades são definidas por classes Java comuns, sem relação com frameworks ou bibliotecas, elas podem ser abstratas ou herdar de outras classes, sem restrições. Um conceito importante é que as entidades possuem um identificador (descrito pela chave primária) e estado, sendo seu tempo de vida independente do tempo de vida da aplicação. Assim, aplicações distintas podem compartilhar a mesma entidade, que é referenciada através de seu identificador. No Java EE 5.0, os “Entity Beans” foram revistos e se tornaram bem mais simples de usar 2 A independência do container trouxe uma grande melhoria na testabilidade das aplicações que mapeiam dados do banco 1
Edição 39 • Java Magazine 29 jm39.indb 29
15/8/2006 18:17:25
Persistência no Java EE 5
Para que uma entidade se torne persistente é necessário associá-la a um persistence context (contexto de persistência), que fornece a conexão entre as instâncias e o banco de dados. A manipulação das entidades é feita, a partir desse contexto, por meio do entity manager (gerenciador de entidades), que é responsável por executar as operações básicas sobre a entidade (criação, atualização, exclusão, localização, consultas etc.). O entity manager na JPA é uma instância da interface javax.persistence.EntityManager. A implementação da JPA é feita por um persistence provider (provedor de persistência). O provedor define “como as coisas funcionam”, através da implementação de todas as interfaces definidas pela especificação da JPA. Dessa forma, cada provedor decide a maneira e o momento de carregar, atualizar e armazenar as entidades, assim como sincronizar os dados com o banco. As configurações utilizadas pelo provedor em uma determinada aplicação (conexão com o banco de dados, entidades que serão gerenciadas, tipo de transação etc.) são descritas em uma persistence unit, que é configurada num arquivo especial denominado persistence.xml. Mapeamento
As classes e interfaces da JPA estão localizadas no pacote javax .persiste nce. A API faz uso intensivo de anotações 3; por isso não é necessário criar descritores XML para cada uma das entidades da aplicação4. Uma entidade é uma classe Java comum, rotulada através da anotação @Entity. Não é preciso implementar interfaces ou estender outras classes para tornar uma classe “persistível”; a única exigência é que a classe da entidade possua um construtor sem parâmetros, pois a instanciação da classe é feita por reflexão. No código a seguir a classe Pessoa representa uma entidade. O atributo cpf é o identificador da entidade (chave primária), especificado através da anotação @Id : @Entity public class Pessoa { @Id private String cpf; }
Grande parte da produtividade trazida pela JPA deve-se à utilização de valores default de mapeamento, que facilitam bast ante o trabalho do desenvolvedor. Assim, o que não é definido explicitamente assume a configuração padrão da API. Por exemplo, por padrão a JPA considera o nome da entidade o mesmo nome da tabela no banco de dados e o nome da propriedade o mesmo nome da coluna. No código anterior, a entidade Pessoa será salva na tabela PESSOA e a propriedade cpf na coluna CPF. Caso seja necessár io alterar a forma de mapeamento, devem-se utilizar as anotações @Table e @Column, por exemplo: @Entity @Table(name=”TB_PESSOA”) public class Pessoa { @Id @Column(name=”DS_CPF)” private String cpf; }
Outro padrão utilizado pelo JPA é considerar todas as propriedades de uma entidade como persistentes (o mapeamento segue as regras descritas anteriormente). Caso seja desejável excluir alguma propriedade do mapeamento (ex.: no caso de ela poder ser criada a partir de outras propriedades), basta marcá-la com a anotação @Transient : @Entity public class Pessoa { @Id private String cpf; @Transient private String nomeCompleto; }
Os dados de uma única entidade podem estar distribuídos em mais de uma tabela, e diversos tipos de relacionamentos entre entidades são possíveis. Os mais comuns são os de agregação (anotações @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, etc.) e herança (anotação @Inheritance ), que serão vistos mais adiante.
uma alternativa ao SQL, que também é suportado. As consultas suportam polimorfismo, o que significa que quando uma entidade é consultada, todas as entidades descendentes que atendam ao critério da consulta também são retornadas. A criação de consultas é feita através do EntityManager, que fornece métodos específicos para instanciar consultas estáticas e dinâmicas, além de permitir a execução das operações CRUD5. As consultas estáticas possuem nomes e são descritas pela anotação @NamedQuery. Elas são definidas nas entidades correspondentes e ficam “pré-compiladas”. Veja um exemplo de consulta estática para localizar uma pessoa pelo seu CPF: @NamedQuery(name = “consultarPorCPF”, query = “SELECT p FROM Pessoa p WHERE p.cpf = :cpf”)
O EntityManager utiliza o nome da consulta para instanciá-la, o que é feito através do método createNamedQuery(). Depois que a consulta é criada, basta setar os parâmetros e executá-la. A execução pode ser feita pelos métodos getSingleResult() ou getResultList(), a depender do resultado esperado. Por exemplo, para localizar uma pessoa pelo CPF (supondo que o retorno será único), basta executar a consulta conforme o exemplo abaixo: Query consulta = manager.createNamedQuery(“consultarPorCPF”); consulta.s etParameter (“cpf”, “111.111.111-11”); Pessoa pessoa = consulta.getSingleResult();
As consultas dinâmicas não possuem nome, e podem ser construídas em tempo de execução. A criação desse tipo de consulta é feito através do método createQuery() : Query consulta = manager.createQuery( “SELECT p FROM Pessoa p WHERE p.cpf = :cpf”); As anotações surgiram da versão 5.0 do Java e estão presentes na maioria das APIs do novo JEE. 4 Mesmo não sendo obrigatório o uso de XML para descrever o mapeamento, ainda é possível utilizar essa opção. Um exemplo típico é o arquivo persistence.xml , que guarda as configurações da unidade de persistência e é utilizado neste artigo. 3
Consultas
A JPA oferece suporte a consultas estáticas e dinâmicas. A API fornece uma linguagem própria de consulta, que é uma extensão bastante poderosa da EJB QL (a linguagem de consultas do EJB). Essa linguagem pode ser usada como
5 As principais operações executadas sobre dados são conhecidas através da sigla CRUD ( Create, Read, Update, e Delete).
30 Java Magazine • Edição 39 jm39.indb 30
15/8/2006 18:17:25
É interessante notar que a execução de ambas as consultas é idêntica, uma vez que as duas são objetos do t ipo Query.
Um exemplo completo Agora que os conceitos básicos da JPA foram apresentados, partiremos para um exemplo completo, onde a API será demonstrada na prática. Enquanto a maioria dos artigos sobre JPA foca no uso de EJBs, aqui faremos uma abordagem distinta. No exemplo deste artigo implementaremos uma aplicação desktop sem nenhum víncu lo com um container EJB. O objetivo é mostrar que a JPA pode ser utilizada no Java SE facilmente. Usaremos na camada de persistência o TopLink Essentials e o MySQL. O TopLink Essentials é a versão open source do TopLink, uma ferramenta de mapeamento objeto-relacional da Oracle. O TopLink Essentials implementa a JPA e é utilizado no projeto GlassFish, que é a base da implementação de referência do Java EE 5.0. Por simplicidade, e para focar melhor na API, não implementamos uma interface gráfica. O código completo da aplicação está disponível no site da Java Magazine. Veja mais sobre o ambiente utilizado e como executar a aplicação de exemplo no quadro “Configurando o ambiente”. A aplicação implementa um sistema (bastante simplificado) para gerenciamento de uma locadora de veículos. Na Figura 1 é exibido seu diagrama de classes e na Figura 2 o modelo ER (EntidadeRelacionamento) utilizado. Mapeamento do cliente
Começaremos com o mapeamento da classe Cliente, a mais simples da aplicação. O código da classe é mostrado na Listagem 1. Como vimos, a anotação @Entity torna a classe “persistível”, permitindo a sua manipulação pela JPA. Note que não foi necessário utilizar a anotação @Table para especificar a tabela utilizada para persistência, uma vez que uma tabela de mesmo nome é usada para persistir os clientes ( CLIENTE). A identidade de cada cliente é definida através da propriedade codigo. A anotação @Id indica que esse campo é utilizado como iden-
tificador ou chave primária, e @Column especifica a coluna correspondente no banco (CD_CLIENTE ). No nosso caso, a chave primária da entidade será gerada automaticamente pelo banco através de um campo de auto-incremento, o que é es-
pecificado pela anotação @GeneratedValue( strategy = GenerationType.IDENTITY) : @Id @Column(name = “CD_CLIENTE”) @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer codigo;
Configurando o ambiente
P
ara executar o exemplo disponível no site da Java Magazine, será necessário ter instaladas as ferramentas Eclipse 3.2, MySQL e TopLink Essentials.
pode variar.) Isso extrai , entre outros, o arquivo toplink-essentials.jar , que deve ser adicionado ao projeto no Eclipse.
Executado a aplicação Eclipse 3.2 O Eclipse 3.2 foi o IDE escolhido para implementar o exemplo. O download pode ser feito a partir de eclipse.org/downloads . Para quem ainda não usa o Eclipse, a instalação é simples, bastando extrair o conteúdo do arquivo compactado para o diretório de sua preferência. Após a instalação do Eclipse, o projeto de exemplo deve ser importado para o IDE. A estrutura do projeto é mostrada na Figura Q1.
MySQL O MySQL é usado no exemplo devido à sua facilidade de instalação e configuração. O download do MySQL e do MySQL Connector/J (driver JDBC) podem ser feitos em dev.mysql. com/downloads. Escolha a versão adequada ao seu sistema operacional e siga as instruções fornecidas no help. Após instalar, crie um database com nome “locadora” e execute o script de criação das tabelas. O arquivo compactado do driver Connector/J contém a biblioteca mysql-connector-java-3.1.12-bin.jar (a versão pode variar), que deve ser adicionado ao pro jeto do Eclipse.
Após esses passos, a aplicação estará pronta para ser iniciada. O projeto já contém a configuração do persis tence unit , localizada no arquivo META-INF/persistence.xml . Para rodar a aplicação basta executar a classe br.com.jm.locadora.Principal . Durante a execução, o console mostrará as seguintes mensagens: Populando tabelas... OK Listando todas as reservas... Sérgio - BMW André - ECO SPORT Listando todas as locacoes... André - ECO SPORT Listando reservas entre 01/06/2006 e 01/08/2006... André - ECO SPORT Listando dados do veículo ABC1234... ECO SPORT - ABC1234 [Utilitario]
Toplink Essentials O Toplink Essentials é a implementação da JPA utilizada no projeto GlassFish (glassfish.dev. java.net ). O download pode ser feito a partir da página do projeto: oracle.com/technology/ products/ias /toplink/jpa /download.html . Após baixar o arquivo execute o comando: java –jar glassfish-persistence-installer-v2-b11.jar
(Lembrando mais uma vez que a versão
Figura Q1. Estrutura do projeto.
Edição 39 • Java Magazine 31 jm39.indb 31
15/8/2006 18:17:26
Persistência no Java EE 5
O mapeamento das propriedades nome e endereco é muito simples. Ambas as propriedades possuem a anotação @Column, contendo dois atributos: name, que define a coluna correspondente no banco; e nullable,
que define se a propriedade em questão pode ser nula: @Column(name = “DS_NOME”, nullable = false) private String nome;
Veiculo Reserva
Cliente
{ From model }
{ From locadora }
{ From model } Attributes
veiculo
private String placa
Attributes
cliente
Attributes
private String modelo
private String codigo
private String codigo
private Integer ano
private Date inicio
private String nome
private String cor
private Date fim
private String endereco
private Double diaria
reserva
Esportivo
Utilitario
Locacao
{ From model }
{ From model }
{ From model }
Attributes
private Integer velocidade
Attributes
private Integer passageiros
Attributes
private Integer codigo
Figura 1. Diagrama de classes do sistema de locação de veículos.
A propriedade reservas é um pouco mais complexa, pois ela determina um relacionamento um-para-muitos. No nosso modelo, um cliente pode estar relacionado a diversas reservas, sendo que cada reserva relaciona-se a um único cliente. A anotação @OneToMany é usada para indicar o relacionamento entre as entidades (um cliente para muitas reservas). As reservas do cliente são armazenadas na propriedade reservas (uma lista de instâncias da entidade Reserva), que é populada automaticamente pelo TopLink. Dois atributos da anotação são utilizados nesse mapeamento: cascade = CascadeType.ALL indica que alterações na entidade Cliente serão refletidas automaticamente nas entidades Reserva relacionadas. O atributo mappedBy = “cliente” indica que na classe Reserva existe uma propriedade denominada cliente, que mapeia o cliente do relacionamento (veremos mais sobre a classe Reserva adiante). @OneToMany(cascade = CascadeType.ALL, mappedBy = “cliente”) private List reservas;
Mapeamento do veículo
Figura 2. Modelo Entidade-Relacionamento para o sistema de locação de veículos. Listagem 1.Cliente.java package br.com.jm.locadora.model; import java.util.List; import javax.persistence.*; @Entity public class Cliente { @Id @Column(nam e = “CD_CLIENTE”) @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer codigo; @Column(nam e = “DS_NOME”, nullable = false) private String nome; @Column(nam e = “DS_ENDERECO” , nullable = false) private String endereco; @OneToMany( cascade = CascadeType .ALL, mappedBy = “cliente”) private List reservas; // ... gets e sets omitidos }
Na modelagem OO da aplicação decidimos especializar a classe Veiculo, guardando as particularidades de cada veículo em classes específicas (veja a Figura 1). Veiculo é uma classe abstrata, que possui duas subclasses: Esportivo e Utilitario. No modelo relacional a abordagem foi distinta. Para evitar o custo de joins, todos os veículos foram mapeados em uma única tabela, que contém propriedades tanto genéricas quanto específicas. Como veremos a seguir, o nosso mapeamento deve contemplar essa decisão arquitetural e permitir que os objetos sejam instanciados corretamente (de acordo com sua classe), mesmo com todos residindo em uma tabela genérica. Vamos analisar o código da entidade Veiculo (Listagem 2). Como já conhecemos as anotações @Entity, @Id e @Column, veremos apenas as novas construções. O mapeamento da herança de entidades é descrito através da anotação @Inheritance, que define a estratégia usada no mapeamento. De forma semelhante a outros
32 Java Magazine • Edição 39 jm39.indb 32
15/8/2006 18:17:28
frameworks O/R, a JPA oferece três tipos de mapeamento de herança. Estes são descritos na enumeração InheritanceType : SINGLE_TABLE, TABLE_PER_CLASS e JOINED. A estratégia SINGLE_TABLE é a mais comum, sendo a opção default da JPA. Nessa estratégia todas as entidades da hierarquia são persistidas em uma única tabela, que deve conter todos os campos necessários. Na estratégia TABLE_PER_CLASS cada entidade é mapeada em uma tabela específica, enquanto na estratégia JOINED uma associação de tabelas é usada para persistência de cada entidade. A estratégia de mapeamento deve ser especificada na entidade raiz da hierarquia, através do atributo strategy. Seguindo o nosso modelo, a hierarquia de veículos seguirá a estratégia SINGLE_TABLE:
que o valor “E” for encontrado no campo CD_TIPO da tabela VEICULO, uma entidade Esportivo será criada. Quando o campo CD_TIPO contiver o texto “U” uma entidade Utilitario será criada, como especificado Listagem 2.Veiculo.java package br.com.jm.locadora.model; import java.util.List; import javax.persistence.*;
@Entity @Table(name = “VEICULO”) @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = “CD_TIPO”, discriminatorType = DiscriminatorType.STRING) public abstract class Veiculo { @Id @Column(name = “DS_PLACA”, nullable = false) private String placa; @Column(name = “DS_MODELO”, nullable = false) private String modelo; @Column(name = “NR_ANO”, nullable = false) private Integer ano;
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Column(name = “DS_COR”, nullable = false) private String cor;
O mesmo mapeamento pode ser definido de forma simplificada, visto que SINGLE_TABLE é a estratégia de mapeamento default da JPA:
@Column(name = “VL_DIARIA”, nullable = false) private Double diaria; @OneToMany(c ascade = CascadeType. ALL, mappedBy = “veiculo”) private List reservas; @Column(name = “CD_TIPO”, nullable = false) public abstract String getTipo();
@Inheritance
Como todas as entidades da hierarquia são armazenadas em uma única tabela, é necessário determinar a correspondência entre registros e entidades. A anotação @DiscriminatorColumn define a regra utilizada pela JPA para “reconhecer” cada registro e instanciar a entidade correspondente. O atributo name especifica a coluna que armazena o descritor da entidade, enquanto o atributo discriminatorType define o tipo dessa coluna: @DiscriminatorColumn(name = “CD_TIPO”, discrim inatorType = Discri minatorType.STRING )
pela anotação @DiscriminatorValue(“U”) da etintidade Utilitario. É importante notar que, como as entidades Esportivo e Utilitario derivam de Veiculo, só é necessário mapear as novas propriedades
// ... gets e sets omitidos }
Listagem 3.Esportivo.java package br.com.jm.locadora.model; import javax.persistence.*; @Entity @DiscriminatorValue(“E”) public class Esportivo extends Veiculo { @Column(name = “VL_VELOCIDADE”, nullable = false) private Integer velocidade; // ... gets e sets omitidos }
Listagem 4.Utilitario.java
Durante a persistência, a JPA lê o valor armazenado na coluna CD_TIPO (uma string) e escolhe a entidade com a qual irá trabalhar. Essa escolha é feita a partir de uma outra anotação, especificada nas entidades descendentes: @DiscriminatorValue. Nas Listagens 3 e 4 são exibidos os códigos das entidades Esportivo e Utilitario. A anotação @DiscriminatorValue(“E”), contida na entidade Esportivo, especifica que sempre
package br.com.jm.locadora.model; import javax.persistence.*; @Entity @DiscriminatorValue(“U”) public class Utilitario extends Veiculo { @Column(name = “NR_PASSAGEIROS”, nullable = false) private Integer passageiros; // ... gets e sets omitidos }
Edição 39 • Java Magazine 33 jm39.indb 33
15/8/2006 18:17:29
Persistência no Java EE 5
especificadas nessas entidades ( velocidade e passageiros). Além disso, é possível especificar a obrigatoriedade dos campos das entidades descendentes ( nullable = false) mesmo com todas compartilhando uma só tabela. A obrigatoriedade só será avaliada para registros correspondentes à entidade em questão.
Mapeamento da reserva
Já apre se nt am os a entidade Reserva (Listagem 5), que descreve a reserva de um veículo, relacionando-se com as entidades Veiculo e Cliente. Reserva representa o lado “filho” dos relacionamentos veiculo-reserva e cliente-reserva, e armazena o cliente e o veículo nas propriedades cliente e veiculo. Para
Listagem 5.Reserva.java package br.com.jm.locadora.model; import java.util.Date; import javax.persistence.*; @Entity @NamedQueries({ @NamedQuery( name = “Reserva.lis tarPorPeriodo ”, query = “SELECT r FROM Reserva r WHERE “ + “r.inicio >= :inicio AND r.fim Generate Entities. A tela de geração de entidades será exibida ( Figura Q4 ) listando todas as entidades encontradas no banco. Selecione as entidades que deseja gerar e clique em Finish. Todas as entidades e mapeamentos serão gerados automaticamente, assim como o arquivo persistence.xml . Para mapeamentos mais complexos é provável que algum ajuste manual ainda seja necessário.
Figura Q2. Adicionado persistência ao projeto.
os JARs do Toplink Essentials e do driver do MySQL. Após a criação do projeto, deve-se torná-lo “persistível”. Para isso clique com o botão direito sobre o projeto e selecione Java Perspective>Add Java Persistence. Uma caixa de diálogo semelhante à da Figura Q2 será exibida. Preencha os dados conforme a figura e clique em Add Connections para criar uma nova conexão ao MySQL. A tela de configuração de conexão será exibida ( Figura Q3). Preencha os dados conforme ilustrado e clique em Finish.
Figura Q4. Gerando as entidades.
36 Java Magazine • Edição 39 jm39.indb 36
15/8/2006 18:17:42
Veiculo veiculo = new Veiculo(“ABC1234”); manager.persist(veiculo);
A exclusão da entidade é feita através do método remove(): manager.remove(veiculo);
Consultas mais complexas são efetuadas através da interface Query. Como visto anteriormente, o EntityManager é responsável por instanciar as consultas, que podem ser tanto dinâmicas quanto estáticas. A seguinte consulta estática lista todas as reservas existentes em um determinado período: @NamedQuery(name = “Reserva.listarPorP “Reserva.listarPorPeriodo” eriodo”,, query = “SELECT r FROM Reserva r WHERE “ + “r.inicio >= :inicio AND r.fim Project> Web>Dynamic Web Project . Na primeira tela (Figura 2) preencha apenas o nome do projeto (“ComponentesGWT”) e clique em Next. Marque a opção Googlipse e finalize. O projeto criado já possuirá Listagem 1.Script para criação do banco de dados.Apenas uma tabela será necessária. as bibliotecas do GWT no classpath. CREATE TABLE artigogwt.contato ( contato_id BIGINT NOT NULL AUTO_INCREME NT, Para começar a consnome VARCHAR(45), truir nossos componentelefone VARCHAR(45), celular VARCHAR(45), tes precisamos criar um email VARCHAR(45), GWT Module. Clique com endereco VARCHAR(150) VARCHAR(150), , PRIMARY KEY(contato_ id) o botão direito sobre o ) nome do projeto, navegue até New>Other>Googlipse e escolha a opção GWT M o d u l e . Na próxima tela configure o campo Location: clique no botão Create, e em Name forneça br.com.jm.gwt ; depois clique em Finish. Em Name digite “JMListaContatos” para o nome do módulo. Após finalizar, três pacotes serão criados: br.com.jm.gwt.client – Com código Java que será transformado em JavaScript e executará no browser. Figura 1. Lista de contatos com paginação, DialogBox, campos editáveis e fotografia. •
Edição 39 • Java Magazine 39 jm39.indb 39
15/8/2006 18:18:12
AJAX Avançado com GWT
br.com.jm.gwt.client.JMListaContatos. java – Classe responsável por inicializar •
os componentes e posicioná-los dentro do HTML. É chamada pelo GWT de Entry Point. br.com.jm.gwt.JMListaContatos.gwt.xml
•
– Documento que define as configurações do módulo GWT e seus serviços. Deixe a classe JMListaContatos e o arquivo JMLi staC ontatos.g wt.x ml inalterados por enquanto. Mas modifique o arquivo JMListaCo ntatos. html deixando-o igual à Listagem 2 . Este arquivo inclui dois componentes que são referenciados pelos seus ids (contatos e fotografia ) dentro de uma tabela HTML:
Figura 2. Criando de um Dynamic Web Project
Com a organização do projeto concluída (veja a Figura 3), podemos passar à construção dos nossos componentes. Todos os componentes serão criados dentro do pacote br.com.jm.gwt.client .
Componente Fotografia O primeiro componente que vamos criar mostra a fotografia do contato selecionado, e estende a classe Image do GWT. Uma funcionalidade adicional da nossa classe Fotografia (mostrada na Listagem 3) é que se não houver imagem cadastrada para o contato, será mostrada uma imagem padrão. A classe também demonstra o tratamento de eventos na leitura de imagens. Observe que o construtor de Fotografia recebe um objeto da classe Contato (Listagem 4). Na instanciação já é definido o tamanho, o estilo CSS e qual a URL da imagem. Outro fato importante está na implementação da interface LoadListener, através dos métodos onError() e onLoad() . O método onError() é
Listagem 2.HTML que define onde ficarão nossos componentes - JMListaContatos.html JavaM agazine - Lista de Contatos Lista de contatos
chamado quando houver algum problema no carregamento da imagem, por exemplo, quando ela não existir. É nele que carregamos uma imagem padrão. Já o método onLoad() é chamado quando a imagem é carregada corretamente; ele está vazio porque não precisamos de nenhum tratamento caso a imagem tenha sido carregada com sucesso. Note ainda que no construtor é definida a própria instância da classe como listener, com addLoadListener(this) . O componente Fotografia será utilizado em dois lugares na nossa aplicação. Ao lado da tabela de contatos e na caixa de diálogo mostrada ao se selecionar um contato.
Componente MeuDialogBox A API do GWT inclui o componente DialogBox, que permite mostrar janelas popup com informações ou avisos. Em nosso exemplo vamos criar uma classe que estende DialogBox , para aproveitar características desse componente, como a exibição de uma área de título, a possibilidade de arrastá-lo e sua característica modal (enquanto a janela estiver ativa o resta nte da aplicação fica bloqueado). A Figura 1 mostra como ficará o layout da nossa caixa de diálogo. Utilizamos um pouco de CSS para ajudar na formatação do título e bordas. Colocamos a definição dos estilos no arquivo estilosListaContatos.css (Listagem 5), dentro da pasta br.com.jm.gwt.public. O CSS é importado pela página JMListaContatos. html. A maioria dos componentes gráficos (widgets) do GWT possui um estilo CSS pré-definido, ou seja, você precisa apenas defini-lo no seu arquivo CSS, que o componente já o usará. O DialogBox utiliza dois estilos, um para a janela e outro para a área de título: gwt-DialogBox e gwt-DialogBox .Caption. A Listagem 6 mostra o código do componente MeuDialogBox . No construtor foram
definidos o título, o tamanho e a localização da caixa de diálogo. A fotografia, o conteúdo e o botão foram posicionados dentro de um componente DockPanel. Para entender mais sobre painéis como este e outros componentes gráficos do GWT, veja o quadro “Panels e Widgets”.
40 Java Magazine • Edição 39 jm39.indb 40
15/8/2006 18:18:13
Note também que na criação do botão de fechar já instanciamos um ClickListener, que no evento de clique ( onClick()) fechará a caixa de diálogo através do método hide() , herdado da superclasse. E por último, o método onKeyDownPreview(), que trata dos eventos do teclado, define que ao pressionar Enter ou Esc a caixa de diálogo seja fechada, também usando hide() .
Trabalhando com Composites A classe Composite do GWT tem a função de organizar a construção de componentes mais complexos. Ela funciona como um “wrapper” de componentes , escondendo os métodos públicos do componente “embrulhado”. Normalmente configuramos o Composite com um panel (ex. DockPanel ) contendo vários componentes, pois dessa ma neira é possível tratá-los como um elemento único. Criaremos três componentes baseados na classe Composite : BarraNavegacao – Composto de um HorizontalPanel, que contém dois Button s e um Label. TabelaEditavel – Composto de um DockPanel com a barra de navegação no topo e um Grid centralizado. ListaContatos – Que instancia um componente TabelaEditavel com uma fonte de dados e nomes das colunas.
Observe que um Composite pode ter outros objetos Composite . Esse é o caso da ListaContatos e da TabelaEditavel . A seguir, veremos detalhadamente como criar os três componentes citados.
Componente BarraNavegacao A barra de navegação, cujo código é mostrado na Listagem 7, terá três elementos: dois botões (próximo e anterior) e um Label. Seu construtor configura o posicionamento dos elementos dentro de um HorizontalPanel, e recebe uma fonte de dados. Não se preocupe com a fonte de dados por enquanto; veremos mais sobre ela ao tratar dos serviços do GWT. A principal função da barra de navegação
é gerenciar os cliques nos botões de próximo e anterior, e mostrar a mensagem “Carregando...” enquanto a tabela é populada. A barra será exibida acima da lista de contatos, como já mostrado na Figura 1 e seu layout é definido como a seguir: horizontalPanel.add(mensagemStatus); horizontalPanel.setCellWidth(mensagemStatus, “100%”); horizontalPanel.add(btnAnterior); horizontalPanel.add(btnProxima);
Para capturar os cliques de próximo ou anterior, o componente BarraNavegacao implementa ClickListener e se registra como listener no momento da instanciação dos botões (passando this como segundo parâmetro):
•
•
•
Figura 3. Estrutura de pacotes e arquivos de um projeto GWT
Edição 39 • Java Magazine 41 jm39.indb 41
15/8/2006 18:18:15
AJAX Avançado com GWT
Listagem 3.Componente Fotografia – Fotografia.java
{
setText(titulo);
public class Fotografia extends Image implements LoadListener { public Fotografia(Contato contato) {
//posicao do DialogBox setPopupPosition(DESLOCAMENTO_ESQUERDA, DESLOCAMENTO_SUPERIOR);
//configura o endereço da foto setUrl(“foto s/” + Long.toString (contato.get ContatoId()) + “.jpg”);
setWidth(TAMANHO_JANELA);
import com.google.gwt.user.client.ui.*;
//configurando o tamanho da imagem setWidth(“110”); setHeight(“110”);
//configurando o estilo/css da imagem setStyleName(“fotografia”);
//configura a própria instância como o loadListener addLoadListener(this);
//Criando o botão para fechar o DialogBox, e o seu listener Button btnFechar = new Button(“Fechar”, new ClickListener() { public void onClick(Widg et sender) { hide(); } });
//cria um DockPanel e define o layout do DialogBox DockPanel panelBase = new DockPanel(); panelBase.a dd(imagem, DockPanel.WE ST); panelBase.add(btnFechar, DockPanel.SOUTH); panelBase.a dd(new HTML(conteud o), DockPanel.CEN TER);
}
public void onError(Widge t sender) { setUrl(“fotos/foto_padrao.jpg”); } public void onLoad(Widget sender) {}
}
//configura a distância entre os componentes deste panel panelBase.setSpacing(4); //configura o conteudo do DialogBox como sendo o DockPanel add(panelBase);
}
Listagem 4.Contato.java public class Contato implements IsSerializable { public long contatoId; public String nome; public String telefone; public String celular; public String email; public String endereco; public Contato() {} //getters e setters ... }
public boolean onKeyDownPrev iew(char key, int modifiers) { //caso o usuário tecle ESC ou ENTER o DialogBox se fechará switch (key) { case KeyboardListe ner.KEY_ENTE R: case KeyboardListe ner.KEY_ESCA PE: hide(); break; } return true; } }
Listagem 7.BarraNavegacao.java
Listagem 5.Arquivo CSS com estilos utilizados pelos componentes GWT – estilosListaContatos.css .gwt-TextBox { border-width : 0px; height: 15; width: 140; font-size: 12; }
public class BarraNavegacao extends Composite implements ClickListener { public Button btnProxima = new Button(“>”, this); public Button btnAnterior = new Button(“<”, this); public Label mensagemStatu s = new Label(); //... outros atributos
.gwt-DialogBox { border: 2px solid #AAAAAA; background-c olor: white; } .gwt-DialogBox .Caption { backgr ound-image: url(‘imagens/ back_degrad. gif’); background-repeat: repeat-x; padding: 4px; padding-bott om: 8px; font-weight: bold; cursor: default; } .gwt-Button { border: 1px solid #8E8E8E; width: 70; }
Listagem 6.Componente personalizado para o endereço do contato – MeuDialogBox.java public class MeuDialogBox extends DialogBox { private final static int DESLOCAMENTO _ESQUERDA = 80; private final static int DESLOCAMENTO _SUPERIOR = (Window.getClientHeight() - 256) / 2; private final static String TAMANHO_JAN ELA = “400px”; public MeuDialogBox(Image image, String titulo, String conteudo)
public BarraNavegaca o(FonteDeDad os fdContatos) { this.fdContatos = fdContatos;
this.horizontalPanel.add(btnAnterior); this.horizontalPanel.add(btnProxima); this.btnAnterior.setEnabled(false); setWidget(horizontalPanel); //… definição do layout da barra de navegação }
public void onClick(Widget sender) { if (sender == btnProxima) { paginaAtual++; } else if (sender == btnAnterior) { paginaAtual--; } setStatus(“ Carregando ...”); fdContatos.setPagina(paginaAtual); } public void setStatus(String status) {…} public void enable(boolean ultimaPagina) {…} }
42 Java Magazine • Edição 39 jm39.indb 42
15/8/2006 18:18:15
Listagem 8.TabelaEditavel.java public class TabelaEditavel extends Composite implements TableListener { public TabelaEdita vel(FonteDeDa dos fonteDeDados, String[] nomesColunas,int nroRegistrosPagina) { //... config. dos atributos com os valores dos parâmetros // configura esta instância como o Listener do Grid // para receber informações sobre o clique, linha e coluna grid.addTableListener(this);
//adiciona a barra de navegação e o Grid ao DockPanel panelBase.ad d(barraNavega cao, DockPanel.N ORTH); panelBase.add(grid, DockPanel.CENTER); //configura o Composite com o DockPanel setWidget(panelBase);
//... métodos para layout – tamanho, cabeçalho, css, etc } /* trata os eventos de cliques na tabela */ public void onCellClicked (SourcesTabl eEvents sender, int row, int cell) { //retornar se clique for no cabeçalho if (row == 0) return; //se clicado na primeira coluna, mostra a caixa de diálogo if (cell == 0) { mostraDialogBox(row); return; } //troca o conteúdo da célula por um campo editável if (cell > 1) { editaCelula( row, cell, widget); } //… definições de layout - destacar linha selecionada
//ao perder o foco atualiza a base de dados com o novo valor textBox.addF ocusListener( new FocusListener (){…});
} private void mostraDialog Box(int row) { Contato contato = getContato(ro w); Fotografia fotografia = new Fotografia(contato); //instancia o DialogBox que criamos MeuDialogBox dialogBox = new MeuDialogBox( fotografia, “Contato: “ + contato.getN ome(), “Endereco : ” + contato.getEndereco());
} private void atualizaRegi stro( int row, int coluna, String conteudoCelul a) { //substitui o TextBox por um Label Label label = new Label(); TextBox textBox = (TextBox) grid.getWidg et(row, coluna); label.setText(textBox.getText()); grid.setWidget(row, coluna, label);
dialogBox.show();
//busca o registro alterado Contato contato = getContato(row);
} private void editaCelula( final int row, final int cell, Widget labelCelula) { //troca o conteúdo da célula - de Label para TextBox final TextBox textBox = new TextBox(); textBox.setT ext(((Label) labelCelula) .getText()); grid.setWidget(row, cell, textBox); //ao pressionar Enter atualiza o registro com a alteração textBox.addK eyboardListen er(new KeyboardList ener() { public void onKeyDown(Wid get sender, char keyCode, int modifiers) {} public void onKeyPress(Widget sender, char keyCode, int modifiers) {}
public void onKeyUp(Widge t sender, char keyCode, int modifiers) { switch (keyCode) { case KeyboardListener.KEY_ENTER: atualizaRegistro(row, cell, textBox.getText()); break; } } });
// como o GWT não possui suporte para reflection, // usamos um switch switch (coluna) { case 2: contato.setTelefone(conteudoCelula); break; case 3: contato.setCelular(conteudoCelula); break; case 4: contato.setEmail(conteudoCelula); break; }
//atualiza base de dados fontDeDados.updateContato(contato);
}
private void mostraFoto(i nt row) {…} public void atualizaDados (List contatos) {…} private void configuraGri dLayout() {…} private Contato getContato(in t row) {…} private void marcarLinha( int row) {…} }
Edição 39 • Java Magazine 43 jm39.indb 43
15/8/2006 18:18:20
AJAX Avançado com GWT
public Button btnProxima = new Button(“> ”, this); public Button btnAnterior = new Button(“&l t;”, this);
Assim que um dos botões é clicado, é chamado o método onClick(). Em seqüência, o componente atualiza o atributo paginaAtual, muda a mensagem do Label para “Carregando...”, e chama o método setPagina() da fonte de dados. Veja que a barra de navegação não possui referência ao componente TabelaEditavel , interagindo apenas com a fonte de dados. É a fonte de dados que é responsável por atualizar os objetos conectados a ela.
Componente TabelaEditavel O componente TabelaEditavel é o mais complexo que criaremos neste artigo. Veja seu código na Listagem 8. A estrutura interna do componente é construída dentro de um DockPanel, com um componente BarraNavegacao na posição NORTH, e um Grid (da API do GWT) no centro, onde serão exibidos os dados dos contatos. O construtor instancia a barra de navegação já com uma fonte de dados, guarda o número de registros por página, configura a própria instância como o listener do Grid (para capturar os cliques sobre ele), e determina o layout dos componentes dentro do DockPanel. O método onCellClicked() é chamado quando algum evento de clique ocorre na grade. Caso o clique tenha sido na primeira coluna, é mostrada uma caixa de diálogo com o endereço do contato. Um clique nas outras colunas tornará a célula correspondente editável. O método mostraDialogBox() instancia a caixa de dialogo já com uma fotografia e o endereço do contato configurados. A caixa de diálogo é exibida através do método show() . E editaCelula() substitui o Label na célula clicada por um TextBox (da API do GWT) para edição do valor. Este TextBox terá dois listeners. Um para eventos de foco, FocusListener, e outro para eventos de teclado, KeyboardListener . Quando o TextBox perder o foco ou o usuário pressionar Enter, será chamado o método atualizaRegistro() para persistir no banco de dados a alteração efetuada. Por fim, o método atualizaRegistro() substitui o TextBox por um Label, atualiza o campo
correto do objeto Contato e persiste a alteração através da fonte de dados. Além disso, para saber qual registro foi selecionado, é mantido um mapeamento interno entre as linhas da grade e os contatos exibidos. Assim, quando uma linha é selecionada, o objeto Contato correspondente também o é.
de explicarmos seu código, veremos uma breve introdução sobre esses serviços. O GWT já traz pronta uma infra-estrutura para trabalhar com RPCs ( Remote Procedure Calls ) facilitando a troca de objetos Java entre o cliente (o browser) e o servidor. Todas as chamadas são assíncronas, logo a interface gráfica fica
Componente ListaContatos O componente ListaContatos , com código mostrado na Listagem 9, reúne uma fonte de dados (objeto FonteDeDados ) e um componente TabelaEditavel. A fonte de dados recebe o número de registros por página, e a tabela editável recebe os nomes das colunas, o número de registros por página e a própria fonte de dados.
Serviços no GWT Para fazer acesso aos dados dos contatos no banco, utilizaremos a classe FonteDeDados. Esta classe faz uso da infra-estrutura de serviços remotos do GWT, assim antes
Figura 4. Configurando o serviço remoto
Listagem 9.ListaContatos.java: componente que reúne a tabela editável e a fonte de dados public class ListaContatos extends Composite { private TabelaEditavel tabelaEditavel; private FonteDeDados fdContatos; public ListaContatos Widget(int nroRegistros Pagina) { String[] nomesColuna s = new String[] { “”, “Nome”, “Telefone”, “Celular”, “E-mail” }; this.fdConta tos = new FonteDeDados( nroRegistros Pagina); this.tabelaE ditavel = new TabelaEditave l(fdContatos , nomesColunas, nroRegistrosPagina); setWidget(tabelaEditavel); } }
Listagem 10.ContatosService.java: interface de serviço package br.com.jm.gwt.client.service; //… imports public interface ContatosService extends RemoteService { //métodos de serviços public List getContatos( int posicaoInicia l, int nroRegistros Pagina) throws Exception; public void updateContat o(Contato contato) throws Exception;
public static final String ENTRY_POINT = “/contatosService”; public static class Util { public static ContatosServ iceAsync getInstance( ) { ContatosServiceAsync instance = (ContatosServiceAsync) GWT .create(ContatosService.class); ServiceDefTa rget target = (ServiceDefTa rget) instance; target.setServiceEntryPoint(ENTRY_POINT); return instance; } } }
44 Java Magazine • Edição 39 jm39.indb 44
15/8/2006 18:18:21
Panels e Widgets
N
este artigo utilizamos vários painéis ( panels) e componentes gráficos (widgets ) da API do GWT. Aqui apresentamos uma breve explicação sobre cada um deles.
Panels Os panels são “recipientes” de componentes gráficos utilizados para facilitar o posicionamento e o agrupamento. Utilizamos três tipos de painéis no exemplo: HorizonalPanel , DockPanel e RootPanel .
seguir um pouco sobre os componentes utilizados neste artigo.
TextoBox Campo simples para entrada de texto. Seu estilo CSS padrão é: gwt-TextBox . Principais métodos: getText() : retorna o texto digitado no campo; getSelectedText(): retorna o texto selecionado dentro do campo; selectAll() : seleciona todo o texto dentro do campo; setText(String): configura o valor do campo; addChangeListener() , addClickListener() , addKeyboardListener() : adicionam listeners para mudança do conteúdo, eventos de mouse e teclado, respectivamente. •
•
•
HorizontalPanel Este tipo de painel organiza seus componentes da esquerda para direita em uma mesma linha, e o utilizamos para organizar os Button s e o Label de mensagem na barra de navegação.
• •
DockPanel O DockPanel possiciona os seus elementos seguindo a numeração da Figura Q1 (extraída da documentação do GWT). Após colocar um widget na posição central nenhum outro widget deve ser adicionado ao DockPanel . Se não for indicado o posicionamento ao adicionar os componentes, o último a ser adicionado ocupará a posição central. No exemplo, utilizamos o DockPanel para fazer o layout da caixa de diálogo e da tabela editável.
RootPanel O RootPanel é o último painel no qual um componente deve ser adicionado. Sua localização é definida dentro do HTML principal ( Host Page ) , normalmente utilizando-se um id. O RootPanel é instanciado pelo GWT e obtido através de dois métodos estáticos: RootPanel.get() : retorna o RootPanel padrão da aplicação. RootPanel.get(String id) : retorna o RootPanel associado a um elemento/id definido no HTML. •
•
O RootPanel não possui um layout definido. Sua função é apenas receber componentes e exibi-los.
Widgets A API do GWT fornece componentes gráficos (widgets ) para vários fins, por exemplo campos de texto, checkboxes, tabelas, menus, janelas popup, árvores de navegação e grades. Veja a
setHTML(int linha, int coluna, String str) : adiciona um código HTML em uma determinada célula; getWidget() , getText() , getHTML() : retornam o widget , a string ou código HTML de uma determinada célula; addTableListener(): adiciona um listener para eventos de clique sobre a tabela. •
•
•
Button Um botão HTML normal. Seu estilo CSS padrã o é: gwt-Button . Principais métodos: click() : equivalente a clicar no botão; addClickListener(), addKeyboardListener(): adiciona listeners para eventos de clique e de teclado. • •
Image Componente para exibição de imagens a partir de uma dada URL. Seu estilo padrão é: gwt-Image{} . Principais métodos: setUrl() : configura a URL da imagem a ser exibida; addClickListener() , addLoadListener() , addMouseListener() : adiciona listeners para eventos de clique, carregamento de imagem e mouse. •
•
DialogBox Caixa de diálogo que possui área de título e a possibilidade de arrastá-la. O DialogBox utiliza dois estilos, um para a janela e outro para a área de título: gwt-DialogBox e gwt-DialogBox .Caption . No nosso exemplo, estendemos este componente para customizá-lo conforme nossas necessidades de layout, e para tratar eventos de teclado. Principais métodos: setText() : configura o título da caixa de diálogo. setHTML(): configura o conteúdo da caixa de diálogo com um código HTML. addWidget() : configura o conteúdo da caixa com um widget. •
•
•
Grid Tabela redimensionável que pode conter texto, html ou widgets. Não possui estilo padrão. Principais métodos: resize(int, int) : redimensiona a tabela; •
north (0)
west (1)
west (2)
center (5)
east (3)
•
setWidget(int linha, int coluna, Widget w) : adiciona um widget em uma determi-
nada célula; •
south (4)
setText(int linha, int coluna, String str) :
adiciona uma String em uma determina célula;
Figura Q1. Ordem de posicionamento de componentes em um DockPanel
Edição 39 • Java Magazine 45 jm39.indb 45
15/8/2006 18:18:25
AJAX Avançado com GWT
Complete a sua coleção!
9 o ã ç i
d E
- Java no Governo - Apache FOP - JSTL – Guia Completo - Cocoon Inicial - Pacotes WAR e JAR
0 1 o ã ç i
1 o ã ç i d E
2 o ã ç i d E
- Ferramentas livres - Introdução ao J2EE 1.4 - Introdução a J2ME - J2EE Fundamental - Dados com JDBC
1 1 o ã ç i
d E
- Multimídia no celular - Automação com Ant - Robocode - Tag Libraries JSP - Processando XML em Java
- Dados em J2ME - JavaServer Faces - Jogos wireless - Certificação J2EE - Montando um ambiente Java
3 1
2 1 o ã ç i
d E
- Códigos no Eclipse - New I/O Fundamental - Game API - Criando Plug-ins para Eclipse - Preferences API
3 o ã ç i d E
o ã ç i d E
d E
- Tutorial de NetBeans - API New I/O (java.nio) - Cesta de compras com Struts - Testes de carga com JMeter - Concorrência e a JVM
- Relatórios Corporativos - Gráficos com Java 2D - Java.net na Prática - Raio-X do Tiger - Paginação na Web
- Eclipse para Web - Fome Zero com Java - Tags Customizadas em JSP 2.0 - Tiger: A Evolução do Java - Dicas para Web
9 1 o ã ç i d E
- JSTL aplicado no Tomcat 5 - Modularizando páginas com Tiles - Componentes View do Struts - O rugido do Java livre
9 2 o ã ç i
d E
- JavaMail - Por Dentro do Apache Derby - Clusters com Tomcat e Apache - Mais HttpClient - Examinando o Mustang
1 2 o ã ç i d E
0 2 o ã ç i d E
- Threads no Java 5.0 - Cadastros com Struts - MVC na web - Servlet API Avançada - Padrões de projeto
0 3 o ã ç i
- Relatórios avançados - Mais design patterns - Gerenciamento com JMX - Java Web Start - Dúvidas de classpath
1 3 o ã ç i
d E
- HSQLDB - Internacionalização de MIDlets - Performance na JVM - Caso de sucesso: Procon - Benchmarks JME
3 2 o ã ç i d E
- Criptografia aplicada - XML de alto desempenho - Segurança em apllicações web - JSF Passo a Passo - Datas e Horas em Java
- O Projeto Eclipse - Segurança no JBoss - JSF Avançado - Começando com Java - Tira-dúvidas
2 3 o ã ç i
d E
46 Java Magazine • Edição 39 www.javamagazine.com.br jm39.indb 46
2 2 o ã ç i d E
3 3 o ã ç i
d E
- SWT no Eclipse - Eclipse Web Tools Project - Validação avançada com Struts - Fronteiras do Java - CD do NetBeans
d E
- NetBeans 5.0 - Aprendendo Groovy - Test-Driven Development - Debate Internacional Sou+Java - Java Business Integration (parte 1)
- Hibernate em aplicações web - Java 6 (Mustang) - Programação em par - Processos ágeis - Java Business Integration (parte 2)
devmedia.com 15/8/2006 18:18:38
4 o ã ç i d E
5 o ã ç i d E
- Eclipse inicial - O mercado J2ME - Segurança em aplicações web - Interfaces ricas com Flash - Expressões regulares no J2SE 1.4
4 1 o ã ç i
6 o ã ç i
d E
- JBoss Inicial - Introdução ao JMX - Java no Lego Mindstorms - Logging - Memória e desempenho
5 1 o ã ç i
d E
d E
- Formulários com Swing - ANT - Automatizando Java - JBoss e Entity Beans - Extreme Programming - Metaprogramação e Reflection
- Otimização de EJBs no JBoss - Processamento de imagens - Programação com regras - Jakarta Taglibs - Case J2ME
- Introdução ao Tomcat - Conectivade com MIDP - Struts, primeiros passos - Automação com XDoclet - Jakarta Velocity
6 1 o ã ç i
d E
- Genéricos no Tiger - JBuilder para web - MIDP 2.0 - JavaOne 2004 - Segurança com JAAS
7 o ã ç i d E
8 o ã ç i d E
- Bancos de dados livres - Testes unitários com JUnit - JSTL- Guia Completo: tags Core - Java na Droga Raia - Validação na Web
8 1 o ã ç i
7 1 o ã ç i
d E
d E
- SwingWT - Java 2D: Animação e impressão - Anotações no Java 5 - Projeto Looking Glass - Java 2D: Animações e Impressão
•
•
•
Gerência deConteúdo DetalhessobreaNovíssimaAPI JCRea CriaçãodeRepositórios deConteúdonaWeb
Edição24-AnoIII-R$9,90
•
ConheçaduasAPIsdeimpressãodo J2SEeobtenhacontrolefino sobrelayouteformatação
•
Imprimindo com Java
- JSP 2 e Servlets no Tomcat 5 - Primeiros passos com wireless - Collections avançado - Conhecendo o JDeveloper 10g - Servlets: do básico ao avançado
Java 5 Impressão Java Content Repository Caches e JDBC NetBeans JavaOne
- JavaOne 2003 - Conhecendo o CVS - JSTL- Guia completo SQL e Format - Tomcat e o Servidor Apache
Uma Aplicação Java Completa com NetBeans
Partefinal–Acessoa Banco deDadoseGerênciade PreferênciasdeUsuários
Tira-Dúvidas Especial CacheseDAOscomJDBC, Usando JVMsAlternativas,Relatóriose DriblandoRestrições daMáquina
Java:
4 2 o ã ç i d E
Futuro
5 2 o ã ç i d E
- Novo NetBeans - Máquinas virtuais alternativas - Gráficos com JFreeChart - SuperWaba Inicial - Tag Files do JSP 2.0
4 3 o ã ç i
d E
- Aplicação Completa - Parte 1 - Desempenho com JDBC e DAOs - Portlets - Fundamentos - JFreeChart Avançado - O Novo Extreme P rogramming
5 3 o ã ç i
d E
- Para onde vai o Struts - Teste com J2MEUnit - Web Services - Scripting na JVM - Aspectos no Mundo Real
.br/anteriores jm39.indb 47
- Frameworks de Logging - Otimização de Código - Ajuda com JavaHelp - Maven 2 Essencial - Java ME no Eclipse
&
6 2 o ã ç i d E
Presente
7 2 JavaOne 2005 Grandesnovidadesem o produtoseAPIs,planose ã comemoraçõesno evento ç mundialda tecnologia Java i d E
- Aplicação Completa - Parte 2 - Mais Desempenho com JDBC - Portlets - Recursos Avançados - Jakarta Commons Inicial - Números: Conceitos e Formatação
6 3 o ã ç i
d E
- Conhecendo o Ant - Acessando Código Nativo - Qualidade Aplicada - Migrando para o Maven 2 - Logging no Java SE
Migrando para o Java 5 Conheça na prática eem detalhes como,porquee quando fazero upgradepara o J2SE 5.0
- Aplicação Completa - Parte 3 - Migrando para o Java 5 - Impress ão com Java - Gerenciamento de Conteúdo - JavaOne 2005
8 2 o ã ç i
d E
- Hibernate Fundamental - Apache Geronimo na Web - Ajax: Interatividade Turbinada - Tutorial de Genéricos - De Volta aos Patterns
Leia uma vez, use em todos os lugares
7 3 o ã ç i
d E
- Criação de Plug-ins - Os 10 Mais do Eclipse 3.2 - JavaOne 2006 - Relatórios Passo a Passo - Testes com mock objects
Edição 39 • Java Magazine 47
21 2283 9012 15/8/2006 18:18:46
AJAX Avançado com GWT
livre para eventuais mudanças enquanto os dados não chegam. Isso aumenta a performance da aplicação e faz com que se consiga paralelismo mesmo sem usar múltiplos threads – já que uma parte do código é executada no servidor e a outra, que trata dos eventos da interface gráfica, executa no cliente.
Para criar um serviço remoto no GWT temos que definir uma interface de serviço, na qual são escritos os métodos a serem expostos. A implementação dos métodos fica a cargo de uma classe que será executada no servidor como um servlet. Esta classe além de implementar a interface de serviço estende a classe RemoteServiceServlet
da API do GWT. Outra interface também deve ser criada: a interface assíncrona. Seus métodos serão os mesmos da interface de serviço, porém terão um parâmetro a mais: um objeto de callback, que receberá o retorno do serviço assim que ele for processado. Outra diferença é que todos os seus métodos retornam void.
Listagem 11.ContatosServiceAsync.java: interface assíncrona
Listagem 13.JMListaContatos.gwt.xml: arquivo de configuração do módulo GWT
package br.com.jm.gwt.client.service;
//… imports public interface ContatosServiceAsync { public void getContatos(int posicaoInicial, int nroRegistrosPagina, AsyncCallback callback); public void updateContato (Contato contato, AsyncCallback callback); }
Listagem 14.FonteDeDados.java Listagem 12.Implementa os serviços de acesso a dados – ContatosServiceImpl.java package br.com.jm.gwt.server;
//… imports public class ContatosServiceImpl extends RemoteServiceServlet implements ContatosService { public ContatosSer viceImpl() {} public List getContatos(int posicaoInicial, int nroRegistrosPagina) throws Exception { List temp = new ArrayList(); String sql = “select contato_id, nome, telefone, celular, email,” + “endereco from contato limit “ + posicaoInicial + “,” + nroRegistrosPagina); Connection conn = getConnection (); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); while (rs.next()) { temp.add(buildContato(rs)); } return temp;
}
public void updateContato (Contato contato) throws Exception { String sql = “update contato set nome = ?, telefone = ?, celular = ?, email = ? where contato_id = ?”; Connection conn = getConnection (); PreparedStat ement stmt = conn.prepare Statement(sql ); stmt.setStri ng(1, contato.getNo me()); stmt.setStri ng(2, contato.getTe lefone()); stmt.setStri ng(3, contato.getCe lular()); stmt.setStri ng(4, contato.getEm ail()); stmt.setLong (5, contato.getCo ntatoId()); stmt.execute(); } private Contato buildContato( ResultSet rs) throws Exception {...} public Connection getConnection () throws ClassNotFoundException, SQLException {...} }
public class FonteDeDados { private int nroRegistrosPagina; private TabelaEditavel tabelaEditabel; public FonteDeDados( int nroRegistros ) { this.nroReg istrosPagina = nroRegistros ; setPagina(0); } public void setPagina(in t pagina) { ContatosServiceAsync service = ContatosService.Util.getInstance(); service.getContatos(pagina * nroRegistrosPagina, nroRegistrosPagina, new ContatosCallBack()); } public void setTabelaEdi tabel(Tabela Editavel tabelaEditab el) { this.tabela Editabel = tabelaEditab el; } public void updateContat o(Contato contato) { ContatosServiceAsync service = ContatosService.Util.getInstance(); service.updateContato(contato, new UpdateCallBack()); } private class ContatosCallBack implements AsyncCallback { public void onFailure(Th rowable caught) { Window.alert(caught.getMessage()); } public void onSuccess(Object result) { tabelaEdita bel.atualizaD ados((List) result); } } private class UpdateCallBack implements AsyncCallback { public void onFailure(Th rowable caught) { Window.aler t(“Erro ao atualizar o banco de dados”); } public void onSuccess(Object result) {} } }
Listagem 15.Inici aliza os componentes do módulo – JMListaContatos.java public class JMListaContatos implements EntryPoint { public void onModuleLoad() { //lista os contatos com 15 registros por página RootPanel.g et(“contatos” ).add(new ListaContatos (15)); } }
48 Java Magazine • Edição 39 jm39.indb 48
15/8/2006 18:18:46
Criando nosso serviço Para criar nosso serviço utilizaremos o plug-in Googlipse que facilita a criação e sincroniza os métodos entre a interface de serviço e a interface assíncrona. Clique com o botão direito sobre o nome do pro jeto, siga New>Other>Googlipse e escolha GWT Remote Service. No campo Module, clique em Browse, navegue até o pacote br.com.jm.gwt e escolha o XML do nosso módulo; em Service Name, forneça ListaContatosService, digite contatosService em Service URI (veja Figura 4), e finalize. Serão geradas a interface de serviço ContatosService, a interface assíncrona ContatosServiceAsync e a classe de implementação ContatosServiceImpl . Definiremos na nossa interface de serviço dois métodos: getContatos() , para buscar a lista de contatos; e updateContato() , para atualizar o contato que foi editado. Veja as mudanças na Listagem 10. Ao salvar a interface alterada, você verá que o Googlipse atualiza a interface assíncrona criando dois métodos semelhantes aos de ContatosService (veja a Listagem 11). Na classe ContatosServiceImpl implementamos os métodos de serviço definidos em ContatosService , como mostrado na Listagem 12. Observe que fizemos acesso direto ao banco de dados; em uma aplicação real seria recomendado o uso do pattern DAO (por exemplo). Devemos também adicionar os serviços
criados ao arquivo de configuração do nosso módulo, br.com.jm.JMListaContatos. gwt.xml . Deixe esse arquivo como na Listagem 13.
Montando o quebra-cabeça Com os serviços e os componentes prontos, vamos agora uni-los. Chegou a hora de falar da fonte de dados, cujo código é mostrado na Listagem 14. A classe FonteDeDados é bem simples; ela contém duas inner classes, ContatosCallBack e UpdateCallBack . Ambas possuem dois métodos: onFailure() , para tratamento de erros na chamada do serviço, e onSuccess() , para receber o retorno. A classe ContatosCallBack será usada para receber o resultado da pesquisa de contatos e configurar a tabela com os dados retornados. A segunda não possui nenhuma implementação específica pois o serviço se trata de um update, não havendo neste exemplo retorno do servidor. Em ambas tratamos os erros de maneira simples, monstrando um PopUp com uma mensagem. Os métodos updateContato() e setPagina() fazem as chamadas de serviços. Vamos voltar agora à classe JMListaContatos , a classe que é o “ponto de entrada” (Entry Point) do módulo GWT. Altere esta classe e deixe-a como na Listagem 15. Em seu método onModuleLoad() buscamos um RootPanel com id “contatos”. Lembre que definimos este mesmo id no arquivo JMListaContatos.html eé justamente lá que colocaremos nossa lista de contatos. Então adicionamos um componente ListaContatos ao RootPanel. Com isso, assim que o arquivo HTML for exibido, o GWT iniciará o módulo e carregará nossa lista de contatos.
Visualizando o exemplo
Figura 5. Criando uma configuração para executar o exemplo
Para visualizar a aplicação de exemplo, abra o menu de configurações de execução (Run|Run). Na árvore
de configurações escolha a opção GWT Application e crie uma nova configuração. Preencha a tela como a Figura 5 e clique em Run. Sua aplicação será exibida e deverá estar semelhante à Figura 1.
Conclusões O GWT é uma excelente opção para tornar o desenvolvimento web mais produtivo na criação de aplicações AJAX. Alguns sites já se especializara m em componentes para GWT, o que pode salvar muito tempo de desenvolvimento (veja links). Há inclusive IDEs que já trazem suporte nativo ao GWT, como o IntelliJ e o VistaFei (este último já possui suporte a “arrastar-e-soltar” para os widgets). google.com/options
Links para os produtos do Google, como Google Maps. www.googlipse.com
Plugin do Eclipse para o GWT. eclipse.org/webtools
Plugin do Eclipse para desenvolvimento Web. intellij.net
IDE para desenvolvimento Java com suporte nativo ao GWT. www.wirelexsoft.com/VistaFei.html
IDE com desenvolvimento “arrastar-e-soltar” para o GWT. gwtpowered.org
Site com tutoriais, artigos e links para download de componentes GWT. java.sun.com/blueprints/corej2eepatterns/ Patterns/DataAccessObject.html
Artigo sobre o design pattern Data Access Object (DAO) javamagazine.com.br/downloads/jm39/ jm-componentesgwt.zip Ari Dias Neto
(
[email protected]) é consultor Java/J2EE com experiência de mais de sete anos em tecnogias para web. Atualmente atua em projeto internacional para IBM. Certificados: SCJP, SCWCD, EA(I) e Borland CaliberRM Certified.
Edição 39 • Java Magazine 49 jm39.indb 49
15/8/2006 18:18:49
Interfaces Gráficas co Parte 2: Presentation Model e a API Binding
N
a primeira parte desta série apresentamos técnicas básicas para um bom projeto visual de interfaces gráficas (GUIs), discutindo questões de organização e posicionamento de elementos que compõem as telas. Criamos também um exemplo demonstrando como a API Forms do JGoodies pode ajudar na construção de GUIs que utilizam componentes Swing. Nesta segunda e última parte, iremos nos aprofundar no código que está por trás da interface e que se comunica com o núcleo do software. Nossa discussão começa tratando de algumas questões que envolvem pro blem as e ne ce ss idades do desenvolvimento de GUIs. Apresentamos o pattern Presentation Model, como uma das estratégias mais eficazes atualmente para o desenvolvimento de aplicações desktop, e vemos seu funcionamento detalhado e a contribuição trazida pela API Binding do JGoodies. Ao longo de todo o processo,
50 Java Magazine • Edição 39 jm39.indb 50
15/8/2006 18:18:56
Descubra como aplicar o padrão Presentation Model e a API Binding do JGoodies para construir GUIs de alta produtividade que favorecem os testes unitários
m Qualidade do JGoodies
HUGO VIDAL TEIXEIRA construímos um exemplo que apresenta diversas características desejadas em aplicações modernas.
Patterns e testes em GUIs A alta produtividade no desenvolvimento de GUIs em aplicações desktop pode ser alcançada com a ajuda de dois elementos importantes no nível do código: a separação de interesses e a eficácia de testes unitários. Separação de interesses
A separação de interesses, no nosso caso, significa separar o estado e a lógica da GUI, dos componentes gráficos utilizados. Você provavelmente já deve ter ouvido falar em patterns que fazem essa separação, como o Model-View-Controller (MVC), que vem sendo usado tanto no desenvolvimento de aplicações desktop quanto web. Embora a separação de interesses traga muitas vantagens ao desenvolvimento, ela exige código específico para sincronizar as variáveis que guardam o estado da GUI com os componentes gráficos. Normalmente as telas recebem objetos de negócio (JavaBeans, por exemplo) do backend e precisam transferir seus dados para os componentes gráficos, e vice-versa. Para que essa conexão entre os dois lados possa ser programada sem muito esforço, precisamos da ajuda de um framework genérico que interligue objetos de negócio e componentes gráficos. Esse é papel da API Binding do JGoodies, que estudaremos nesse artigo. Eficácia de testes unitários
Outro problema que atinge o desenvolvimento de GUIs é a dificuldade de se construir testes unitários abrangentes e eficazes, que simulem as interações do
usuário com a GUI, reproduzindo cliques do mouse e eventos de teclado 1. Mas será que a separação de interesses não poderia ajudar nesses testes, já que a lógica está separada dos componentes gráficos? Para responder essa pergunta, vamos examinar a Figura 1, que apresenta os três principais patterns para o desenvolvimento de GUIs em aplicações desktop. Observe a estrutura do Mod el-Vie wPresenter (MVP) que é uma pequena variação do MVC. O importante a ser observado aqui é o fato de que tanto no MVC quanto no MVP, o controlador ( Controller/Presenter) possui uma referência para a visão ( View). Assim, para instanciar um controlador dentro de um teste unitário, precisamos da visão com seus componentes gráficos também. Isso faz com que o desenvolvimento de testes unitários ainda continue bastante difícil. Existe uma solução para contornar esse problema: adicionar uma interface entre o controlador e a visão. Mas não vale a pena explorar essa alternativa quando temos uma terceira opção como o Presentation Model, descrito a seguir.
Model – não possui uma referência para a classe View. Ocorre exatamente o contrário: é a classe View que guarda uma referência para a classe Presentation Model. Isso é extre-
mamente valioso na criação de testes. Estrutura e classes
Quando aplicamos o pattern Presentation Model no desenvolvimento de uma GUI, precisamos criar basicamente três classes: • View – Classe simples que contém os componentes gráficos da GUI. Os dados apresentados na tela refletem o estado mantido pela classe Presentation Model. • Presentation Model – Classe que contém o estado e a lógica da GUI. Os dados e variáveis são manipulados em memória, independentemente da existência de componentes e formas de apresentação. • Service Layer – Classe que oferece à classe Presentation Model uma comunicação com o mundo exterior, onde serviços externos podem existir (como EJBs, bancos de dados etc.) A separação em classes com diferentes responsabilidades traz vantagens
O pattern Presentation Model O pattern Presentation Model, também ilustrado na Figura 1, é bastante diferente dos outros dois porque quem detém a lógica e o estado da GUI – a classe Presentation Model-View-Controller
Model-View-Presenter
Model
View
1 Um dos fatores que torna isso muito difícil é a existência de somente uma thread para executar o código do teste e o código que renderiza os componentes na tela. A thread única é um requisito imutável de APIs como Swing e AWT.
Model
Controller
View
Referêcia Notificação por Eventos
Presentation Model View
Presenter
Presentation Model
Service Layer
Figura 1. Estrutura dos principais patterns para GUIs desktop
Edição 39 • Java Magazine 51 jm39.indb 51
15/8/2006 18:19:00
Interfaces Gráficas com Qualidade – Parte 2
importantes para a implementação e a manutenção da GUI. Quando trabalhamos assim, as classes ficam mais “finas” e consequentemente mais simples, o que facilita o entendimento do código e sua manutenção. Na verdade, essas vantagens também existem no MVC e no MVP. A grande vantagem no Presentation Model é que não precisamos da classe View para testar a lógica da GUI. Isso permite a criação de testes unitários simples, eficientes e sem a presença de componentes gráficos para atrapalhar.
A API JGoodies Binding A API Binding do JGoodies oferece um conjunto de classes que simplifica muito a aplicação do pattern Presentation Model. Essa API foi desenvolvida para trabalhar com o JFC/Swing e possui mecanismos para conectar componentes gráficos a variáveis que podem ser manipuladas independentemente de suas formas de apresentação. Para entender essa idéia, imagine, por exemplo, uma variável booleana que está conectada a um JCheckBox (Figura 2). Sempre que trocarmos o valor dessa variável, o JCheckBox irá refletir a mudança e vice-versa. Ao mesmo tempo, poderíamos conectar outros componentes gráficos a essa mesma variável, por exemplo um JToggleButton . Assim, podemos ter quantos componentes quisermos ligados a uma mesma variável, sendo que a mudança feita em um dos componentes é refletida nos outros. Uma variável pode conter tanto um valor simples quanto uma lista de objetos. Vamos analisar cada caso separadamente. Conectando componentes a valores simples
Entre os componentes que podem ser conectados a um valor simples, os principais são JLabel, JTextField, JTextArea, JCheckBox,
JRadioButton, JPasswordField e JFormattedTextField.
A variável que pode ser conectada a eles é uma implementação da interface ValueModel (pacote com.jgoodies.binding.value ), que armazena um valor e o mantém sincronizado com o que é apresentado pelo componente. Ao mudar o valor do ValueModel, o componente refletirá essa mudança. Da mesma forma, se o usuário editar esse valor pela GUI, o ValueModel receberá o valor fornecido. Existem duas formas de conectar um ValueModel a um componente. Uma opção é utilizar o método estático bind () da classe Bindings (do pacote com.jgoodies.binding.adapter ): JTextField textField = new JTextField(); ValueModel valueModel = new ValueHolder(); Bindings.bind(textField, valueModel);
Repare que criamos um ValueModel instanciando a classe ValueHolder (do mesmo pacote de ValueModel ), que é a implementação que precisamos para esse caso. A segunda maneira de fazer a conexão é utilizando uma classe de conveniência chamada BasicComponentFactory (também de com.jgoodies.binding.adapter), que possui métodos prontos para criar componentes. Assim, podemos fazer: ValueModel valueModel = new ValueHolder(); JTextField textField = BasicComponentFactory.createTextField(valueModel);
Essa segunda alternativa será utilizada pelo nosso exemplo por ser mais simples. Duas questões importantes ainda precisam ser discutidas. Primeiro, o valor mantido pelo ValueModel pode ser lido e alterado através dos métodos getValue () e setValue (). É importante lembrar que para conectar um componente a um ValueModel, o componente precisa conhecer o valor guardado por ele. Portanto, um JCheckBox pode se conectar sem problemas a um ValueModel
View JCheckBox
JToggleButton
JCheckBox
JToggleButton
Presentation Model
que guarda um valor booleano, mas não a um que contém um String, por exemplo. Se o ValueModel receber um valor incompatível com o valor esperado pelos componentes conectados a ele, uma exceção será lançada durante a tentativa de conversão. A outra questão é a emissão de eventos PropertyChangeEvent, que acontece quando é alterado o valor guardado pelo ValueModel . Isso permite que o valor seja “observado” com PropertyChangeListeners. Demonstraremos esse recurso na prática mais adiante. Conectando componentes a listas de objetos
Os componentes que podem ser conectados a listas de objetos são JComboBox, JList e JTable. A lista de objetos, por sua vez, é representada por um objeto da interface javax.swing.ListModel do Swing, que define métodos para a manipulação da lista e emite eventos quando ela for modif icada. A contribuição do JGoodies Binding foi criar implementações que combinassem a interface ListModel com a simplicidade da interface java.uti l.List , resultando em duas classes: ArrayListModel (que herda de ArrayList e implementa ListModel ) e LinkedListModel (que herda de LinkedList e implementa ListModel). Dessa forma, podemos atuar sobre essas classes dentro da classe Presentation Model, enquanto os componentes gráficos refletem as mudanças na classe View. Entretanto, ainda existe um problema. Sabemos que os componentes desta categoria guardam, além da lista de objetos, um item selecionado 2. Portanto, o ListModel sozinho não resolve nosso problema. Precisamos da ajuda da classe SelectionInList (em com.jgoodies.binding.list ), que guarda um ListModel e também contém um ValueModel que aponta para o item selecionado. Veja um exemplo: ListModel listModel = new ArrayListModel(); ValueModel itemSelecionadoHolder = new ValueHolder(); SelectionInList selectionInList = new Selecti onInList(listModel, itemSelecionadoHolder);
true Variável Booleana
Com isso, podemos conectar o objeto
false
Figura 2. Variável booleana conectada a um JCheckBox e a um JToggleButton.
2 Os casos em que há mais de um item selecionado são normalmente raros, e por isso não serão tratados neste artigo.
52 Java Magazine • Edição 39 jm39.indb 52
15/8/2006 18:19:00
SelectionInList aos componentes da View da
A classe Usuario
seguinte forma:
pois podemos usar a fábrica de componentes da API Binding. Para a classe JTable, precisaríamos implementar um TableModel que defina as colunas e exiba os objetos do ListModel . Esse caso não será tratado neste artigo, mas há exemplos dele dentro do ZIP de distribuição da API Binding.
Começaremos construindo o exemplo pela classe Usuario, mostrada na Listagem 1. Para essa classe JavaBean, o ponto mais importante a destacar é a emissão de eventos, que é realizada quando os dados do usuário são alterados. Precisamos implementar isso porque as classes da API Binding exigem que os objetos manipulados sejam observáveis3. Assim sendo, o primeiro passo é fazer a classe Usuario herdar da classe Model (pacote com.jgoodies.binding.beans ); em seguida alteramos todos os métodos setXxx() para que chamem firePropertyChange() e, dessa forma, avisem que a propriedade em questão mudou seu valor.
A aplicação de exemplo
A classe Presentation Model
JComboBox comboBox = new JComboBox(new ComboBoxAdapter(selectionInList)); JList list = BasicComponentFactory.createList(selectionInList);
Repare que precisamos da classe ComboBoxAdapter (pacote com.jgoodies.binding. adapter ) para conectar o JComb oBo x ao SelectionInList . O caso do JList é mais simples,
Apresentaremos a partir de agora um exemplo de uso do pattern Presentation Model com a API JGoodies Binding. O código completo está disponível para download no site da Java Magazine (para rodar o exemplo, basta executar a classe Programa ). Além da API Binding você vai precisar das APIs Forms e Looks do JGoodies. Para obtê-las, no site do projeto ( jgoodies.com) vá até a seção Download e clique em Libraries. Lá você terá acesso aos arquivos zipados de todas as APIs, contendo exemplos, documentação, código-fonte e os JARs que devem ser incluídos no classpath da sua aplicação. Apresentando a aplicação
O exemplo que vamos construir é um cadastro de usuários conforme apresentado na Figura 3. É mostrada uma lista contendo todos os usuários cadastrados; as informações do usuário selecionado são exibidas à direita. Cada usuário possui quatro atributos: o nome, o cargo, e dois indicadores definindo se é administrador e se tem acesso ao controle de permissões da aplicação. O acesso ao controle de permissões só é possível para administradores. Um título no lado direito apresenta qual usuário estamos editando. Se nenhum estiver selecionado, é apresentado o texto “Nenhum usuário selecionado” e todos os componentes de edição ficam desabilitados.
Ainda no construtor, repare a chamada de dois métodos: iniciaModelos() e iniciaLogicaApresentacao(). É nesses métodos que o estado e a lógica da GUI começam a surgir. Para entender o código, acompanhe a Figura 4 e veja como a API Binding está sendo usada. A Tabela 1 apresenta um resumo dos objetos citados a seguir. O método iniciaModelos() começa criando o objeto usuarioSelectionInList, a partir de um ListModel (usuarioListModel) e um ValueModel (usuarioSelecionadoHolder ): usuarioListModel = new ArrayListModel(); usuarioSelecionadoHolder = new ValueHolder(null, true); usuarioSelectionInList = new SelectionInList((ListModel) usuarioListModel, usuarioSelecionadoHolder);
Embora ainda não tenhamos explicado a
Basicamente, um objeto é observável quando mudanA segunda classe que vamos desenvolver ças nele emitem eventos. O objeto que escuta esses evenchama-se CadastroUsuarioPresentationModel tos chama-se observador . Esses são conceitos trazidos pelo pattern Observer do livro “Design Patterns” [Gamma et al.]. (Listagem 2). Conforme vimos na Figura 1, essa classe deve possuir uma referência para a classe Service Layer, que é o único meio de comunicação da GUI com o resto do software. Assim, o construtor da classe recebe e guarda um objeto do tipo CadastroUsuarioServiceLayer, que é explicado mais adiante. Figura 3. Tela de cadastro de usuários. 3
Listagem 1.Classe de modelo Usuario. import com.jgoodies.binding.beans.Model; public class Usuario extends public static final String public static final String public static final String public static final String private private private private
Model { PROPERTY_NOME = “nome”; PROPERTY_CARG O = “cargo”; PROPERTY_ADMI NISTRADOR = “administrado r”; PROPERTY_CONTROLE_PERMISSAO = “controlePermissao”;
String nome; String cargo; boolean isAdministr ador; boolean isControleP ermissao;
public String getNome() { return nome; } public void setNome(Str ing nome) { String valorAntigo = this.nome; this.nome = nome; // Todos os metodos set lançam eventos... this.fireProp ertyChange(P ROPERTY_NOME, valorAntigo , this.nome); } /*...Os outros metodos get/set são similares */ }
Edição 39 • Java Magazine 53 jm39.indb 53
15/8/2006 18:19:01
Interfaces Gráficas com Qualidade – Parte 2
Listagem 2.Classe CadastroUsuarioPresentationModel, onde fica o estado e a lógica da GUI. import import import import import import import
com.jgoodies.binding.beans.*; com.jgoodies.binding.list.*; com.jgoodies.binding.value.*; javax.swing.*; java.awt.event.ActionEvent; java.beans.*; java.util.*;
public class CadastroUsuarioPresentationModel extends Model { public static final String TITULO_SEM_SELECAO = “Nenhum usuário selecionado.”; public static final String TITULO_COM_SELECAO = “Detalhes de “; private private private private private private private private private private private private
CadastroUsuarioServiceLayer serviceLayer; ArrayListModel usuarioListModel; BeanAdapter usuarioSelecionadoBeanAdapter; SelectionInList usuarioSelectionInList; SelectionInList cargoSelectionInList; ValueHolder usuarioSelecionadoHolder; ValueHolder tituloHolder; ValueHolder usuarioDisponivelHolder; ValueHolder campoPermissaoDisponivelHolder; Action adicionarAction; Action removerAction; Action salvarAction;
public CadastroUsuarioPresentationModel( CadastroUsuarioServiceLayer serviceLayer) { this.serviceLayer = serviceLayer; iniciaModelos(); iniciaLogicaApresentacao(); } private void iniciaModelos() { usuarioListModel = new ArrayListModel(); usuarioSelecionadoHolder = new ValueHolder(null, true); usuarioSelectionInList = new SelectionInList( (ListModel) usuarioListModel, usuarioSelecionadoHolder); usuarioSelecionadoBeanAdapter = new BeanAdapter( usuarioSelecionadoHolder, true); ArrayListModel cargoListModel = new ArrayListModel( Arrays.asList(Cargos.TODOS)); cargoSelectionInList = new SelectionInList( (ListModel) cargoListModel, getCargoHolder());
tituloHolder = new ValueHolder(); usuarioDisponivelHolder = new ValueHolder(); campoPermissaoDisponivelHolder = new ValueHolder();
Usuario novoUsuario = new Usuario(“Novo Usuário”, null); usuarioListModel.add(novoUsuario);
}
private void removeUsuario() { int indice = usuarioSelectionInList.getSelectionIndex(); if (indice != -1) { usuarioListModel.remove(indice); } } private void salvaUsuarios() { List lista = Collections.unmodifiableList(usuarioListModel); serviceLayer.salvaUsuarios(lista); } private void habilitaCampos() { boolean enabled = usuarioSelecionadoHolder.getValue() != null; usuarioDisponivelHolder.setValue(enabled); habilitaCampoControlePermissao(); } private void habilitaCampoControlePermissao() { Usuario selecionado = ( Usuario) usuarioSelecionadoHolder.getValue(); boolean enabled = selecionado != null && selecionado.isAdministrador(); campoPermissaoDisponivelHolder.setValue(enabled); } /* Inner classes de ações */ private class AdicionarAction extends AbstractAction { public void actionPerformed(ActionEvent e) { adicionaUsuario(); } } private class RemoverAction extends AbstractAction { public void actionPerformed(ActionEvent e) { removeUsuario(); } } private class SalvarAction extends AbstractAction { public void actionPerformed(ActionEvent e) { salvaUsuarios(); } }
adicionarAction = new AdicionarAction(); removerAction = new RemoverAction(); salvarAction = new SalvarAction();
/* Inner classes de observadores */
} private void iniciaLogicaApresentacao() { ObservadorSelecaoUsuario observadorSelecao = new ObservadorSelecaoUsuario(); usuarioSelecionadoHolder.addValueChangeListener( observadorSelecao); observadorSelecao.propertyChange(null);
ObservadorNomeUsuario observadorNome = new ObservadorNomeUsuario(); this.getNomeHolder().addValueChangeListener(observadorNome); observadorNome.propertyChange(null);
ObservadorCampoAdmin observadorAdmin = new ObservadorCampoAdmin(); this.getAdminHolder().addValueChangeListener( observadorAdmin); observadorAdmin.propertyChange(null);
PropertyConnector connector = new PropertyConnector( usuarioDisponivelHolder, “value”, removerAction, “ enabled”); connector.updateProperty2();
List usuarios = serviceLayer.buscaUsuarios(); usuarioListModel.addAll(usuarios); } /* Metodos get omitidos */
private class ObservadorNomeUsuario implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { if (getUsuarioSelecionadoHolder().getValue() == null) { getTituloHolder().setValue(TITULO_SEM_SELECAO); } else { getTituloHolder().setValue(TITULO_COM_SELECAO + getNomeHolder().getValue()); } // Emite um evento de mudança para que a GUI reflita o novo // nome. usuarioListModel.fireContentsChanged( usuarioSelectionInList.getSelectionIndex()); } }
private class ObservadorSelecaoUsuario implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { habilitaCampos(); } } private class ObservadorCampoAdmin implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { habilitaCampoControlePermissao(); } } }
private void adicionaUsuario() {
54 Java Magazine • Edição 39 jm39.indb 54
15/8/2006 18:19:04
Eventos Java Agende-se!
O Maior Evento Java do Brasil 30/Nov a 2/Dez São Paulo - SP
De Competições a Workshops Outubro Várias Cidades E para o primeiro semestre de
A Conferência Internacional do SouJava
A Tecnologia Java, Livre
2007
Java e o Governo Brasileiro Edição 39 • Java Magazine 55 www.soujava.org.br
jm39.indb 55
15/8/2006 18:19:11
Interfaces Gráficas com Qualidade – Parte 2
classe View do exemplo, podemos adiantar que o objeto usuarioSelectionInList conecta-se ao JList que apresenta os usuários na tela (usuariosList). O próximo objeto a ser criado chama-se usuarioSelecionadoBeanAdapter : usuarioSelecionadoBeanAdapter = new BeanAdapter(usuarioSelecionadoHolder, true); CadastroUsuarioView
Para que você entenda o significado desse objeto, considere a nossa situação: temos uma lista de objetos JavaBeans (usuários) e precisamos amarrar as propriedades do JavaBean que está selecionado aos componentes da tela: nome, cargo, administrador e controle de permissões. Para não termos que declarar um ValueModel para cada propriedade desse JavaBean e ficar CadastroUsuarioPresentationModel usuarioSelectionInList
usuarioListModel usuarioSelecionadoHolder usuarioSelecionadoBeanAdapter nomeHolder
cargoSelectionInList
cargoHolder
cargoListModel
administradorHolder controlePermissaoHolder
Figura 4. Conexão entre os modelos da classe Presentation Model com os componentes da classe View .
Modelo
Classe
Descrição
usuarioListModel
ArrayListModel
Lista observável que guarda todos os usuários.
usuarioSelecionadoHolder
ValueModel
Mantém uma referência para o usuário selecionado na lista.
usuarioSelectionInList
SelectionInList
Une o usuarioListModel e o usuarioSelecionadoHolder em um mesmo objeto.
usuarioSelecionadoBeanAdapter
BeanAdapter
Fornece um ValueModel para cada propriedade do usuário selecionado.
gerenciando as mudanças de valores quando a seleção na lista de usuários mudar, podemos usar a classe BeanAdapter. A classe BeanAdapter funciona como uma “caixa”, na qual podemos colocar um objeto JavaBean. Essa classe fornece um ValueModel para cada propriedade do JavaBean – justamente o que precisamos. Assim, quando mudamos o usuário selecionado na lista, os ValueModel s fornecidos pelo BeanAdapter irão refletir os valores do novo usuário. Repare que passamos o objeto usuarioSelecionadoHolder como parâmetro na criação do BeanAdapter . Isso faz com que o BeanAdapter sempre exiba os dados do usuário selecionado. Observe ainda o valor true como segundo parâmetro do construtor, o que indica que as mudanças no JavaBean precisam ser observadas. No nosso caso, isso é importante porque temos que atualizar certas partes da GUI quando os valores do usuário mudarem (ex.: para habilitar/desabilitar o JCheckBox “Controle de Permissões” quando a propriedade Administrador for alterada). Por fim, para adquirir os ValueModels fornecidos pelo BeanAdapter, utilizamos o método getValueModel() , passando o nome da propriedade em que estamos interessados. Assim, para conectar o
ValueModels (fornecidos pelo usuarioSelecionadoBeanAdapter) que
nomeHolder, cargoHolder, administradorHolder, controlePermissaoHolder
ValueModel
cargoListModel
ArrayListModel
Lista observável com os possíveis cargos de um usuário.
cargoSelectionInList
SelectionInList
Une o cargoHolder e o cargoListModel em um mesmo objeto.
usuarioDisponivelHolder
ValueModel
Contém um booleano que indica se existe um usuário disponível para edição.
campoPermissaoDisponivelHolder
ValueModel
Contém um booleano que indica se o campo “Controle de Permissões” deve estar habilitado ou não.
tituloHolder
ValueModel
Contém o texto que é apresentado no topo da tela.
contém, para o usuário selecionado, o nome, o cargo, um booleano indicando se é um administrador, e um booleano indicando se pode controlar as permissões do sistema.
Tabela 1. Resumo dos objetos mantidos pela classe CadastroUsuarioPresentationModel .
56 Java Magazine • Edição 39 jm39.indb 56
15/8/2006 18:19:14
JTextField nomeTextField à propriedade Nome do usuário selecionado na lista, a classe View utiliza o método getNomeHolder() da classe CadastroUsuarioPresentationModel : // Definido na classe CadastroUsuarioPresentationModel public ValueModel getNomeHolder() { return usuarioSelecionadoBeanAdapter.getValueModel( Usuario.PROPERTY_NOME); } // Na classe View… JComponent nomeTextField = BasicComponentFactory. createTextField( pm.getNomeHolder(), false);
O parâmetro false passado para o método createTextField() significa que o valor editado pelo usuário será transferido ao ValueModel a cada tecla pressionada. Se o parâmetro fosse true, o valor só seria transferido quando o componente JTextField perdesse o foco. O caso do JComboBox de cargos segue um estilo diferente, porque é um com-
ponente que contém uma lista de objetos e uma seleção (da mesma forma que o JList de usuários). Portanto precisamos criar o objeto cargoSelectionInList a partir do cargoListModel e do ValueModel que aponta para o cargo selecionado (o cargoListModel é um ListModel preenchido com uma lista fornecida pela classe Cargos4 ): // Na classe CadastroUsuarioPresentationModel... ArrayListModel cargoListModel = new ArrayListModel( Arrays.asList(Cargos.TODOS)); cargoSelectionInList = new SelectionInList( (ListModel) cargoListModel, getCargoHolder()); // Na classe View... JComboBox cargoComboBox = new JComboBox( new ComboBoxAdapter(pm.getCargoSelectionInList()));
Continuando no método iniciaModelos() , vemos a criação do objeto tituloHolder, que guarda o texto apresentado no topo da tela, informando o usuário sendo editado. Depois temos a criação de dois
outros ValueHolder s: usuarioDisponivelHolder e campoPermissaoDisponivelHolder . O primeiro guarda um booleano que indica se existe um usuário disponível para edição, e o segundo contém outro booleano que indica se o JCheckBox “Controle de Permissões” está habilitado. Esses valores são manipulados pelos observadores descritos mais adiante. Ainda nesse método temos a criação de três ações (objetos que implementam a interface javax.swing.Action ): adicionarAction, removerAction e salvarAction. Essas ações são usadas pela classe View para criar os botões da tela (com exceção do botão “Fechar” que é implementado na própria classe View por não possuir lógica de negócio). A implementação dessas ações é bem simples, por exemplo: A classe Cargos, não listada, é bem simples e define apenas um array de strings. 4
Edição 39 • Java Magazine 57 jm39.indb 57
15/8/2006 18:19:17
Interfaces Gráficas com Qualidade – Parte 2
Classe
Interface Implementada
Descrição
Obser va dorSelecaoUsua rio
Prope rt yChangeLis tener
Observa o usuarioSelecionadoHolder: qualquer mudança de seleção na lista de usuários resulta numa chamada ao método habilitaCampos() .
ObservadorNomeUsuario
PropertyChangeListener
Observa as mudanças no nome do usuário selecionado e preenche o valor dotituloHolder.
ObservadorCampoAdmin
PropertyChangeListener
Observa a propriedade Administrador do usuário selecionado para inferir se o campo “Controle de Permissões” deve estar habilitado (chama o métodohabilitaCampoControlePermissao() )
Tabela 2. Observadores definidos na classe CadastroUsuarioPresentationModel. Listagem 3.CadastroUsuarioServiceLayer: classe Service Layer com dados fixos de teste. import java.util.*;
usuarioDisponivelHolder nos indica se há um
usuário disponível para edição. Só se houver, habilitamos a remoção.
public class CadastroUsuarioServiceLayer { public List buscaUsuarios () { // Retorna dados fixos para nosso exemplo... // O primeiro usuário é um administrador (true no terceiro parâmetro) ArrayList usuarios = new ArrayList(); usuarios.add (new Usuario(“Deo doro da Fonseca”, Cargos.GERE NTE_PROJETO, true)); usuarios.add (new Usuario(“Flo riano Peixoto”, Cargos.DESE NVOLVEDOR)); usuarios.add (new Usuario(“Pru dente de Moraes”, Cargos.LIDE R_EQUIPE)); usuarios.add (new Usuario(“Cam pos Salles”, Cargos.SUPOR TE)); return usuarios; } public void salvaUsuarios (List listaUsuarios ) { // Aqui você coloca seu código para salvar na base de dados. } }
private class AdicionarAction extends AbstractAction { public void actionPerformed(ActionEvent e) { adicionaUsuario(); } }
E para criar um botão a partir de uma ação, basta passar uma instância da ação ao construtor do JButton : JButton botaoAdicionar = new JButton(pm.getAdicionarAction());
Para fazer o ObservadorSelecaoUsuario observar o objeto usuarioSelecionadoHolder, devemos adicioná-lo como sendo seu listener: ObservadorSelecaoUsuario observadorSelecao = new ObservadorSelecaoUsuario(); usuarioSelecionadoHolder. addValueChangeListener(observadorSelecao);
Assim, quando o valor guardado pelo usuarioSelecionadoHolder mudar, o método habilitaCampos() será invocado.
Agora que os modelos estão criados e conectados, o método iniciaLogicaApresentacao() (ainda na Listagem 2) adiciona mais vida à GUI. Começamos com a criação de objetos “observadores” – que implementam a interface PropertyChangeListener. A Tabela 2 explica os três observadores presentes no final da classe Presentation Model. A implementação deles é bastante simples e segue este estilo:
Logo após criar e configurar os observadores, utilizamos a classe PropertyConnector para habilitar/desabilitar a ação removerAction. Isso é feito conectando-se a propriedade value do objeto usuarioDisponivelHolder com a propriedade enabled da ação:
private class ObservadorSelecaoUsuario implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { habilitaCampos(); } }
Assim, sempre que o valor booleano em usuarioDisponivelHolder mudar, o PropertyConnector vai transmitir esse valor ao enabled da ação (via setEnabled()). Fazemos isso porque o valor mantido pelo
A classe Service Layer A classe CadastroUsuarioServiceLayer, mostrada na Listagem 3, é a fronteira
através da qual passam os dados do exemplo. Temos dois métodos: buscaUsuarios() e salvaUsuarios(). Para o nosso caso, criamos o método buscaUsuarios() , que retorna um conjunto fixo de objetos Usuario para que a GUI possa ser testada sem depender de qualquer outra coisa. Em sistemas mais robustos, recomendo a adoção de duas práticas importantes. A primeira é criar uma interface para a Service Layer que resume todos os métodos de manipulação de dados. A idéia é que haja duas implementações para ela: uma de teste e uma real. A implementação de teste é igual à nossa, onde dados fixos ajudam no desenvolvimento e nos testes. A implementação real é a que acessa verdadeiramente o backend do sistema (bancos de dados, EJBs,
PropertyConnector connector = new PropertyConnector( usuarioDis ponivelHold er, “value”, removerAction, “enabled”) ;
58 Java Magazine • Edição 39 jm39.indb 58
15/8/2006 18:19:19
etc). Recomendo manter ambas as implementações juntas no código e utilizar uma ou outra conforme o objetivo. A segunda prática recomendada é criar, dentro da classe Service Layer, uma outra thread para comunicação com o restante do sistema. Normalmente, chamadas ao backend demoram alguns segundos; assim disparar uma nova thread para processar as chamadas evita o travamento da GUI e possíveis irritações por parte dos usuários. A classe View A classe CadastroUsuarioView (Listagem 4)
contém os componentes gráficos da nossa GUI. Ela possui uma referência para
a classe CadastroUsuarioPresentationModel , que é esperada em seu construtor. A montagem da tela é feita dentro do método createViewComponent() , onde os componente gráficos são instanciados e conectados aos modelos mantidos pela classe CadastroUsuarioPresentationModel, conforme foi visto na Figura 4. Repare que não há necessidade de manter referências para os componentes criados, já que os valores mostrados neles vêm dos modelos com os quais estão conectados. Além disso, o método conectaHabilitacao() estabelece uma ligação entre o valor mantido pelo usuarioDisponivelHolder e a propriedade enabled dos componentes (da mesma forma
que fizemos com a removerAction dentro do método iniciaLogicaApresentacao()). No caso do controlePermissaoCheckBox, amarramos o enabled ao valor do controlePermissaoHolder. Dessa forma, a lógica que controla a habilitação desses componentes fica inteiramente dentro da classe Presentation Model. Como a classe View é a responsável pela apresentação visual da GUI, é nela que colocamos os textos e ícones da tela: veja os métodos criaBarraFerramentas() e montaBarraBotoes() . Poderíamos ter colocado essas informações dentro das ações, mas estaríamos introduzindo in formações visuais em classes que não deveriam ter esse conhecimento.
Listagem 4.CadastroUsuarioView: classe View com os componentes gráficos. import import import import import import import import import import
com.jgoodies.binding.adapter.*; com.jgoodies.binding.beans.PropertyConnector; com.jgoodies.forms.builder.DefaultFormBuilder; com.jgoodies.forms.factories.ButtonBarFactory; com.jgoodies.forms.layout.*; javax.swing.*; java.awt.*; java.awt.event.ActionEvent; java.io.File; java.net.URL;
public class CadastroUsuarioView { private CadastroUsuarioPresentationModel pm; private JFrame frame;
private void conectaHabilitacao(JComponent component) { PropertyConnector connector = new PropertyConnector( pm.getUsuarioDisponivelHolder( ), “value”, component, “enabled”); connector.updateProperty2(); } private Component criaBarraFerramentas() { JButton botaoAdicionar = new JButton(pm.getAdicionarAction()); botaoAdicionar.setIcon(getIcon(“add.png”)); JButton botaoRemover = new JButton(pm.getRemoverAction()); botaoRemover.setIcon(getIcon(“remove.png”));
/* Aqui a toolbar é montada e retornada */ public CadastroUsuarioView(CadastroUsuarioPresentationModel pm) { this.pm = pm; } protected Component createViewComponent() { JList usuariosList = BasicComponentFactory.createList( pm.getUsuarioSelectionInList());
} private Component montaBarraBotoes() { JButton botaoSalvar = new JButton(pm.getSalvarAction()); botaoSalvar.setText(“Salvar”); JButton botaoCancelar = new JButton(new FecharAction()); botaoCancelar.setText(“Fechar”);
JLabel tituloLabel = BasicComponentFactory.createLabel( pm.getTituloHolder()); tituloLabel.setFont(tituloLabel.getFont().deriveFont( Font.BOLD, 14f));
JComponent nomeTextField = BasicComponentFactory .createTextField(pm.getNomeHolder(), false); conectaHabilitacao(nomeTextField);
JComboBox cargoComboBox = new JComboBox( new ComboBoxAdapter(pm.getCargoSelectionInList())); conectaHabilitacao(cargoComboBox);
JCheckBox administradorCheckBox = BasicComponentFactory.createCheckBox( pm.getAdminHolder(), “Administrador”); conectaHabilitacao(administradorCheckBox);
JCheckBox controlePermissaoCheckBox = BasicComponentFactory.createCheckBox( pm.getControlePermissaoHolder(), “Controle de Permissões”); PropertyConnector connector = new PropertyConnector( pm.getCampoPermissaoDisponivelHolder(), “value”, controlePermissaoCheckBox, “enabled”); connector.updateProperty2(); /* Aqui são posicionados os componentes no painel... */ }
return ButtonBarFactory.buildCenteredBar( botaoSalvar, botaoCancelar); }
public JFrame criaFrame() { if (frame == null) { frame = new JFrame(“Exemplo do Padrão Presentation Model”); frame.getContentPane().add(createViewComponent()); frame.pack(); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } return frame; } private Icon getIcon(String fileName) { String path = “images” + File.separator + fileName; URL url = CadastroUsuarioView.class.getResource(path); return new ImageIcon(url); }
private class FecharAction extends AbstractAction { public void actionPerformed(ActionEvent e) { frame.hide(); System.exit(0); } } }
Edição 39 • Java Magazine 59 jm39.indb 59
15/8/2006 18:19:20
Interfaces Gráficas com Qualidade – Parte 2
Desenvolvendo testes unitários sobre a GUI Como vimos no início do artigo, uma das maiores vantagens do pattern Presentation Model é a facilidade de criação de testes unitários sobre a GUI. Para ilustrar isso, criamos diversos testes na classe CadastroUsuarioPresentationModelTest (Listagem 5), utilizando o JUnit. Essa classe cria no método setUp() uma instância da classe CadastroUsuarioPresentationModel e outra de CadastroUsuarioServiceLayer , que contém nossos dados de teste. Conforme dito antes, não precisamos da classe View para testar a lógica da GUI. O nosso primeiro teste é feito pelo
método testAdicionarUsuario() , que mede o tamanho da lista de usuários antes e depois de executar a ação adicionarAction da classe CadastroUsuarioPresentationModel. O tamanho final da lista deve ser o tamanho inicial mais uma unidade. Da mesma forma, temos o teste testRemoverUsuario() , que começa selecionando o primeiro usuário (índice zero) e depois executa a ação removerAction . O objetivo do terceiro teste – testHabilitacaoControles() – é verificar se a habilitação dos controles está funcionando corretamente. Para isso, limpamos a seleção da lista de usuários com o método clearSelection() e depois verificamos se o usuarioDisponivelHolder e o
Listagem 5.CadastroUsuarioPresentationModelTest.java: Testes sobre a lógica da GUI. import junit.framework.TestCase; public class CadastroUsuarioPresentationModelTest extends TestCase { private CadastroUsuar ioServiceLay er serviceLayer ; private CadastroUsuar ioPresentati onModel pm; protected void setUp() throws Exception { serviceLayer = new CadastroUsua rioServiceLay er(); pm = new CadastroUsuarioPresentationModel(serviceLayer); } public void testAdicionaU suario() { int tamanhoInici al = pm.getUsuari oListModel(). getSize(); pm.getAdicionarAction().actionPerformed(null); int tamanhoFinal = pm.getUsuari oListModel(). getSize(); assertEquals(“Deveria ter mais um usuário na lista”, tamanhoInicial + 1, tamanhoFinal); } public void testRemoveUsu ario() { pm.getUsuarioSelectionInList().setSelectionIndex(0); int tamanhoInici al = pm.getUsuari oListModel(). getSize(); pm.getRemoverAction().actionPerformed(null); int tamanhoFinal = pm.getUsuari oListModel(). getSize(); assertEquals (“Deveria ter menos um usuário na lista”, tamanhoInicial - 1, tamanhoFinal); }
public void testHabilitac aoControles( ) { pm.getUsuarioSelectionInList().clearSelection(); assertEquals (“Os controles devem estar desabilitad os”, pm.getUsuarioDisponivelHolder().getValue(), Boolean.FALSE); assertEquals (“O campo ‘controle de permissões’ deve estar desabilitado” , pm.getCampoPermissaoDisponivelHolder().getValue(), Boolean.FALSE); assertFalse( “A ação de remover deve estar desabilitada ”, pm.getRemoverAction().isEnabled());
pm.getUsuarioSelectionInList().setSelectionIndex(0); assertEquals (“Os controles devem estar habilitados ”, pm.getUsuarioDisponivelHolder().getValue(), Boolean.TRUE); assertEquals (“O campo ‘controle de permissões’ deve estar habilitado”, pm.getCampoPermissaoDisponivelHolder().getValue(), Boolean.TRUE); assertTrue(“A ação de remover deve estar habilitada”, pm.getRemoverAction().isEnabled()); } public void testHabilitac aoCampoPermi ssao() {...} public void testLabelTitu lo() {...} public void testConexaoNo meTitulo() {...}
}
campoPermissaoDisponivelHolder contêm o valor false. Ainda nesse caso, a ação removerAction
deve estar desabilitada. Depois disso, selecionamos o primeiro usuário da lista e checamos se tudo está habilitado (considerando que o primeiro usuário é um administrador). Outros três testes foram omitidos da listagem, mas seus códigos estão bem documentados na versão completa disponível no site da Java Magazine.
Conclusões Apresentamos nesse artigo a combinação do padrão Presentation Model com a API Binding do JGoodies, que nos permite construir GUIs pela simples conexão de modelos e propriedades. Dentre as vantagens dessa abordagem, temos um código mais limpo, separado em responsabilidades, e a simplificação da c riação de testes unitários eficazes sobre a GUI. Assim terminamos a série sobre qualidade de interfaces gráficas que abordou os dois lados do desenvolvimento de GUIs: a aparência das aplicações, na Parte 1, e o código escondido por trás das telas nesta segunda parte.
martinfowler.com/eaaDev/ OrganizingPresentations.html
Página escrita pelo autor Martin Fowler que discute os diferentes padrões para o desenvolvimento de GUIs em aplicações desktop. jgoodies.com
Site do JGoodies com tutoriais, artigos, aplicações e APIs para download. javamagazine.com.br/downloads/jm39/ jm-gui2.zip
Hugo Vidal Teixeira
(
[email protected] ) é Bacharel em Informática pela UFRJ,mestre em Engenharia de Software pela COPPE/UFRJ e pesquisador da área de componentes e GUI Design.Atua como consultor integrado à Sakonnet Technology,onde trabalha diretamente com Karsten Lentzsch (criador do JGoodies) e empresas especializadas em GUI design,como Ergosign (.de),Centigrade(.de)e OculusInfo(.com).
60 Java Magazine • Edição 39 jm39.indb 60
15/8/2006 18:19:23
Edição 39 • Java Magazine 61 jm39.indb 61
15/8/2006 18:19:26
Uma Mala Direta com Mensagens Personalizadas e Expressõ
N
este artigo, vamos demonstrar a API JavaMail criando uma aplicação completa. Iniciamos com conceitos básicos sobre a API, depois mostramos passo a passo o desenvolvimento de uma aplicação de mala direta funcional, na qual lemos informações sobre clientes de uma tabela em um banco de dados MySQL e enviamos e-mails personalizados para cada cliente. Também apresentamos o conceito de expressões regulares e as funcionalidades relacionadas oferecidas pela plataforma Java, usando-as para personalizar as mensagens enviadas.
A API JavaMail A API JavaMail é utilizada para leitura e envio de e-mails, e oferece diversos recursos essenciais para aplicações corporativas, entre os quais destacamos: Suporte a vários protocolos de envio e leitura de e-mail (ex.: POP3, SMTP, IMAP) Suporte a mensagens em HTML Suporte a envio de mensagens com anexos •
• •
A API JavaMail não é incluída com o JDK, portanto é necessár io fazer o download e disponibilizá-la para a aplicação. Além disso, será necessária a API JavaBeans Activation Framework (JAF). Os downloads podem ser feitos em java.sun. com/products/javamail/downloads e java.sun.com/ products/javabeans/jaf/downloads. Será preciso
adicionar dois JARs ao classpath da aplicação: mail.jar e activation.jar. Se você está desenvolvendo aplicações Java EE, não há nenhuma preocupação adicional, pois as APIs JavaMail e JAF fazem par te do Java EE.
Visão geral das classes da API Apresentamos a seguir uma breve descrição de algumas classes fundamentais do JavaMail. javax.mail.Session A classe Session representa uma sessão de e-mail. É através
dela que obtemos os objetos responsáveis pelo envio e leitura de e-mails, como por exemplo Transport , Store e Folder. Para obter um Session precisamos de informações sobre o servidor de
62 Java Magazine • Edição 39 jm39.indb 62
15/8/2006 18:19:52
JavaMail es Regulares
Aprenda como utilizar a API JavaMail para enviar e-mails com HTML, enviar anexos e ainda personalizar o e-mail com os dados dos clientes utilizando expressões regulares
YARA SENGER E ANA ABRANTES e-mails, que podem ser passadas através de um objeto java.util.Properties .
dereço de e-mail, incluindo opcionalmente um nome.
javax.mail.Transport
javax.mail.Authenticator e javax.mail.PasswordAuthentication
A classe abstrata Transport encapsula o protocolo de envio de e-mails (geralmente SMTP) e contém métodos para envio de mensagens. Um objeto do tipo Transport é obtido através do objeto Session informando-se o protocolo utilizado. A API JavaMail fornece apenas uma subclasse concreta de Transport : javax.mail.internet.SMTPTransport . javax.mail.Store e javax.mail.Folder
Apesar de não utilizarmos estas classes no nosso exemplo pois são usadas apenas para leitura/transferência de mensagens, vale a pena citá-las. A classe abst rata Store é utilizada para a leitura de mensagens de um servidor de e-mail. Ao obter um objeto Store através da sessão indicamos qual o protocolo utilizado, por exemplo POP3 ou IMAP (há duas subclasses concretas de Store na API: POP3Store e IMAPStore). Então nos conectamos ao servidor informando os dados para autenticação e recebimento das pastas ( javax.mail.Folder) que contêm os e-mails do usuário autenticado. A classe abstrata Folder representa uma pasta de e-mails do usuário autenticado. Objetos Folder (há as subclasses POP3Folder e IMAPFolder na API) são obtidos através de um objeto Store fornecendo seu nome. javax.mail.internet.MimeMessage A classe MimeMessage representa uma
mensagem a ser lida ou enviada por e-mail, e estende a classe abstrata javax.mail.Message. Para criarmos uma MimeMessage é necessário termos um objeto Session. A mensagem fica associada à sessão de e-mail. javax.mail.internet.InternetAddress A classe InternetAddress representa um en-
Um objeto da classe Authenticator sabe como obter os dados para autenticação do usuário, retornando um objeto PasswordAuthentication . Não há restrição para as formas de se obter esses dados, que podem vir de um arquivo, de entradas do usuário em um prompt, de uma conexão a uma fonte de dados externa, ou mesmo informando-se o usuário e a senha no construtor do objeto, como implementamos no exemplo deste artigo.
O projeto de Mala Direta A aplicação de exemplo é formada por quatro classes. A classe EnviadorDeEmail (Listagem 1) é responsável por criar a mensagem, anexar um arquivo e fazer o envio (e numa segunda etapa, por personalizar as mensagens). A classe MalaDireta (Listagem 2) é a classe executável da aplicação; ela cuida da leitura dos clientes do banco de dados e chama os métodos da classe EnviadorDeEmail para cada cliente recuperado. Cada destinatário da mala direta é representado por um objeto Cliente (Listagem 3), com nome, endereço de e-mail, telefone e id. A classe Autenticador (Listagem 4) faz a autenticação no servidor de e-mails, necessária para o envio. Na Listagem 5 é mostrado o comando SQL para criação da tabela de clientes no banco de dados (chamamos o banco de “maladireta”). Inicialmente iremos mostrar como enviar e-mails em formato HTML com um anexo. Depois veremos a personalização do conteúdo do e-mail. A Listagem 7 e a Listagem 8 exibem as alterações nas classes EnviadorDeEmail e MalaDireta necessárias para a personalização das mensagens.
Nessa segunda etapa, é feita a substituição do nome do cliente, e caso o telefone esteja em um formato incorreto, será adicionada uma mensagem no corpo do e-mail solicitando a atualização cadastral. As próximas seções descrevem o processo passo a passo e apresentam detalhes de cada classe.
Classe EnviadorDeEmail: enviando mensagens em HTML com um anexo A classe EnviadorDeEmail tem apenas um método público: enviarEmail() . Neste método é obtido um Session e criado um Message já com o destinatário e o assunto. Em seguida, o método adiciona o conteúdo e anexa um arquivo à mensagem. São usados vários métodos auxiliares para modularizar o código. Obtendo a sessão O método obterSessao() é o primeiro a ser chamado em enviarEmail(). Ele configura um objeto da classe Properties indicando,
através de propriedades pré-estabelecidas pela API, o nome do servidor SMTP (propriedade mail.smtp.host ), e se o servidor exige ou não autenticação (propriedade mail.smtp.auth). No nosso caso consideramos que a autenticação é necessária. Properties props = new Properties(); props.put(“mail.smtp.host”, SERVIDOR_SMTP); props.put(“mail.smtp.auth”, “true”);
Depois, obterSessao() obtém o objeto Session usando Session.getDefaultInstance() . Como indicamos que o servidor exige autenticação, é necessário passar um objeto que implementa a interface Authenticator. Passamos um objeto da nossa classe Autenticador (Listagem 4), que é instanciada informando o usuário e a senha para autenticação no servidor SMTP. sessao = Session.getDefaultInstance( props, new Autenticador(EMAIL, SENHA));
Edição 39 • Java Magazine 63 jm39.indb 63
15/8/2006 18:19:53
Uma Mala Direta com JavaMail
Criando o objeto Message
Em seguida, o método enviarEmail() cria uma mensagem chamando criarMensagem(), que retorna um MimeMessage . Note que é passado como parâmetro o objeto Session; dessa forma a mensagem fica vinculada a esta sessão. MimeMessage mensagem = criarMensagem(sessao, assunto, destinatario);
É possível enviar mensagens para um ou mais destinatários, que podem ser configurados com CC (Carbon Copy), BCC (Blind Carbon Copy) e TO (Destinatário principal). Além disso, cada destinatário pode ter um nome e um e-mail (um objeto InternetAddress ). Os tipos do destinatário são
representados por atributos da inner class RecipientType da classe Message . No trecho a seguir de criarMensagem() é criado um InternetAddress com o e-mail e o nome do destinatário, e depois este endereço é associado à mensagem, informando que será utilizado como destinatário principal (TO).
enderecoRemetente = new InternetAddress( EMAIL_REMETENTE, NOME_REMETENTE); message.setFrom(enderecoRemetente);
enderecoDestinatario = new InternetAddress( destinatario.getEmail(), destinatario.getNome()); message.addRecipient(Message.RecipientType.TO, enderecoDestinatario);
E por fim o método criarMensagem() retorna a mensagem criada.
O remetente também é representado por uma instância da classe InternetAddress , que é associada à mensagem através do método setFrom().
O próximo método a ser executado é anexarConteudoEAnexo() . O conteúdo do email é representado por um objeto Multipart, sendo que nossa mensagem terá duas
O assunto e a data de envio são configurados através dos métodos setSubject() e setSentDate(), que são bastante simples. message.setSubject(assunto); message.setSentDate(new Date()) ;
Definindo o conteúdo do e-mail
Listagem 1.EnviadorDeEmail.java sem personalização package maladireta; import import import import import import
java.io.*; java.util.*; java.util.regex.*; javax.activation.*; javax.mail.*; javax.mail.internet.*;
}
public class EnviadorDeEmail { private static String EMAIL_REMETENTE = “
[email protected]”; private static String NOME_REMETENTE = “Yara”; private static String SERVIDOR_SMTP = “smtp.globalcode.com.br”; private static String SENHA = “senha”;
public void enviarEmail(String assunto, String conteudo, Cliente destinatario, String arquivo) throws AddressException, MessagingException { Session sessao = obterSessao(); Message mensagem = criarMensagem(sessao, assunto, destinatario); adicionarConteudoEAnexo( mensagem, conteudo, destinatario, arquivo); mensagem.saveChanges(); Transport transport = null; try { transport = sessao.getTransport(“smtp”); transport.send(mensagem); } finally { transport.close(); } }
private Message criarMensagem(Session session, String assunto, Cliente destinatario) throws MessagingException, AddressException {
MimeMessage mensagem = new MimeMessage(session); Address enderecoRemetente = null; Address enderecoDestinatario = null; try { enderecoDestinatario = new InternetAddress( destinatario.getEmail(), destinatario.getNome()); enderecoRemetente = new InternetAddress(EMAIL_REMETENTE, NOME_REMETENTE); } catch (UnsupportedEncodingException ex) {
throw new AddressException(ex.getMessage()); } mensagem.addRecipient(Message.RecipientType.TO, enderecoDestinatario); mensagem.setFrom(enderecoRemetente); mensagem.setSubject(assunto); mensagem.setSentDate(new Date()); return mensagem;
private void adicionarConteudoEAnexo(Message message, String conteudo, Cliente destinatario, String arquivo) throws MessagingException, AddressException { // Cria um Multipart que será composto pelo conteudo e pelo anexo Multipart multipart = new MimeMultipart(); // Cria a parte do corpo da mensagem (texto do conteúdo do e-mail) BodyPart messageBodyPartConteudo = new MimeBodyPart(); messageBodyPartConteudo.setContent(conteudo, “text/html”); // Adiciona o corpo da mensagem ao Multipart multipart.addBodyPart(messageBodyPartConteudo); // Cria uma segunda parte do corpo (anexos do contéudo do e-mail) BodyPart messageBodyPartAnexo = new MimeBodyPart(); // Cria o anexo DataSource source = new FileDataSource(arquivo); // Define o data handler para o anexo messageBodyPartAnexo.setDataHandler(new DataHandler(source)); // Define o nome do arquivo String nomeArquivo = new File(arquivo).getName(); messageBodyPartAnexo.setFileName(nomeArquivo); // Adiciona o anexo ao Multipart multipart.addBodyPart(messageBodyPartAnexo); // Adiciona o Multipart a mensagem message.setContent(multipart);
}
private Session obterSessao() { Session sessao = null; Properties props = new Properties(); props.put(“mail.smtp.host”, SERVIDOR_SMTP); props.put(“mail.smtp.auth”, “true”); sessao = Session.getDefaultInstance(props, new Autenticador( EMAIL_REMETENTE, SENHA)); return sessao; } }
64 Java Magazine • Edição 39 jm39.indb 64
15/8/2006 18:19:54
partes, uma com o corpo do e-mail e outra com o arquivo a ser anexado. As partes são representadas por objetos MimeBodyPart . O corpo do e-mail deve estar em HTML, por isto a instância de MimeBodyPart é configurada informando o tipo como “text/html”: Multipart multipart = new MimeMultipart(); BodyPart messageBodyPartConteudo = new MimeBodyPart(); messageBodyPartConteudo.setContent(conteudo, “text/html”);
Depois associamos esta parte ao objeto Multipart que representa todo o conteúdo da mensagem. multipart.addBodyPart(messageBodyPartConteudo);
Anexando um arquivo
O anexo do e-mail também é representado por uma instância da classe MimeBodyPart, criada com seu construtor default:
FileDataSource instanciado, e associá-lo ao objeto da classe MimeBodyPart que
representa o anexo através do método setDataHandler() : messageBodyPartAnexo.setDataHandler( new DataHandler(source));
BodyPart messageBodyPartAnexo = new MimeBodyPart();
3. E
Para anexar o arquivo ao e-mail é necessário realizar os seguintes passos: 1. Criar um FileDataSource a partir do caminho completo do arquivo que queremos anexar: DataSource source = new FileDataSource(arquivo);
2 . Criar
um DataHandler a partir do
indicar qual o nome do arquivo anexo para o objeto MimeBodyPart , através do método setFileName(String nome): String nomeArquivo = new File( arquivo).getName(); messageBodyPartAnexo.setFileName(nomeArquivo);
Depois, mais uma vez, associamos esta parte da mensagem ao objeto Multipart, que representa todo o conteúdo da mensagem:
Listagem 2. MalaDireta.java sem personalização package maladireta; import import import import
java.io.*; java.sql.*; java.util.*; javax.mail.MessagingException;
} } catch (Exception e) { System.out.printl n(“Erro ao ler clientes do banco de dados.\n”); throw e; }
public class MalaDireta { private final static String STRING_CON = “ jdbc:mysql://localhost/maladireta”; private final static String USUARIO_BD = “root”; private final static String SENHA_BD = “root”; private final static String ANEXO = “C:\\temp\\teste.txt”; public static void main(String[] args) throws Exception { String assunto = “Teste de envio de e-mail do projeto MalaDireta”;
StringBuilder conteudo = new StringBuilder(); conteudo.append(“ ”); conteudo.append( “Teste de envio de e-mail”); conteudo.append(“ Caro(a) cliente, ”); conteudo.append( “Você está cadastrado(a) no nosso sistema e esta“); conteudo.append(“é uma mensagem que testa o envio de” + “ e-mail para a sua conta.”); conteudo.append(“ Se você não quer mais” + “ receber nossos e-mails, por “); conteudo.append( “favor escreva para ”); conteudo.append( “
[email protected] solicitando descadastramento.”); conteudo.append(“Pedimos a gentileza de responder este ” +“e-mail confirmando o recebimento.”); conteudo.append( “Atenciosam ente,Equ ipe Globalcode< br>”); conteudo.append(“” + “www.globalcode.com.br”); conteudo.append(“Telefone: (11) 3171-1987 ”); EnviadorDeEmail enviadorDeEmail = new EnviadorDeEmail(); try { List clientes = obterClientes(); // envia o e-mail para todos os clientes for (Iterator it = clientes.iterator(); it.hasNext();) { Cliente cliente = (Cliente) it.next(); try { enviadorDeEmail.enviarEmail(assunto, conteudo.toString(), cliente, ANEXO); System.out.println(“e-mail enviado com sucesso para “ + cliente.getNome());
} catch (MessagingExcepti on e) { System.out.prin tln(“Erro ao enviar email para “ + cliente.getNome ()); e.printStackTrace(); }
}
public static List obterClientes() throws SQLException, ClassNotFoundException { Connection con = obterConexao(); Statement st = null; ResultSet rs = null; List listaClientes = new ArrayList(); try { st = con.createStat ement(); rs = st.executeQuer y(“select * from clientes”); while (rs.next()) { Cliente cliente = new Cliente(rs.getL ong(“id”), rs .getString(“nome ”), rs.getString(“e mail”), rs .getString(“telefone”)); listaClientes.add(cliente); } return listaClientes; } finally { if (rs != null) rs.close(); if (st != null) st.close(); if (con != null) con.close(); } }
public static Connection obterConexao() throws SQLException, ClassNotFoundException { Class.forName(“com.mysql.jdbc.Driver”); Connection con = DriverManager.g etConnection(STRI NG_CON, USUARIO_BD, SENHA_BD); return con; } }
Edição 39 • Java Magazine 65 jm39.indb 65
15/8/2006 18:19:55
Uma Mala Direta com JavaMail
multipart.addBodyPart(messageBodyPartAnexo);
Finalmente, colocamos as duas partes na mensagem: mensagem.setContent(multipart);
Classe MalaDireta: controlando a aplicação Por simplicidade, no nosso exemplo o acesso ao banco de dados foi realizado
inteiramente na classe MalaDireta ; em um projeto real haveria uma separação de responsabilidades muito maior. O acesso é feito através dos métodos obterConexao() e obterClientes(), que retornam respectivamente uma conexão ao banco de dados e uma lista de objetos Cliente criados a partir dos dados na tabela de clientes. No método main() criamos um String com o assunto da mensagem e um StringBuilder para armazenar o conteúdo a ser enviado
Listagem 3. Cliente.java package maladireta; import java.io.Serializable; public class Cliente implements Serializable { private long id; private String nome = “”; private String email = “”; private String telefone = “”; public Cliente() {} public Cliente(String nome, String email, String telefone) { this.nome = nome; this.email = email; this.telefone = telefone; } public Cliente(long id, String nome, String email, String telefone) { this(nome, email, telefone); this.id = id; } //… get/set }
Listagem 4. Autenticador.java package maladireta; import javax.mail.PasswordAuthentication; public class Autenticador extends javax.mail.Authenticator { private String user; private String senha; public Autenticador() {} public Autenticador(String user, String senha) { this.user = user; this.senha = senha; } public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, senha); } }
Listagem 5. Script para criação da tabela no banco de dados CREATE TABLE `clientes` ( `id` int(11) NOT NULL auto_increment, `nome` char(40) NOT NULL default ‘’, `email` char(40) NOT NULL default ‘’, `telefone` char(20) NOT NULL default ‘’, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 ROW_FORMAT=FIXED;
(o código HTML do corpo da mensagem). Obtemos a lista de clientes do banco de dados através da chamada ao método obterClientes() e enviamos um e-mail para cada cliente recuperado utilizando o método enviarEmail() da classe EnviadorDeEmail discutida anteriormente. Veja o código completo da classe MalaDireta na Listagem 2. A Figura 2 mostra o e-mail enviado. Até aqui mostramos como enviar e-mails para múltiplos destinatários, mas sem personalizar as mensagens para cada cliente. No nosso exemplo, essa personalização será feita usando expressões regulares. Por isso, antes de continuar com o código, vamos apresentar conceitos gerais sobre este tema e suas diversas aplicações.
Conceitos gerais de expressões regulares Expressões regulares são muito utilizadas para localização de informações dentro de textos e para validação de dados textuais que devem seguir formatos específicos, como URLs, datas, endereços de e-mail, CEPs, números de telefone etc. Na API do Java SE, o pacote javax.util.regex contém duas classes principais voltadas ao trabalho com expressões regulares. Pattern representa uma expressão regular compilada, e Matcher é capaz de verificar se uma dada str ing está de acordo com a expressão regular em um objeto Pattern. A documentação JavaDoc da classe Pattern inclui um resumo dos principais símbolos utilizados para expressões regulares. Veja alguns exemplos de expressões usando esses símbolos e o que elas representam: • \d – um digito [0-9] • [a-zA-Z] – qualquer letra • [a-z] – qualquer letra minúscula • ? – o símbolo antes do ? aparece zero ou uma vez • * – o símbolo antes do * aparece zero ou mais vezes • + – o símbolo antes do + aparece uma ou mais vezes • {n} – o símbolo antes do {n} aparece exatamente n vezes • {n,m} – o símbolo antes do {n,m} aparece no mínimo n vezes e não mais do que m vezes • {n,} – o símbolo que está antes do {n,}
66 Java Magazine • Edição 39 jm39.indb 66
15/8/2006 18:19:55
aparece no mínimo n vezes • X|Y – ou X ou Y • \s – espaço em branco Na Figura 1 é analisado um exemplo complexo de expressão regular. A figura inclui também uma tabela com nomes que estão ou não de acordo com essa expressão. O trecho de código na Listagem 6 ilustra a utilização das classes Pattern e Matcher para verificar vários nomes com a expressão regular detalhada na Figura 1. As classes Pattern e Matcher atendem das mais simples às mais complexas situações envolvendo expressões regulares. No entanto, outras classes do Java SE também suportam essas expressões, como a própria classe String, que oferece funcionalidades suficientes em muitos casos. Veja alguns métodos da classe String que suportam expressões regulares: • Strin g replaceAll (String regex, Strin g replacement) – Substitui todos os “pedaços” da string que obedecem ao padrão estabelecido pela expressão regular em regex. • String replaceFirst (String regex, String replacement) – Substitui a primeira ocorrência dentro da string que obedeça o padrão estabelecido pela expressão regular.
[A-Z] O texto deve começar com uma letra maiuscula
[a-z] + Seguido de uma ou mais letras minusculas
\\s[A-Z][a-z]+ Um espaço, seguido de uma letra maiúscula e uma ou mais letras minúsculas
| ou
[A-Z][a-z]+((\\s[A-Z][a-z]+)|\\s[a-z]{2}|\\s[A-Z].)+ + Uma ou mais vezes
\\s[a-z]{2} Um espaço, seguido de duas letras minúsculas
\\s[A-Z] . Um espaço, seguido de uma letra maiúscula, seguido de um ponto
No me s válidos de aco rdo com a exp ress ão
N ome s invá lidos de acord o co m a expre ssã o
Maria Aparecida Maria A. Soares Maria Aparecida Soares Maria da Silva
maria aparecida MARIA APARECIDA Maria A Soares
Figura 1. Um exemplo de expressão regular
Vamos apresentar agora a aplicação de expressões regulares no nosso exemplo, realizando a substituição do nome do cliente no e-mail e também a verificação do telefone. Se o telefone for considerado inválido pela expressão regular construída, iremos inserir um texto no e-mail solicitando que o cliente atualize seus dados cadastrais.
Aplicando as expressões regulares Para permitir a personalização das mensagens vamos fazer algumas alterações im-
Figura 2. E-mail enviado em HTML com anexo teste.txt
Edição 39 • Java Magazine 67 jm39.indb 67
15/8/2006 18:19:57
Uma Mala Direta com JavaMail
portantes na classe MalaDireta, mostradas na Listagem 7. Observe que no texto do e-mail incluímos no conteúdo HTML do e-mail a ser enviado “pedaços” de texto para serem substituídos: %cliente.nome% e %cliente. telefone% . Vamos utilizar expressões regulares através do método replaceAll() da classe String para substituir esses pedaços pelo nome e o telefone do cliente. Observe que este é o caso mais simples de uso de expressões regulares, em que o padrão fornecido é exatamente igual ao texto que deve ser localizado. Para demonstrar mais recursos de expressões regulares, vamos verificar se um telefone de cada cliente segue um padrão estabelecido, de acordo com a seguinte expressão: \\d{2}(-|\\s)??\\d{8}
Essa expressão verifica se o telefone contém um DDD com dois dígitos e um número
com oito dígitos, sendo que o DDD pode estar colado ao número, ou separado por um espaço ou hífen. De acordo com essa expressão, os seguintes números seriam considerados válidos: • 11-31711987 • 11 31711987 • 1131711987 Já os números a seguir não estariam de acordo com a expressão: • 31711987 • 222323 • 7632323r34 Agora vamos analisar passo a passo o novo método personalizarConteudo() da classe
MalaDireta, que realiza a substituição do
nome e a verificação do telefone. 1. Utilizamos o método replaceAll() da classe String. Este método recebe uma string
contendo uma expressão regular e a string que deve substituir o texto localizado. Neste caso a expressão contém apenas caracteres, ou seja, não utilizamos símbolos especiais como /d, ou [A-Z] , apenas uma string %cliente.nome% (note que a porcentagem não é um símbolo especial de expressão regular). conteudo = conteudo.replaceAll( “%cliente.nome%”,destinatario.getNome());
2. Compilamos a expressão regular que
Listagem 6. Trecho demonstrando o uso da expressão regular ilustrada na Figura 1. Pattern p = Pattern.compile( “[A-Z][a-z]+((\\s[A- Z][azz]+)|\\s[a-z]{2}|\\s[A-Z].)+”); String nomeCorreto = “Maria Aparecida da Silva”; String nomeCorreto1 = “Maria Aparecida Silva”; String nomeCorreto2 = “Maria A. Soares”; String nomeIncorreto = “maria aparecida”; String nomeIncorreto2 = “MARIA APARECIDA”; String nomeIncorreto3 = “Maria”; String[] nomes = {nomeCorreto, nomeCorreto1, nomeCorreto2, nomeIncorreto, nomeIncorreto2, nomeIncorreto3}; for (String nome: nomes){ Matcher m = p.matcher(nome); if (m.matches()){ System.out.println(nome+”: está no padrão”); } else{ System.out.println(nome+”: não está no padrão”); }
Listagem 7. Alterações na classe MalaDireta para personalização das mensagens public class MalaDireta { //... public static void main(String[] args) throws Exception { String assunto = “Teste de envio de e-mail do projeto MalaDireta”; StringBuilder conteudo = new StringBuilder(); conteudo.append(“”); conteudo.append(“ Teste de envio de e-mail”); conteudo.append(“ Caro(a) %cliente.nome% , ”); conteudo.append(“Você está cadastrado(a) no nosso sistema e esta “); conteudo.append(“é uma mensagem que testa o envio de e-mail para a sua conta.”); conteudo.append(“ Se você não quer mais receber nossos e-mails, por “); conteudo.append(“favor escreva para ”); conteudo.append(“
[email protected] solicitando descadastramento.”); conteudo.append(“ %corrigir_telefone% ”); conteudo.append(“Pedimos a gentileza de responder este e-mail confirmando o” +“recebimento.”); //... }
68 Java Magazine • Edição 39 jm39.indb 68
15/8/2006 18:20:16
irá validar o telefone com um método estático da classe Pattern.
String telefone = destinatario.getTelefone(); Matcher m = p.matcher(telefone);
Pattern p = Pattern.compile(“\\d{2}(-|\\s)??\\d{8}”);
4. Se o telefone estiver no padrão, a cadeia de caracteres “%corrigir_telefone%” será removida do e-mail, através da substituição por uma string vazia. Se o telefone não estiver no padrão esperado, será substituído por um texto que solicita que o cliente entre em contato para atualização do cadastro.
3. Depois Verificamos se o telefone do destinatário está de acordo com a expressão regular compilada. Esta operação retorna um objeto da classe Matcher, sobre o qual podemos chamar o método matches() que retorna verdadeiro ou falso.
if (m.matches()){ System.out.println(“O “+telefone+” está no padrão”); conteudo = conteudo.replaceAll(“%corrigir_telefone%”,””); } else { System.out.println(“O “+telefone+” não está no padrão”); conteudo = conteudo.replaceAll(“%corrigir_ telefone%”,”Seu telefone “+ telefone+ “ parece estar incorreto /*... */ “); }
O método personalizarConteudo() deve ser chamado na classe EnviadorDeEmail. Veja na Listagem 8 o trecho alterado nesta classe. A Figura 3 mostra um exemplo de e-mail enviado com o conteúdo personalizado.
Conclusões Neste artigo, vimos como usar recursos essenciais da API JavaMail aplicando-os em uma situação real. Usamos também expressões regulares, um recurso poderoso que é suportado por classes específicas da API do Java SE, bem como a classe String. java.sun.com/products/javamail/
Página oficial do JavaMail. Figura 3. E-mail com conteúdo personalizado
java.sun.com/docs/books/tutorial/extra/ regex/intro.html
Tutorial sobre Expressões Regulares
Listagem 8. Alterações na classe EnviadorDeEmail para personalização public class EnviadorDeEmail { //... private void adicionarConteudoEAnexo(Message message, String conteudo, Cliente destinatario, String arquivo) throws MessagingException, AddressException { conteudo = personalizarConteudo(conteudo, destinatario); //... Igual à Listagem 1 } private String personalizarConteudo(String conteudo, Cliente destinatario){ // personalização do conteúdo conteudo = conteudo.replaceAll(“%cliente.nome%”,destinatario.getNome()); Pattern p = Pattern.compile(“\\d{2}(-|\\s)??\\d{8}”); String telefone = destinatario.getTelefone(); Matcher m = p.matcher(telefone); if (m.matches()){ System.out.println(“O “+telefone+” está no padrão”); conteudo = conteudo.replaceAll(“%corrigir_telefone%”,””); } else { System.out.println(“O “+telefone+” não está no padrão”); conteudo = conteudo.replaceAll(“%corrigir_telefone%”, “Seu telefone “+ telefone + “ parece estar incorreto,” + “solicitamos a gentileza de entrar em contato” + “para atualização do cadastro”); } return conteudo; } //... }
javamagazine.com.br/downloads/jm39/ jm-maladireta.zip Yara M. H. Senger
(
[email protected] ) é formada em Ciências da Computação na USP em São Carlos, especialista em desenvolvimento web; possui as certificações SCJA, SCJP e SCWCD. Atualmente é Instrutora e Diretora Educacional da Globalcode, criadora e coordenadora de diversos cursos das carreiras Academia do Java e Academia do Web Developer. Ana Abrantes
(
[email protected] ) é desenvolvedora Java na Globalcode, co-autora do curso de JasperReports/ iReport e possui algumas certificações em Java (SCJA, SCJP e SCWCD). É formada pela FATECSP e atua na área de informática há mais de 15 anos.
Edição 39 • Java Magazine 69 jm39.indb 69
15/8/2006 18:20:18
RIA com Open Construindo Rich Internet Applications
C
om o crescimento da internet, a distribuição e a manutenção de aplicações tornaram-se muito mais simples e rápidas. Utilizando um brows er, o usuário passou a aces sar o último release sem ter que se preocupar com versionamento ou procedimentos de instalação. Mas um efeito colateral do paradigma web não tardou a aparecer. Interfaces mais pobres e menos intuitivas substituíram os sistemas “fat-client” antes largamente utilizados, forçando o usuário a se acostumar com o “clicar e esperar” das interfaces web. O conceito de Rich Internet Application (RIA) é uma das respostas a este problema. Permitindo que o usuário interaja com a aplicação como se esta fosse um sistema desktop tradicional, o RIA baseia-se na
premissa de rodar na máquina do usuário todo o processamento de interface gráfica, deixando para o lado do servidor o processamento da lógica de negócio. O lado cliente das aplicações RIA pode ser implementado de diversas maneiras, dentre elas applets Java, páginas DHTML, aplicativos Macromedia Flash, JavaScript e outros. O OpenLaszlo é uma plataforma madura de desenvolvimento RIA baseada em JavaScript e XML com suporte à depuração de aplicações, que renderiza aplicações em Macromedia Flash. Neste artigo vamos apresentar os principais conceitos dessa plataforma e construir uma
aplicação de exemplo funcional.
Funcionamento do OpenLaszlo Aplicações OpenLaszlo são definidas em documentos na linguagem LZX, baseada em XML. A engine de renderização do OpenLaszlo (que chamaremos de “Servidor OpenLaszlo”) é uma aplicação Java para web, portanto deve ser instalada em um container web como o Tomcat, ou num servidor de aplicações Java EE. Em tempo de execução, o servidor OpenLaszlo “compila” os arquivos-fonte LZX para um aplicativo Flash ( .SWF) que é enviado ao cliente. Na máquina cliente, o browser roda o aplicativo (às vezes chamado de “Cliente OpenLaszlo”) normalmente, utilizando o plug-in Flash. A Figura 1 ilustra a organização dos componentes-chave do OpenLaszlo.
Obtendo e configurando o OpenLaszlo Server O download do OpenLaszlo pode ser feito em openlaszlo.org/download, selecionando-se a plataforma desejada (Windows, Linux ou Mac OS). Para outras plataformas, ou para fazer a instalação manualmente, obtenha o pacote identificado como “Dev Kit”. O download já inclui o Tomcat, com o servlet do Servidor OpenLaszlo configurado, bem como documentação e exemplos. Para verificar a instalação, inicie o servidor com um duplo-clique no arquivo server\lps-3.3.3\lps\utils\ startTomcat, dentro do diretório raiz do OpenLaszlo (ajustando sempre o
70 Java Magazine • Edição 39 jm39.indb 70
15/8/2006 18:20:28
Laszlo com Flash e Java
Turbine suas interfaces gráficas e maximize a interatividade com o usuário utilizando uma plataforma open source baseada em XML e Java
ANDRÉ LUÍS MONTEIRO número da versão para o seu caso). Após iniciá-lo, aponte o browser para: http:// localhost:8080/lps-3.3.3/examples/hello.lzx.
Se tudo estiver OK, deverá aparecer a mensagem “Hello Laszlo!”. (Para parar o servidor, dê um duplo-clique no arquivo server\lps-3.3.3\lps\utils\stopTomcat. )
Primeiro aplicativo OpenLaszlo Criar uma aplicação “Olá Mundo” no OpenLaszlo é muito simples. Comece criando um arquivo texto com o conteúdo a seguir: Ola Mundo!
Salve-o com extensão . lzx (ex.: OlaMundo. lzx) no diretório server\lps-3.3.3\my-apps, e teste a aplicação apontando o browser para http://localhost:8080/lps-3.3.3/myapps/OlaMundo.lzx.
Toda aplicação OpenLaszlo começa com a tag , que renderiza o painel principal. Já a tag renderiza um texto qualquer.
Aplicação de exemplo Como exemplo, iremos desenvolver uma aplicação simples de calculadora, cujos cálculos são realizados no lado do servidor por uma página JSP. Os cálculos a serem processados são submetidos ao Tomcat usando parâmetros HTTP, e o resultado é um documento XML como a seguir: 25
O objetivo é demonstrar como uma aplicação cliente criada com o OpenLaszlo (a interface gráfica da calculadora) pode submeter dados para processamento no servidor e obter o resultado mostrando-o para o usuário. Com as técnicas mostradas, você poderá criar aplicações RIA em Flash que acessam recursos corporativos rodando, por exemplo, em um servidor Java EE.
Componentes gráficos e layout O primeiro passo é criar um arquivo LZX de acordo com a Listagem 1. Analisando o código, você verá que os elementos XML representam componentes visuais, e definições de classes e de funções JavaScript (estas últimas em tags ). Outro item que chama atenção é o fato de as classes usadas como elementos XML começarem com letra minúscula, por exemplo button, o que pode soar um tanto estranho para quem está acostumado às convenções do Java.
Como se trata de uma linguagem orientada a objetos, a LZX nos permite definir classes e métodos e usar herança e polimorfismo. Tomemos como exemplo a classe meuBotao. Ela herda da classe button e define um método que será disparado pelo evento onclick. O corpo do método é todo escrito em JavaScript e faz referência a outros objetos do documento, através dos seus respectivos ids. Um id permite que um ob jeto seja referenciado de qualquer parte do documento LZX. Por exemplo, na chamada visor.setAttribute(‘text’, ‘ ‘) visor é o id. Ao desenvolver aplicações OpenLaszlo, geralmente criamos primeiro toda a estrutura estática da aplicação, definindo os componentes visuais e o layout, e depois adicionamos o comportamento e o processamento de eventos. Para a calculadora, definimos uma janela, instanciando uma classe window. Dentro dela adicionamos uma caixa de texto edittext (o visor), e os botões do tipo meuBotao. Em arquivos LZX, o layout é definido pela classe lzLayout e suas classes filhas. No exemplo utilizamos a classe simplelayout que estende lzLayout e Browser Cliente Plug-in Flash Cliente OpenLaszlo
HTTP
Container Web / Servidor Java EE Servidor OpenLaszlo
Figura 1. Arquitetura da plataforma OpenLaszlo
Edição 39 • Java Magazine 71 jm39.indb 71
15/8/2006 18:20:40
RIA com Open Laszlo
organiza os componentes na tela, vertical e horizontalmente. Além desses componentes gráficos, criamos também as views que são “recipientes” para outros componentes (similares aos containers do Swing/AWT) e ajudam na organização visual. A Figura 2 ilustra como foi feito o layout da calculadora – os retângulos em laranja são views. A Figura 3 mostra a calculadora em sua versão final.
Definindo o comportamento Com o layout definido e a parte estática construída, partimos para o comportamento da aplicação e a interação com o usuário. A começar com os botões, o leitor notará que todos são instâncias da classe meuBotao criada no início do arquivo LZX, e que contêm um método para o evento onclick definido no elemento . Ou seja, toda instância desta classe invocará este método
Listagem 1. calculadora.lzx var textoBotao = this.getAttri bute(‘text’) ; var valorVisor = visor.getAttribute(‘text’); if (textoBotao == ‘+’ || textoBotao == ‘-’ || textoBotao == ‘/’ || textoBotao == ‘X’) { operacao = textoBotao; param1 = valorVisor.substring(1); visor.setAtt ribute(‘text’ , ‘ ‘); } else if (textoBotao == ‘=’) { param2 = valorVisor.substring(1); visor.setAtt ribute(‘text’ , ‘ ‘); chamaServidor(); } else if (textoBotao == ‘C’) { visor.setAtt ribute(‘text’ , ‘ ‘); } else {
if (valorVisor == ‘0’) { visor.setAttribute(‘text’, ‘ ‘); } visor.setAttribute(‘text’, valorVisor + textoBotao); }
72 Java Magazine • Edição 39 jm39.indb 72
15/8/2006 18:20:48
Edição 39 • Java Magazine 73 jm39.indb 73
15/8/2006 18:20:51
RIA com Open Laszlo
Figura 2. Layout da calculadora.
Figura 3. Versão final da calculadora.
ao receber o evento onclick. O método seta as variáveis param1, operacao e param2 de acordo com a operação indicada pelo usuário. Para submeter os dados para o JSP, utilizamos a classe dataset , que permite enviar parâmetros HTTP e recuperar a resposta formatada em XML. Note que na definição do dataset myData, o evento ondata chama a função JavaScript mostraResultado() . Dessa forma, assim que o servidor retornar a reposta, a função será invocada. O código do JSP é bem si mples, conforme ilustra a Listagem 2. Após os dados serem submetidos e a resposta do cálculo ser rec uperada, uma animação faz com que a calculadora se mova para a direita, para que o resultado seja mostrado no plano de fundo. Isso é possível graças ao método animate() da classe lzNode (que é a superclasse de todos os componentes gráficos). Este método permite que alterações nas propriedades de um objeto sejam feitas num espaço de tempo determinado. A nossa aplicação contém algumas animações, como a definida no elemento . Este elemento agrupa um conjunto de animações relacionadas, que são executadas simultaneamente ou em seqüência. A função animaJanela() também utiliza recursos de animação. No código dessa função, observe que o método animate() recebe quatro parâmetros: o atributo que será animado, o valor que o atributo deverá receber; o tempo total da animação, para que o atributo assuma o novo valor; e um booleano, indicando se a alteração do atributo deve ser relativa ou não.
Para rodar a aplicação, basta copiar o arquivo calculadora.lzx para o diretório server\lps-3.3.3\my-apps de sua instalação do OpenLaszlo. É necessário também instalar o JSP no Tomcat. Para simplificar, vamos colocá-lo no diretório server\tomcat-5.0.24\webapps\ROOT (a versão do Tomcat pode variar). Além disso, como o JSP usa a JSTL precisamos do jstl.jar e do standard.jar (disponíveis em jakarta.apache.org/site/downloads/downloads_taglibs. html, na seção “Standard 1.1 Taglib”).
Copie estes dois JARs para o diretório Server\tomcat-5.0.24\webapps\ROOT\ WEB-INF\lib.
Pronto. Basta apontar o browser para http://localhost:8080/lps-3.3.3/my-apps/ calculadora.lzx para ver o exemplo fun-
cionando.
Conclusões Neste artigo apresentamos o básico do OpenLaszlo, da instalação à criação de uma aplicação funcional que faz uso de código Java no servidor. O OpenLaszlo é uma plataforma que vem ganha ndo apoio crescente da comunidade de desenvolvedores e integra-se especialmente bem com a tecnologia Java. A plataforma está em franca evolução, como atesta o novo pro jeto “Legals” com release final previsto para o final deste ano, e que suportará a geração para Flash 9 e DHTML.
OpenLaszlo.org
Site oficial do OpenLaszlo que contém tutoriais e um fórum com participação da comunidade. laszlosystems.com
Listagem 2. calculadora.jsp
(
[email protected]. br ) é consultor sênior da Fiveware Solutions e tem as certificações SCJP, SCWCD e SCEA.
74 Java Magazine • Edição 39 jm39.indb 74
15/8/2006 18:20:52
jm39.indb 75
15/8/2006 18:20:56