java magazine 41

111
Atenção: por essa edição ser muito antiga não há arquivo PDF para download.Os artigos dessa edição estão disponíveis somente através do formato HTML. Editorial Esta edição cobre uma gama variada de assuntos importantes para desenvolvedores Java em vários níveis. O Struts 2, que está nas suas primeiras versões mas já chama muita atenção dos programadores, é o destaque desta capa. No artigo, além de serem descritos as principais mudanças do novo Struts (que não são poucas), é criada uma aplicação completa que faz uso dos novos recursos, inclusive de validação de dados com AJAX. O Struts 2 ainda está em franca evolução, mas aqui você terá uma visão antecipada do que está pronto, e verá na prática as reais vantagens da nova versão do framework que domina o cenário da programação web, por sua qualidade, utilidade e versatilidade. Começamos também uma série sobre a tecnologia JDBC, explorando esse assunto essencial para praticamente qualquer desenvolvedor Java. A persistência de dados é o coração da maioria dos sistemas de informação, e o JDBC é um padrão tão consolidado que hoje é imitado mesmo pelos mais árduos concorrentes do Java. A primeira parte da série aborda os conceitos básicos da tecnologia, descreve o histórico de suas versões, e mostra como preparar e testar o ambiente-e, claro, como realizar as primeiras consultas e mudanças no banco de dados. O Swing é considerado o toolkit gráfico mais completo e bem-estruturado do mercado. Com ele é possível criar interfaces gráficas que desafiam qualquer limite de forma funcionalidade. Talvez por essa versatilidade e poder, o Swing tem uma curva mais íngreme de aprendizado, assustando desenvolvedores iniciantes, especialmente ao trabalhar com componentes complexos como JList, JComboBOx e JTable. Nesta edição, você com ela a conhecer os detalhes por trás desses elementos mais sofisticados do Swing, entendendo sua arquitetura e explorando na prática a versatilidade que toolkit tem a oferecer. O agendamento de tarefas é tratado em dois artigos que se complementam, tornando esta edição uma referência completa sobre o assunto. Você verá como usar da melhor forma os recursos incluídos no Java SE e no Java EE para agendar tarefas (periódicas ou não), e poderá testar tudo com uma aplicação Swing reutilizável, que usa várias boas práticas do desenvolvimento. E para ir além do oferecido nas plataformas Java obtendo o máximo de controle no agendamento de tarefas, você verá como usar o framework open source Quartz, do OpenSymphony (o mesmo grupo criador do WebWork, que se juntou ao Struts para criar o Struts 2). Veja também nesta edição um tutorial crítico sobre o Java EE5. Ao mesmo tempo em que você cria uma aplicação completa passo a passo, usando EJB 3.0, Java Pesistence API e JSF, e o NetBeans

Upload: sandoval-bento-da-silva

Post on 25-Jul-2015

381 views

Category:

Documents


5 download

TRANSCRIPT

Page 1: Java Magazine 41

Atenção: por essa edição ser muito antiga não há arquivo PDF para download.Os artigos dessa edição estão disponíveis somente através do formato HTML.

Editorial Esta edição cobre uma gama variada de assuntos importantes para desenvolvedores Java em vários níveis. O Struts 2, que está nas suas primeiras versões mas já chama muita atenção dos programadores, é o destaque desta capa. No artigo, além de serem descritos as principais mudanças do novo Struts (que não são poucas), é criada uma aplicação completa que faz uso dos novos recursos, inclusive de validação de dados com AJAX. O Struts 2 ainda está em franca evolução, mas aqui você terá uma visão antecipada do que está pronto, e verá na prática as reais vantagens da nova versão do framework que domina o cenário da programação web, por sua qualidade, utilidade e versatilidade. Começamos também uma série sobre a tecnologia JDBC, explorando esse assunto essencial para praticamente qualquer desenvolvedor Java. A persistência de dados é o coração da maioria dos sistemas de informação, e o JDBC é um padrão tão consolidado que hoje é imitado mesmo pelos mais árduos concorrentes do Java. A primeira parte da série aborda os conceitos básicos da tecnologia, descreve o histórico de suas versões, e mostra como preparar e testar o ambiente-e, claro, como realizar as primeiras consultas e mudanças no banco de dados. O Swing é considerado o toolkit gráfico mais completo e bem-estruturado do mercado. Com ele é possível criar interfaces gráficas que desafiam qualquer limite de forma funcionalidade. Talvez por essa versatilidade e poder, o Swing tem uma curva mais íngreme de aprendizado, assustando desenvolvedores iniciantes, especialmente ao trabalhar com componentes complexos como JList, JComboBOx e JTable. Nesta edição, você com ela a conhecer os detalhes por trás desses elementos mais sofisticados do Swing, entendendo sua arquitetura e explorando na prática a versatilidade que toolkit tem a oferecer. O agendamento de tarefas é tratado em dois artigos que se complementam, tornando esta edição uma referência completa sobre o assunto. Você verá como usar da melhor forma os recursos incluídos no Java SE e no Java EE para agendar tarefas (periódicas ou não), e poderá testar tudo com uma aplicação Swing reutilizável, que usa várias boas práticas do desenvolvimento. E para ir além do oferecido nas plataformas Java obtendo o máximo de controle no agendamento de tarefas, você verá como usar o framework open source Quartz, do OpenSymphony (o mesmo grupo criador do WebWork, que se juntou ao Struts para criar o Struts 2). Veja também nesta edição um tutorial crítico sobre o Java EE5. Ao mesmo tempo em que você cria uma aplicação completa passo a passo, usando EJB 3.0, Java Pesistence API e JSF, e o NetBeans

Page 2: Java Magazine 41

5.5, conhece detalhes práticos sobre as novidades no Java EE 5, e como elas afetam a produtividade e os padrões de desenvolvimento Java. E você vai conferir uma ferramenta de qualidade que analisa seu código à procura de possíveis problemas e apresenta minúcias sobre as questões identificadas. O FindBugs é uma mão na roda para quem está sempre à procura de aumentar a qualidade e a performance de sua aplicações. Boa Leitura! Struts 2: primeiros passos Prepare-se para a Evolução do Mais Popular Framework Web Explorando as principais novidades com um exemplo completo: interceptores, novas tags, componentes AJAX – e muito mais produtividade. Os Struts 2 é o resultado da junção do antigo Struts com outro excelente framework web chamado WebWork. Como ambos possuíam arquitetura semelhante e trabalhavam como o paradigma de ações, as equipes de desenvolvimento decidiram unir o melhor dos dois mundos. Neste artigo abordamos a nova arquitetura do Struts, discutindo as mudanças mais significativas, e construímos uma aplicação completa de cadastro de usuários com validação cliente/servidor, utilizando as novas tags e componentes AJAX. Para a construção de exemplo, utilizaremos o IDE Eclipse com o plug-in WTP (Web Tools Project). É possível fazer o download do IDE já com o WTP integrado, em eclipse.org/webtools. Se você já tem o Eclipse instalado, pode também baixar e instalar o WTP a partir do site de atualizações do Callisto. Todo o código aqui demonstrado está disponível no site da Java Magazine. O Struts 2, no momento em que escrevo, ainda está no processo de construção do seu primeiro release, portanto a criação de um projeto compatível pode ser um pouco tortuosa. Então, para facilitar o acompanhamento e garantir que tudo funcione como descrito aqui, recomendo fazer o download do site da revista e importar o projeto completo para o Eclipse. Struts 2 na prática Nada melhor para conhecer o novo Struts do que usá-lo na pratica. Então vamos diretamente à nossa aplicação de exemplo. Para saber detalhes sobre os novos conceitos que surgirão no caminho, como Interceptors e Results, veja os quadros “Mudanças e novidades no Struts 2” e “Tipos de Interceptors”. Utilizaremos como banco de dados o MySQL. A Listagem 1 mostra o script para criação das três tabelas do exemplo. Note que um usuário possui nome, data de nascimento, um perfil e um departamento, e que as tabelas de departamento e perfil contêm apenas um código e uma descrição. A aplicação final em execução deverá se parecer com a Figura 1. Observe que a interface gráfica consiste em um formulário de inclusão e uma lista de usuários cadastrados. O formulário possuirá validação no lado do cliente (através de chamadas AJAX) e também validação do servidor.

Page 3: Java Magazine 41

Figura 1. Layout da aplicação de exemplo Após fazer o download do projeto no site Java Magazine, importe-o para workspace do Eclipse, acessando File/Menu>Web>Existing Project into Workspace. O projeto deverá parecer com a Figura 2.

Figura 2. Estrutura do projeto após importação Listagem 1. Script de criação do banco de dados. CREATE TABLE ‘departamento’ (

Page 4: Java Magazine 41

‘id’ bigint(20) NOT NULL auto_increment , ‘nome’ varchar(30) NOT NULL , PRIMARY KEY (‘id’) , UNIQUE KEY ‘id_departamento’ (‘id’)

); CREATE TABLE ‘perfil’ (

‘id’ int(11) NOT NULL auto_increment , ‘nome’ varchar(30) NOT NULL , PRIMARY KEY (‘id’)

); CREATE TABLE ‘usuario’ (

‘nome’ varchar(45) , ‘id’ bigint(20) NOT NULL auto_increment, ‘id_departamento’ bigint(20) NOT NULL, ‘data_nascimento’ datetime , PRIMARY KEY (‘id’)

); web.xml e entidades Vamos então à descrição do projeto mostrando como criá-lo passo a passo. Primeiro precisamos alterar o arquivo WEB-INF/web.xml (Listagem 2). No Struts 1.x era necessário configurar um servlet para tratar todas as requisições (normalmente *.do). no Struts 2 configuramos um filtro: <filter>

<filter-name>filtroStruts</filter-name> <filter-class> org.apache.struts2.dispatcher.ilterDispatcher </filter-class>

</filter> <filter-mapping>

<filter-name>filtroStruts</filter-name> <url-pattern>/*</url-pattern>

</filter-mapping> Observe que o padrão de URL que adotamos aqui foi “/*”, o que significa que o filtro irá interceptar todas as requisições da aplicação. No web.xml você vê que também configuramos um servlet chamado dwr que irá interceptar as chamadas AJAX para validação (veja mais sobre isso no final do artigo). Tendo o arquivo web.xml ajustado, construímos as classes Usuario, Perfil e Departamento, mostradas nas Listagens 3,4 e 5. Listagem 2. Configuração do filtro-WEB-INF/web.xml <web-app>

<filter> <filter-name>filtroStruts</filter-name> <filter-class>

org.apache.struts2.dispatcher.FilterDispatcher </filter-class>

</filter> <filter-mapping>

<filter-name>filtroStruts</filter-name>

Page 5: Java Magazine 41

<url-pattern>/*</url-pattern> </filter-mapping>

<servlet>

<servlet-name>dwr</servlet-name> <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class> <init-param>

<param-name>debug</param-name> <param-value>true</param-value>

</init-param> </servlet> <servlet-mapping>

<servlet-name>dwr</servlet-name> <url-pattern>/dwr/*</url-pattern>

</servlet-mapping> </web-app> Listagem 3. Entidade Usuário – Usuario.java public class Usuario{

public Long id; public String nome; public Departamento departamento; public Perfil perfil; public Date dataNascimento; public Usuario() {} //… getters/setters

} Listagem 4. Entidade Perfil – Perfil.java public class Perfil {

public Integer id; public String nome; public Perfil(){} //...getters/setters

} Listagem 5. Entidade Departamento – Departamento.java public class Departamento {

public Integer id; public String nome; public Departamento() { }

Page 6: Java Magazine 41

//...getters/setters } Action de persistência A action que fará o controle da persistência do usuário será UsuarioAction (Listagem 6). Os seguintes são os atributos da classe: · Usuário usuario – O objeto que será instanciado pelo Struts e preenchido com os

valores enviados pelo formulário web. O Struts cuida da instanciação e do mapeamento entre os campos do formulário e os atributos do objeto.

· Collection<Perfil>perfis – A lista de perfis utilizada para popular um <select> no formulário web. O Struts obtém essa lista através do método getPerfis() da Action no momento de geração do formulário.

· Collection<Departamento>departamentos – Lista também utilizada para popular um <select> no formulário (o Struts obtém a lista usando getDepartamento()).

A classe UsuarioAction estende ActionSupport e implementa Peparable. Fizemos isso para reutilizar funcionalidades oferecidas pelo Struts; no entanto a Action não precisa depender de outras classes ou interfaces. A classe ActionSupport fornece alguns métodos e atributos importantes para facilitar o desenvolvimento, como: GetText(String chave) – Retorna a string correspondente do ResourceBundle, e é muito útil para internacionalização. GetActionMessages() – Retorna as mensagens de erro referentes à validação da Action. Por implementar Preparable, a nossa Action possui o método prepare(), o qual no exemplo popula as listas de perfis e de departamentos. Este método é chamado antes de qualquer regra de controle que a Action possa ter. Veja sua implementação: public void prepare() { perfis = ServicoDeDados.get.Perfis(); departamentos = ServicoDeDados.getDepartamentos(); } A classe ServicoDeDados (não listada mas incluído no download) é responsável em fazer os acessos ao banco de dados. Por simplicidade, sua implementação utiliza apenas JDBC diretamente. Para uma aplicação em produção, recomendo implementar o acesso a dados com o design pattern DAO (ou frameworks de persistência como Hibernate). Vejamos mais dois métodos de UsuarioAction. O método salvar () será chamado quando for postado o formulário web com os dados do usuário: public String salvar () { ServicoDeDados.saveUsuario(usuário); return SUCCESS; } O método faz a gravação dos dados do usuário no banco de dados através da classe ServicoDeDados. Depois, como nas Actions do antigo Struts, é necessário definir qual o retorno da aplicação. A mudança no Struts 2 é que para isso agora informamos apenas uma string que será o Result. No caso, retornamos a constante string SUCCESS, que é herdada de ActionSupport:

Page 7: Java Magazine 41

O método input() de UsuarioAction somente retorna o Result SUCCESS. Usamos este método para fazer um redirecionamento para a página JSP contendo o formulário. A configuração inicial do Struts 2, faz com que alguns métodos não sejam submetidos às regras de validação. Um deles é o input(). Veremos adiante mais detalhes sobre o novo sistema de validação do Struts 2. Listagem 6. Action responsável em executar a persistência do usuário – UsuarioAction.java public class UsuarioAction extends ActionSupport implements Preparable{

private Usuario usuario; private Collection<Perfil> perfis; public Collection<Departamento> departamentos; public String salvar() {

ServicoDeDados.saveUsuario(usuário); return SUCESS;

} public String input() { return SUCESS; } public void prepare() {

perfis = ServicoDeDados.getPerfis(); departamentos = ServicoDeDados.getDepartamento();

} //... getters/setters

} Configuração O arquivo de configuração do Struts (struts-config.xml) foi renomeado para struts.xml e agora fica junto das classes da aplicação em WEB-INF/classes. O struts.xml do nosso exemplo é mostrado na Listagem 7. Vamos analisar cada parte desse arquivo, começando pelo elemento <include>: <include file = “struts-default.xml”/> Aqui incluímos as configurações padrão do Struts com os tipos de Result disponíveis e os Interceptors que acompanham o framework. Normalmente você ira fazer essa inclusão para reutilizar as configurações básicas do Struts. Depois encontramos: <package name = “default” extends = “struts-default”> Os packages (pacotes) são uma nova forma de organizar e agrupar as configurações, definindo Actions, Results e Interceptors. Pacotes de configurações podem também ser estendidos através do atributo extends. Note que nosso pacote declarado no trecho acima estende o pacote de configurações padrão, definido no arquivo struts-default.xml (este arquivo está dentro da biblioteca struts2-core.jar).

Page 8: Java Magazine 41

Prosseguimos no struts.xml, encontramos a definição das Actions. Um elemento <action> configura uma Action e tem os atributos name, class e method: <action name = “inserirUsuario!*” class = “br.com.jm.action.UsuarioAction“ method = “{1}”> O atributo name define o mapeamento da Action. Observe que no exemplo ele possui o caractere “*” como sufixo. Como no antigo Struts, as Actions podem utilizar caracteres coringa (wildcards) nos seus mapeamentos. A diferença é que agora se pode também usar coringas para definir qual método será chamado na Action. Por isso definimos o método input() e salvar () na classe UsuarioAction. Por exemplo, se a chamada da Action for inserirUsuario! input. action o método input() será chamado. Caso seja inserirUsuario!salvar.action, será a vez do método salvar(). Esse recurso substitui as DispatchActions do Struts 1.x e traz maior flexibilidade ao desenvolvimento. E para o Struts saber qual método chamar, configuramos o atributo method com o valor {1} que é uma referencia ao caractere “*” do mapeamento da Action. Lembrando que o que estiver entre o caractere “!” e a extensão “.action” definirá o nome do método a ser invocado. Em seguida são definidos os Results, que no exemplo são bem simples. Todos eles redirecionam o usuário a um arquivo JSP, formUsuario.jsp: <result name = “success”>/formUsuario.jsp</result> <result name = “input”>/formUsuario.jsp</result> O Result input é utilizado para redirecionamento em caso de erro de validação. É a mesma idéia do atributo input das Actions na versão anterior do Struts. Continuando, note que a Action possui um Interceptor configurado: <interceptor-ref name = “paramsPrepareParamsStack"/> O objetivo dos Interceptors é adicionar códigos que serão executados antes ou depois das chamadas às Actions. O elemento <interceptor-ref> mostrado anteriormente faz uma referencia a uma pilha (stack) de Interceptors. As pilhas de Interceptors são apenas uma forma de organizá-los em grupo para facilitar a reutilização da mesma configuração. No exemplo essa configuração tem o objetivo de adicionar à Action regras de validação e uma chamada ao método prepare() , que carregara dados necessários para o formulário. A definição de Interceptors esta dentro do arquivo struts-default.xml, que importamos no começo do struts.xml. O nome dos Interceptors responsáveis pela chamada ao método prepare() e ao código de validação, são respectivamente prepare e validation . Como ambos estão dentro da pilha paramsPrepareParamsStack , não precisamos fazer uma referencia direta a cada um. Veja mais detalhes e exemplos de Interceptors no quadro “tipos de Interceptors”. Listagem 7. Configuração do Struts – struts.xml <struts>

<include file=”struts-default.xml”/>

<package name=”default” extends=”struts-default”>

<action name=”inserirUsuario!*” class=”br.com.jm.action.UsuarioAction” method=”{1}”>

<result name=”success”>/formUsuario.jsp</result>

Page 9: Java Magazine 41

<result name=”input”>/formUsuario.jsp</result> <interceptor-ref name=”paramsPrepareParamsStack”/>

</action>

<action name=”listaUsuarios” class=”brl.com.jm.action.ListaUsuariosAction” method=”listaUsuarios”>

<result name=”success”>/listaUsuarios.jsp</result> </action>

</package> </struts> Action de Listagem A próxima Action configurada é utilizada para a listagem de usuários: <action name = “ listaUsuario “

class = “ br.com.jm.action.ListaUsuariosAction “ method = “ listaUsuario “ >

<result name = “sucess”>/listaUsuarios.jsp</result> </action > Esta Action possui apenas um Result que redireciona para um arquivo listaUsuarios.jsp . Seu método de execução é listaUsuarios() e sua classe é ListaUsuariosAction (Listagem 8). A classe da Action é bem simples, contendo um método listaUsuarios () que popula o atributo usuarios com public string listaUsuarios () {

usuarios = ServicoDeDados.getUsuarios() ; return SUCCESS;

} O arquivo listaUsuarios.jsp possui uma tag de listagem de usuários, <s:iteratorvalue = "usuarios" >. Esta tag utilizara o método getUsuarios() fornecido pela Action para obter a lista dos usuários. Listagem 8. Action responsável em popular a lista de Usuarios – ListaUsuariosActions.java public class ListaUsuariosActions extends ActionsSupport {

public Collection<Usuario> usuarios; public String lista Usuarios() {

usuarios=ServicoDeDados.getUsuarios(); return SUCCESS;

} // ... getters/setters

} Construindo a parte gráfica Tendo apresentado o detalhes de configuração da aplicação, podemos passar às páginas JSP: index.jsp, formUsuario.jsp e listaUsuarios.jsp.

Page 10: Java Magazine 41

index.jsp A página index.jsp (listagem 9), embora seja bem pequena, é pouco diferente do usual. Nela utilizamos as novas tags do Struts, que estão muito mais poderosas. Além de as tags estarem agora definidas em um só arquivo.tld, elas trazem uma série de novas funcionalidades e novos componentes, alguns já com suporte a AJAX. Veja mais no quadro “Novos componentes gráficos”. A primeira tag que você irá notar no arquivo index.jsp será a <s:url>, que apenas reescreve uma URL considerando o contexto em que a aplicação está sendo executada: <s:url value = “/css/styles.css”/> A próxima é <s:head>, que define parte da seção HEAD do HTML. Esta tag escreve códigos JavaScript ou CSS necessários para alguns componentes gráficos do Struts. Em nosso exemplo, como utilizaremos AJAX para validação do formulário web, definimos que a tag deve adicionar o código JavaScript através do atributo theme=“ajax”. <s:head theme = “ajax”/> O resultado da tradução da tag <s:head> será uma seqüência de importações de scripts. Veja uma dessas importações: <scriplanguage = “JavaScript” type = “ text/javascript”

src = “/struts2/struts/dojo//dojo.js”></script> Neste caso, o arquivo JavaScript importado é o dojo.js, do toolkit DOJO (veja links). Os novos componentes gráficos do Struts 2 usam esse toolkit tanto para chamadas AJAX como para tratamento de eventos JavaScript. Continuando no arquivo index.jsp, temos duas tags <s:div>. Esta nova tag é às vezes chamada de “Remote DIV”, pois seu conteúdo pode ser definido com uma requisição remota através do seu atributo href. O conteúdo dessa tag também pode ser o resultado de algum outro evento na página, por exemplo, o retorno de uma postagem de formulário. Veja a definição dos DIVs. <s:div id = “formulario” href = “/inserirUsuario!input.action”

theme = “ajax” ></s:div> <s:div id = “listaUsuario” href = “/listaUsuarios.action”

theme = “ajax” listenTopics = “listagemUsuarios”> </s:div> Os dois DIVs têm o atributo href configurado, portanto seu conteúdo virá das URLs fornecidas. Observe que as URLs são exatamente as definições das Actions no struts.xml: inserirUsuario e listaUsuarios. Um detalhe importante sobre o segundo DIV é o atributo listenTopics. Este atributo transforma o DIV em um listener de tópicos. Os tópicos, neste contexto, são utilizados para manter um canal de comunicação entre os componentes gráficos, e são criados e mantidos no cliente, ou seja, no navegador do usuário. Um listener de tópicos é um componente que recebe avisos quando alguma mensagem chega ao tópico no qual ele se registrou. Listagem 9. JSP que define o layout do sistema – índex.jsp <%@ taglib prefix=”s” uri=”/tags”%> <html>

Page 11: Java Magazine 41

<head> <title>JM Struts 3</title> <link href=”<s:url value=”/css/styles.css”/>” rel=”stylesheet” type=”text/css” /> </head> <body> <s:div id=”formulario” href=”/inserirUsuario!input.action” theme=”ajax”></s:div> <s:div id=”listaUsuario” href=”/listaUsuarios.action” theme=”ajax” listen Topics=”listagemUsuarios”></s:div> </body> </html> listaUsuarios.jsp A exibição da lista de usuários é feita pela página listaUsuarios.jsp, mostrada na Listagem 10. A tag que cria as linhas com os dados é <s:iterator>. <s:iterator value = “usuarios”> ... </s:iterator> O atributo value = “usuarios” significa que será chamado o método getUsuarios() na Action que a precedeu, ou seja, a Action listaUsuarios. O retorno desse método deve ser uma collection (objeto que implemente java.util.Collection) para que a tag <s:iterator> consiga criar um laço e exibir as linhas e tabela. A tag <s:property>, utilizada dentro do laço, exibe os atributos do usuário, definidos pela propriedade value. A tag <s:date> é utilizada para exibir e formatar a data de nascimento: <s:property value = “nome”/> <s: date name = “dataNascimento” format = “dd/MM/yyyy”/> Antes de continuar, vamos revisar como está definido o fluxo da aplicação para exibir a lista de usuários: 1) O componente <s:div>, na página index.jsp, cria um “Remote DIV” e define seu conteúdo como sendo o retorno da requisição à Action listaUsuarios. 2) Esta Action popula seu atributo usuarios e redireciona ao arquivo listaUsuarios.jsp. 3) Dentro do JSP utilizamos a tag <s:iterator> para acessar o atributo usuarios e exibir suas informações como linhas de uma tabela. Listagem 10. Arquivo JSP que exibe a lista de usuários – listaUsuarios.jsp <%@ taglib prefix= “s” uri= “/tags” %> <span class =”titulo”>Lista de usuários</span> <table>

<tr> <td class=”head”>ID</td> <td class=”head”>Nome</td> <td class=”head”>Departamento</td> <td class=”head”>Perfil</td> <td class=”head”>Data Nascimento</td>

</tr>

Page 12: Java Magazine 41

<s:iterator value=”usuarios”> <tr>

<td><s:property value=”id” /></td> <td><s:property value=”nome” /></td> <td><s:property value=”departamento.nome” /></td> <td><s:property value=”perfil.nome” /></td> <td><s:date name=”dataNascimento” format=”dd/MM/yyyy” /></td>

</tr> </s:iterator>

</table> formUsuario.jsp e as novas tags A página formUsuario.jsp (Listagem 11) começa verificando se há erros de validação para os campos. Caso existam, estes são impressos. Antes de partir para o restante da página (o formulário em si), vamos entender um pouco mais sobre as novas tags e o modelo de templates/temas do Struts 2. As novas tags do Struts podem seguir um template que traz, além de definições gráficas, códigos que definem atributos com valores padrão e importação de JavaScripts. Os templates do Struts 2 normalmente são escritos utilizando o framework FreeMarker. Além dos templates, o Struts 2 traz os chamados temas (themes). Esses temas são basicamente um conjunto de templates nos quais as tags deverão se basear, desde que configuradas para isso. Alguns temas já vêm prontos para serem usados, como simple, xhtml, css_html e ajax. Cada um define estruturas e elementos de layout diferentes. Os temas são definidos através do atributo theme de cada tag. No exemplo, usamos o tema ajax, pois validaremos nossos campos através de chamadas AJAX: <s:form action = “inserirUsuario!Salvar” method = “post” theme = “ajax” validate = “true” id = “formUsuario”>

Combinando o uso do tema ajax com validate = true, informamos que cada campo deverá ser validado no evento onBlur. O evento onBlur é disparado quando um componente do formulário perde o foco (por exemplo, passando-se de um componente para outro). Outra característica do tema ajax, é que o formulário será dividido em um layout de duas colunas. A primeira coluna terá o nome/label do campo e a segunda o campo propriamente dito. Isso nos economiza algumas linhas de código para definir o layout do formulário, além de deixar o código JSP mais legível. O conteúdo do formulário utiliza os novos componentes e tags do Struts 2. Veja que todos os campos do formulário têm as propriedades name e label em comum. A propriedade name define qual atributo será procurado e configurado na Action do formulário, e label define o texto exibido ao lado do campo na primeira coluna. Veja um exemplo: <s:extfield name = “usuario.nome”

label = “Nome” size = “40”/> A tag <s:datepicker> cria um campo texto e ao lado um link para um calendário popup (veja a Figura 3). A propriedade format define qual o padrão de exibição da data: <s:datepicker id = “usuário.dataNascimento”

name = “usuário.dataNascimento” label = “data de Nascimento” format = “#dd/#M/#yyyy” theme = “ajax”/>

Page 13: Java Magazine 41

Os dois componentes <s:select> criam as opções de Departamento e Perfil: <s:select list = “departamentos” listKey = “id”

listValue = “nome” name = “usuário.departamento.id” label = “Departamento”/>

<s:select list = “ perfis “ listKey = “ id “

listValue = “nome” name = “usuário.perfil.id” label = “Perfil”/>

Figura 3. Componente de calendário para campos de data O atributo list define qual método deverá ser chamado na Action precedente para obter a lista de opções. Para a lista de possíveis perfis será chamado o método getPerfis(), já que a propriedade esta configurada com o valor perfis. Seguindo a mesma lógica, o método chamado para departamentos será getDepartamentos(). Os outros dois atributos, listKey e listValue, definem, respectivamente, o atributo value da tag HTML <option> e o texto apresentado para as opções. Veja o resultado: <select name = “usuário.perfil.id”

id = “formUsuario_usuario_perfil_id” onblur = “validate(this);”>

<option value = “1”>Administrador</option> <option value = “2”>Operador</option>

</select> Finalizando a descrição do formUsuarios.jsp temos o botão de enviar, que possui três atributos interessantes : resultDivld, notifyTopics e prelnvokeJS. <s:submit value = “Cadastrar” resultDivld = “formUsuario” notifyTopics = “listagemUsuarios” prelnvokeJS = “confirm (‘Confirma a inclusão de usuário?’);”/>

No Struts 2, é possível informar qual DIV deverá ser atualizado com a resposta de alguma requisição. Ou seja, a requisição é efetuada mas a resposta é exibida dentro do DIV especificado. No caso do formulário, optamos por atualizar apenas o DIV do formulário deixando o resto da página estático. Essa configuração foi feita através do atributo resultDivlD. O atributo notifyTopics define quais tópicos receberão uma mensagem de aviso. O tópico informado é o mesmo que o DIV da listagem de usuários, pois queremos que a listagem seja atualizada assim que o usuário acionar o botão Enviar. O último atributo é o prelnvokeJS, que invoca um código JavaScript antes do envio dor formulário. Utilizamos este recurso para pedir a confirmação do usuário sobre a inclusão dos dados. Note que não programamos nenhuma linha de JavaScript para os eventos do formulário, nem para a confirmação de envio como nem para mostrar o calendário.

Page 14: Java Magazine 41

Listagem 11. Arquivo JSP que exibe o formulário de cadastro de usuários-formUsuario.jsp <%@ taglib prefix= “s” uri= “/tags” %> <s:if teste=”hasErrors()”>

<b>Erros:</b> <s:fieldderror />

</s:if> <s:form action=”inserirUsuariosalvar”

method=”post” id=”formUsuario” validate-“true” theme=”ajax”> <span class=”titulo”>Cadastro de usuário:</span> <s:textfield name=”usuário.nome” label=”Nome” size=”40” /> <s:datepicker id=”usuario.dataNascimento”

name=”usuario.dataNascimento” label=”Data de nascimento” format=”#dd/#M/#yyyy” theme=”ajax” />

<s:select list=”departamentos” listKey=”id”

listaValue=”nome” name=”usuario.departamento.id” label=”Departamento” />

<s:select list=”perfis” listKey=”id” listValue=”nome”

name=”usuario.perfil.id” label=”Perfil” /> <s:submit value=”Cadastrar” resultDivId=”formUsuario”

notifyTopics=”listagemUsuarios” preInvokeJS=”confirm(‘Confirma a inclusão de usuário?’);” />

</s:form> Regras de validação Vamos agora configurar as regras de validação. Para isso criamos o arquivo UsuarioAction-validation.xml, mostrado na Listagem 12. A primeira regra de validação é para o campo usuário.nome, que é um campo de texto obrigatório. Sua regra de validação é requiredstring: <field name = “usuário.nome”>

<field-validador type = “requiredstring”> <message > Campo nome é obrigatório </message> </field-validator>

</field> A Segunda regra é um pouco diferente pois a propriedade type está configurada como validatorPerfil: <field-validator type = “validatorPerfil”> Esse é um validador personalizado, mostrado na Listagem 13. Para adicionar validadores personalizados como este é preciso alterar arquivo WEB-INF/classes/validators.xml. Definimos o nosso com a seguinte linha: <validator name = “validatorPerfil”

Page 15: Java Magazine 41

class = “ br.com.jm.validador.ValidadorPerfil”/> Este validador verifica se um usuário configurado com o perfil de Administrador pertence ao departamento de Informática. Lembre-se que no evento onBlur de cada campo, é feita uma requisição AJAX. Por isso configuramos um Servlet chamado dwr para tratar essas requisições, como já mostramos na Listagem 2. A Figura 4 mostra o formulário após a postagem com campos inválidos. O Struts 2 utiliza o toolkit DWR para tratar as requisições AJAX de validação. Listagem 12. Arquivo que contém as regras de validação – UsuarioAction-validation.xml <!DOCTYPE validators PUBLIC “-//OpenSymphony Group//XWork Validator 1.0.2//EN” http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd> <validators>

<field name=”usuario.nome”> <field-validator type=”requiredstring”>

<message>Campo nome é obrigatório</message> </field-validator>

</field> <field name=”usuario.perfil.id”>

<field-validator type=”validadorPerfil”> <message>Administradores devem pertencer ao

departamento de informatica.</message> </field-validator>

</field> </validators> Listagem 13. Arquivo que contém as regras de validação – UsuarioAction-validation.xml public class ValidadorPerfil extends FieldValidatorSupport {

public void validate(Object object) throws ValidationException { Usuario usuario = (Usuario) getFieldValue(“usuario”, object);

Integer idPerfil – usuario.getPerfil() !=null ? usuario.getPerfil() .getId() : null;

Integer idDepartamento = usuario.getDepartamento() !=null ? usuario .getDepartamento().getId() : null;

if (!isPerfilDepartamentoValid(idPerfil, idDepartamento)) { addFieldError(getFieldName(), object);

} }

private boolean isPerfilDepartamentoValid(Integer idPerfil,

Integer idDepartamento) {

if (idPerfil == null l l idDepartamento == null l l (idPerfil.equals(1) && “idDepartamento.equals(1))) return false;

return true; }

Page 16: Java Magazine 41

}

Figura 4. Erros de validação do formulário Conclusões Aqui exploramos apenas alguns recursos dos Struts 2. Suas novas funcionalidades e sua nova arquitetura sanam vários problemas existentes no desenvolvimento web com versões anteriores do Struts, além de trazer mais flexibilidade para a construção da camada de visualização. As novas tags do Strutus 2, que seguem o modelo templates, tornam a construção de aplicações web mais produtiva, oferecendo um layout padronizado e deixando o desenvolvedor mais livre das tags HTML. E os novos componentes gráficos tornam os formulários mais ricos e mais interativos, além de diminuir a quantidade de código JavaScript nas páginas JSP. A nova versão do Struts ainda levará algum tempo para amadurecimento e correção de bugs, mas já mostra que o projeto está mais vivo do que nunca. Vale a pena explorar Struts 2! LINKS: struts.apache.org/2.x Para página inicial do Struts 2. getahead.ltd.uk/dwr Biblioteca para acessar componentes java no servidor via JavaScript dojotoolkit.org Toolkit para JavaScript. Mudanças e novidades no Struts 2 Actions As actions no Struts 2 agora são independentes de qualquer outra classe, como HttpServletRequest e HttpServletResponse, e podem ter apenas um método execute(): public class MinhaAction{ public String execute (){ return “sucesso”; } } Essa independência faz com que possamos criar testes unitários para as Actions com mais simplicidade.

Page 17: Java Magazine 41

Arquivo de Configuração O arquivo de configuração do Struts mudou de nome e de localização, de WEB-INF/struts-config.xml para WEB-INF/classes/struts.xml. A estrutura também mudou, como é mostrado no exemplo do artigo. Os ActionsForwards foram substituídos pelos Results. Os Results seguem a mesma idéia que seu predecessor porém dão maior flexibilidade para definir o tipo de retorno que será enviado ao usuário. Alguns exemplos de Results são JSP, JasperReports, XSL, texto simples, Action Redirection e frameworks de templates como Tiles, Velocity e FreeMaker. Veja o exemplo de como definir uma Action e um JSP simples como Result. <action name = “salvarUsuário” class = “br.com.action.SalvaUsuarioAction” method = “salvar”> <result name = “success” > / sucesso.jsp </result> </action> Aqui definimos um Result para o JasperReports: <result name = “success” type = “jasper”> <param name = “location” > /jasper/arquivo_compilado.jasper</param> <param name = “dataSource”>meuDataSource</param> <param name = “format”> PDF</param> </result > Nessa configuração, location define a localização do arquivo compilado do Jasper Reports; dataSource define qual método deverá ser chamado na Action para obter a fonte de dados, ou seja, getMeuDataSource (); e format determina qual o formato de saída do relatório, PDF neste exemplo. Interceptors Os Interceptors são outra grande novidade do Struts 2 e têm papel fundamental na nova arquitetura do framework. Sua principal intenção é adicionar novas funcionalidades às Actions sem a necessidade de alterá-las. Veja o quadro “Tipos de Interceptors” para conhecer vários tipos de Interceptors e o exemplo do artigo para vê-los em ação. Adeus aos FormBeans No Strusts 2 o FormBeans não são mais utilizados. Para cada request uma nova instância da Action é criada e preenchida com os valores postados. Os dados postados estarão disponíveis dentro da sua Action como atributos da classe ou em um Map. Veja este exemplo: public class MinhaAction implements ParameterAware {

public Map parameters; public String execute () {...} //getters e setters

} A interface ParemeterAware implementada faz com que o Struts saiba que a classe tem o método setParameters (), e assim a configure com os parâmetros do request. Para adicionar esta funcionalidade à Action é necessário configurar um Interceptor. A configuração ficaria assim: <action name = “minhaAction”

class = “br.com.jm.MinhaAction”>

Page 18: Java Magazine 41

<result name = “success”>sucesso.jsp </result> <result name = “error”>erro.jsp</result> <interceptor – ref name = “servlet - config”/>

</action> Existem outras interfaces que a Action poderá implementar para obter mais informações sobre a aplicação, cada uma fornecendo um objeto diferente. São elas: ServletContextAware, ServletRequestAware, ServletResponseAware, RequestAware, SessionAware, AplicationAware e PrincipalAware Validação Para cada Action podemos criar um arquivo NomeDaAction-validation.xml que conterá as validações desejadas. Este arquivo deve estar localizado dentro do mesmo pacote que a Action. Veja um exemplo de validação para um campo obrigatório: <validators>

<field name = “nome”> <field-validator type = “requiredstring”>

<message> Campo nome obrigatório </message> </field-validator>

</field> </validators> Para adicionar a funcionalidade de validação à Action, você apenas precisa definir o interceptor validation, como no seguinte exemplo: <action name = “salvarUsuario”

class = “br.com.jm.action.SalvarUsuarioAction” method = “salvar”>

<result name = “success”>/ sucesso.jsp</result> <interceptor-ref name = “validation”>

</action > Neste exemplo, assim que a action salvarUsuario for requisitada, o Interceptor de validação procura o arquivo SalvaUsuarioAction-validation.xml e identifica os campos que devem ser validados. Caso todos estejam corretos, a Action é executada. Este mesmo arquivo pode ser utilizado pelos componentes de validação AJAX, deixando suas regras de validação centralizadas. No exemplo do artigo e visto na prática como fazer esta validação. Integração com Spring, Hibernate e outros frameworks Para os desenvolvedores que projetam suas aplicações utilizando os frameworks Spring Hibernates, boas notícias acompanham o Struts 2. A instanciação das Actions pode ser delegada ao Spring permitindo o uso de Injeção de Independência. Isso faz com que as dependências das Actions sejam resolvidas no momento de instanciação. A configuração do Spring é feita no arquivo WEB-INF/application.xml. Veja o exemplo simples deste arquivo: <beans>

<bean id = “usuarioDAO” class = “br.com.jm.UsuarioDAO”/> <bean id =“employeeAction” class = “br.com.jm.MyAction”>

<constructor-arg ref = “usuarioDAO”/> </bean>

</beans>

Page 19: Java Magazine 41

Mesmo para quem não conhece Spring, este XML não será difícil de entender. No momento da instanciação da classe MyAction o Spring irá passar para o construtor (em <constructor-arg >) o bean usuarioDAO como parâmetro. Assim, no momento em que a Action for executada, você terá este objeto disponível para uso. O Spring também fornece a facilidade de controle de transações para acesso a dados, deixando a integração com o Hibernate mais elegante e gerenciado, evitando usar o pattern Open Session in View (hibernate.org/43.html). O Struts 2 possui integração com outros frameworks além do Spring e Hibernate, incluindo JSF, JUnit, JasperReports, Pico Container, Quartz, SiteMesh e Tiles.

Tipos de Interceptors

Os Interceptors são objetos utilizados para execução de códigos antes ou depois de Actions. Com eles, é possível adicionar funcionalidades à aplicação sem alterar o código das Actions. Veja a seguir alguns exemplos de interceptors. Alias Interceptor Utilizado para Actions que possuem nomes de atributos diferentes dos nomes dos parâmetros enviados no request. Nesse caso você pode utilizar o interceptor para mapear tais parâmetros para os atributos. Veja um exemplo onde o parâmetro nomeUsuario será mapeado para o atributo da Action nomeFuncionario: <action name = “minhaAction “class =”br.com. jm.MinhaAction>

<param name = “aliases” > #{ ‘nomeUsuario’ }: ‘nomeFuncionario’}</param>

<interceptor-ref name = “alias”/> <result name = “success” >posExecucaoAction.jsp</result>

</action> Execute and Wait Interceptor Utilizado para rodar serviços em segundo plano, enquanto é mostrado ao usuário uma mensagem a negado. Exemplo de configuração: <action name = “minhaAction “

class = “ br.com.jm.MinhaAction “> <interceptor – ref name = “completStack “/> <interceptor- ref name “execAndWait “/> <result name = “wait “> mensagemDeEspera.jsp </result> <result name = “success “> posExecucaoAction.jsp </result>

</action> File upload Interceptor Criará dentro da Action uma referencia ao arquivo que existe no request. Isso facilitara o upload, visto que com esta referencia o desenvolvedor apenas precisa definir o local onde deseja gravar o arquivo. Veja um exemplo de configuração: <action name = “minhaAction”

class = “br.com.jm.MinhaAction”> <interceptor-ref name “ fileUpload “>

<param name = “ allowedTypes “> image/png,image/gif,image/jpeg

Page 20: Java Magazine 41

</param> </interceptor-ref>

</action> Para obter a referencia e outras informações sobre o arquivo, você precisa criar os seguintes métodos na sua Action: setUpload() , setUploadContentType() e setUploadFileName(). Exception Interceptor Permite que você mapeie uma exceção Java para um Result. Exemplo: <action name = “minhaAction”

class = “br.com.jm.MinhaAction”> <interceptor-ref name = “exception”/> <interceptor-ref name = “basicStack”/> <exception-mapping

exception = “ java.lang.Exception” result = “erro_personalizado”/>

<result name = “erro_personalizado”/> paginaDeErro.jsp </result>

<result name = “sucesso”>sucesso.jsp</result> </action > Token Interceptor Trata duplo-cliques em botões de envio, detectando o os e reenviando o usuário para outra página. Um duplo-clique pode causar erros graves, como por exemplo, dois cadastros no banco de dados com as mesmas informações (ou dois débitos em sua conta bancaria). A página para qual o usuário será redirecionado é definida pelo Result invalid.token. Configuração: <action name = “someAction”

class = “com.exemples.SomeAction”> <interceptor-ref name = “ token “/> <result name = “invalid.token”> erro.jsp </result>

</action> <result name = “successor”> sucesso.jsp </result>

</action> Criando novos Interceptors Você pode também criar seu próprio interceptor implementado a interface com.opensymphony.xwork.interceptor.Interceptor e definindo o no arquivo struts.xml: <interceptor name = “meuInterceptor”

class = “br.com.jm.MeuInterceptor”/>

Novos componentes gráficos

O Struts 2 traz uma coleção de novas tags para facilitar a construção da camada de visualização de aplicativos web. Veja aqui alguns exemplos: DoubleSelect Permite que um <select> seja atualizado a partir de outro. Exemplo: <s:doubleselect label = “Estados” name = “ regiao”

Page 21: Java Magazine 41

list = “ { ‘Nordeste’ , ‘Sudeste’ } value = “Nordeste” doubleValue = “Sergipe” doubleList = “ top = = ‘Sudeste’ ? {Minas Gerais’,

‘Sao Paulo’ }: {‘Sergipe’, ‘Alagoas’}” doubleName = “estado” emptyOption = “true”/>

Neste exemplo (Figura Q1) o primeiro <select> terá a região e o segundo, os estados correspondentes. Neste exemplo simplificado, quando for selecionada a região Nordeste, os estados de Sergipe e Alagoas serão carregados; ao selecionar Sudeste teremos os estados de Minas Gerais e São Paulo. Note que os valores neste exemplo foram populados diretamente no JSP, mas poderiam vir através de uma Action.

Figura Q1. Componente DoubleSelect com a região Nordeste selecionado Option Transfer Select Componente muito útil para exibir de maneira clara opções aos usuários. Permite a manipulação e a ordenação das opções (Figura Q2). Veja como utilizá-lo: <s:optiontransferselect

label = “Selecione suas cores favoritas” name = “esquerda” doubleName = “direita” leftTitle = “ Cores “ rightTitle = “ Cores Escolhidas” list = “ { ‘Branco’ , ‘Preto’, ‘Azul ’ } “ doubleList = { “ } ‘/>

Page 22: Java Magazine 41

Figura Q2. Option Transfer Select com a cor “Branca” selecionada Novamente aqui as opções poderiam ser populares através da Action. Árvore Componentes gráfico para criação de árvores (Figura Q3). Veja como criá-lo : <s: tree label = “ Arvore Struts 2 “ id = “ arvore “

theme = “ ajax “ treeSelectedTopic = “ arvoreSelecionada “> <s: treenode theme = “ajax “label = “Raiz “id = “ raiz “> <s: treenode theme = “ajax “label = “Folha 1 “

id = “ folha 1 “/> <s: treenode theme = “ajax “label = “Folha 2 “

id = “ folha 2 “/> <s: treenode theme = “ajax “label = “Folha 3 “

id = “ folha 3 “/> </s: treenode>

</s: tree >

Figura Q3. Árvore de opções criada pela

Page 23: Java Magazine 41

nova tag do Struts 2. A árvore cria um tópico para avisar quando é selecionada, através da propriedade treeSelectedTopic. Dessa forma outros componentes podem monitorar quando algum nó da árvore for selecionado. Tabbled Panel Componente útil para criação de formulários passo a passo, mostrado em ação na Figura Q4. Veja como criá-lo: <s:tabbledPanel id = “ panel” >

<s:panel id = “ primeiro” tabName = “Primeira”> Primeiro Panel selecionado </s: panel >

<s:panel id = “ primeiro” tabName = “Segundo”> Segundo Panel selecionado </s: panel >

<s:panel id = “ primeiro” tabName = “Terceiro”> Terceiro Panel selecionado </s: panel>

<s:tabbedPanel> O conteúdo de cada Panel pode ser gerado a partir de uma chamada remota através das propriedades remote e href: <s:panel remote = “ true “ href = “/ MinhaAction.action”

id = “ ryh1 “ theme = “ ajax “ tabName = “ remote one “/>

Figura Q4. Exemplo de Tabbled Panel.

Page 24: Java Magazine 41

JDBC Ponta a Ponta Parte 1:De conceitos essenciais a configurações e consultas Aprenda os fundamentos de uma APIs mais importante do Java A tecnologia de bancos de dados relacionais é talvez a mais importante em todos os tempos para os Sistemas de Informação. Os bancos de dados mais populares do mercado, desde bancos leves como o HSQLDB, passando pelos livres “convencionais” como MySQL até os pesos-pesados como Oracle são, com poucas exceções, banco de dados relacionais.Desde os primórdios do Java, a importância dos bancos de dados foi reconhecida,e a versão 1.1 do JDK já trazia como componente padrão a API JDBC. Por meio do JDBC,uma aplicação Java pode se conectar a qualquer banco relacional,submeter comandos SQL para execução e recuperar os resultados gerados pela execução desses comandos.além disso,o JDBC permite acesso aos metadados do banco de dados (também conhecido como “catalogo”) permitindo a construção de ferramentas para administração do próprio banco e apoiando o desenvolvimento de sistemas. Embora versões posteriores do Java tenham trazido alguns novos recursos ao JDBC (veja o quadro”Versões da API JDBC”),os recursos presentes já nas primeiras versões da API atendem plenamente às necessidades da maioria das aplicações,mesmo quando há demandas fortes de performance e de suporte a recursos avançados como dados multimídia.O melhor de tudo é que a compatibilidade retroativa foi preservado - ao contrário de APIs como Swing e coleções, que mudaram bastante do Java 1.1 para o Java 2.Então aplicações Java baseadas nas primeiras versões do JDBC não necessitam de modificações para adaptação a versões mais recentes da JVM ou do JDK. Mesmo o desenvolvedor que utiliza mecanismo de persistência objeto-relacional como Hibernate e EJB 3,ou que a prefere frameworks relacionais como iBatis ou Spring JDBC (veja links) necessita de um conhecimento abrangente da API JDBC e de conceitos de bancos de dados relacionais em geral.Afinal, todos esses frameworks e bibliotecas usam o JDBC como base para comunicação com banco de dados. Esta série sobre JDBC atende a dois públicos distintos.Para os iniciantes,que nunca tiveram contato com o JDBC,esta primeira parte apresenta os fundamentos da API.Já para os desenvolvedores com alguma experiência prévia com JDBC,a segunda parte,na próxima edição,apresenta recursos que fazem a diferença entre uma aplicação “de brinquedo” e uma aplicação profissional como transações,o uso de PreparedStatements,e dicas de segurança. Os exemplos serão todos executados sobre o HSQLDB, para que não seja necessário instalar e administrar um banco mais sofisticado.Mas funcionam sem alterações em qualquer outro banco de dados – foram testados MySQL,PostgreSQL,FireBird e Oracle.Como não haveria espaço suficiente para apresentar os procedimentos para inicialização de todas esses bancos,nesta parte focamos no HSSQLDB, e na segunda mostraremos como executar os exemplos (das duas partes) no MySQL e PostgreSQL,os dois bancos de dados livres mais populares. Drivers JDBC Para acessar um banco relacional, uma aplicação Java necessita,além da própria máquina virtual, de um driver JDBC.Este driver é em geral fornecido junto com o banco de dados ou com um download separados pelo próprio fornecedor do banco,sem custo adicional. Se você vem de outros ambientes, como VB e o Delphi, vai se surpreender ao descobrir que o JDBC não necessita de nenhuma configuração prévia, nem que seja instalado um cliente nativo do banco de dados para funcionar. Drivers JDBC são na grande maioria simples biblioteca Java – arquivo JAR que podem ser copiados para qualquer sistema operacional. Não há necessidade de editar arquivos de configuração nem de executar algum painel de controle administrativo.

Page 25: Java Magazine 41

Ø “Drivers JDBC escritos inteiramente em Java são conhecidos como drivers Tipo 4 (Type 4).” Existem no mercado alguns drivers que utilizam códigos nativo (via JNI) para aproveitar código dos clientes nativos proprietários do banco de dados. Mas eles em geral têm performance inferior, e são mais pesados e menos estáveis do que os escritos inteiramente em Java para o mesmo banco. Drivers que usam códigos nativos têm o overhead de traduzir objetos Java para estruturas de dados e tipos nativos do sistema operacional. Já drivers puro-Java pode usufruir dos recursos avançados de conectividade de rede, gerência de memória e segurança embutidas no Java. A Figura 1 compara um driver JDBC escrito inteiramente em Java com drivers que usam códigos nativo. A mesma figura compara drivers JDBC com mecanismo nativo de acesso a bancos de dados, tomando como referencia o popular ODBC do Windows.É utilizado como exemplo o driver JDBC do Oracle.A figura ficaria praticamente igual se em vez de ODBC fosse utilizado o dbExpress do Delphi, o ADO.NET da Microsoft,ou tecnologia similares. Para compilar uma aplicação Java que acessa um banco de dados via JDBC,não é necessário ter nenhum drivers JDBC, não é necessário ter nenhum driver JDBC instalado nem configuração no seu IDE.Mas para executar a aplicação,as classes do driver devem estar no classpath.Isto significa que uma mesma aplicação pode ser executada utilizando qualquer banco de dados que deseje, contanto que a aplicação seja escrita usando apenas comando SQL padronizados,ou então encapsulando com cuidado comandos que utilizam sintaxes proprietárias de cada banco.

Figura 1.Drivers JDBC Tipo 4 versus drivers ODBC do (os drivers JDBC OCI e Thin são ambos fornecidos pela Oracle,no mesmo pacote O banco de dados de exemplo Como indicado na introdução, os exemplos os exemplos deste artigo utilizam o HQSLDB,um banco de dados livre escrito inteiramente em Java,mas foram testados e funcionam com vários bancos.Não entraremos em muitos detalhes sobre HQSLDB,pois ele já foi descoberto extensamente em edições anteriores da Java Magazine.Apresentaremos aqui apenas o suficiente para criar o banco de dados de exemplo e executar as aplicações criadas neste artigo. Baixe o arquivo hqsldb_1_8_0_7.zip de hqsldb.sf.net e descompacte em uma pasta qualquer,por exemplo c:\java ou /home/usuário no Linux.O resultado será a criação de um diretório chamado hqsldb,contendo a documentação,classes Java e fontes HQSLDB existe o arquivohsqldb.jar que contém tanto o driver JDBC quanto o próprio servidor do banco de dados.Este é um caso raro:em geral o driver JDBC é fornecido em um pacote JAR à parte,mas o uso embarcado do HQSLDB justifica este empacotamento atípico.

Page 26: Java Magazine 41

O HQSLDB pode ser executado como um servidor de rede, aceitando conexões TCP/IP da mesma forma que o MySQL ou Oracle, ou então no chamado modo standalone,onde o banco de dados fica embutido dentro da aplicação Java e acessa diretamente os arquivos de dados.Para a aplicação,os dois modos são indiferente.A aplicação apenas indica ou o caminho para um arquivo local,ou a URL para um servidor remoto. Vamos então configurar o classpath do sistema operacional para incluir o driver do HSQLDB.Usuários Windows podem digitar o comando a seguir em um prompt de comandod do MS-DOS: setCLASSPATH=%CLASSPATH%;c:\java\hsqldb\lib\hsqldb.jar Utilize este mesmo prompt de comando para executar os exemplos deste artigo. Caso a janela seja fechada, a configuração do classpath será perdida. Usuários Linux podem usar o comando: exportCLASSPATH=

$CLASSPATH:/home/lozano/hsqldb/lib/hsqldb.jar E, da mesma forma que no Windows, também devem ser executados os exemplos no mesmo shell onde foi configurado o classpath.Em ambos os casos,altere o diretório de instalação do HSQLDB conforme sua preferência. O arquivo hsqldb.jar contém ainda um pequeno aplicativo de administração do banco de dados chamdo DataBase Manager.Todos os bancos de dados fornece uma interface similar para execução de comandos SQL.Um detalhe é que a fornecida pelo HSQLDB é escrita em Java e pode ser usada com outros bancos. A Listagem 1 apresenta a seqüência de comando SQL que cria o banco de dados de exemplo e insere alguns dados de teste.Nosso banco de dados contém um resumo de informações sobre vendas de produtos de uma rede de lojas varejistas. Para executar os comando da Listagem 1,inicie o DataBase Manager do HSQLDB utilizando o comando a seguir: java.org.hsqldb.util.DataBaseManagerSwing O DataBase Manager irá pedir informações para a conexão ao banco de dados.Informe como tipo “HSQLDB Standlone”,URL de conexão “jdbc:hsqldb:file:vendas:shutdown=true”,usuário “as” e senha em branco.Como o banco de dados ainda não existe,ele será automaticamente pelo HSQLDB no caminho especificado – que por omissão é diretório corrente. A Figura 2 apresenta a seqüência de telas do DataBase Manager,desde conexão (criação) do banco de dados ate a execução dos comandos SQL do Script que criam as tabelas e inserem dados de teste.

Page 27: Java Magazine 41

Figura 2.Usando o DataBase Manager do HSQLDB para criar e inicializar o banco de dados de exemplo.Observe que os arquivos vendas.properties,vendas log e vendas Ick formam o banco de dados em si,enquanto que vendas.sql contém script SQL da Listagem 1.

ü Muito cuidado na digitação do URL de conexão, pois qualquer erro fará com que em vez do HSQLDB abrir um banco já existente,ele crie um novo banco.Por este motivo é melhor especificar caminhos completos para os arquivos do banco de dados, em vez de usar caminhos relativos como foi feito por exemplo.

Os comandos de criação das tabelas podem ser salvos em um arquivo texto, por exemplo, vendas.sql,e este arquivo pode ser aberto e executado pelo DataBase Manager.O script para o exemplo esta disponível junto com fontes para download deste arquivo. Observe que, depois da execução dos comandos SQL,a parte esquerda do DataBase Manager se altera,indicando a presença das tabelas recém-criadas.Feche o utilitário e o abra novamente,para garantir que as tabelas e informações não foram perdidas com a finalização do banco de dados.Experimente ainda executar alguns comando SQL para consultar os dados de teste,por exemplo select*from vendas where região=’RJ’.O resultado esperado é apresentado na Figura 3.

Page 28: Java Magazine 41

Figura 3.Nova execução do DataBase Manager,para verificar a correta inicialização do banco de dados. Listagem 1. Comandos SQL para inicialização do banco de dados de exemplo. create table produto ( id integer not null primary key, nome varchar (30) not null, preco decimal (14,2) not null, categoria varchar (20) not null ); insert into produto values ( 1. ‘iPod Nano’, 299.00, ‘Eletro Eletrônicos’ ) ; insert into produto values ( 2. ‘Endredon dupla face’ , 34.00, ‘Cama e Banho’) ; insert into produto values ( 3. ‘Tv de Plasma’, 7599.00, ‘Eletro Eletrônico’) ; insert into produto values ( 4. ‘Travesseiro anti-alérgico , 49.00, ‘Cama e Banho’) ; create table vendas ( regiao char(2) not null , produto integer not null , qtde integer not null , foreign key (produto) references produto (id) ) ; insert into vendas values (‘RJ’ , 1, 322 ) ; insert into vendas values (‘SP’ , 1, 518 ) ; insert into vendas values (‘RJ’ , 2, 567 ) ; insert into vendas values (‘SP’, 2, 987 ) ; insert into vendas values (‘RJ’ , 3, 45 ) ; insert into vendas values (‘SP’ , 3, 78 ) ;

Page 29: Java Magazine 41

insert into vendas values (‘RJ’ , 4, 67 ) ; insert into vendas values (‘SP’, 4, 75 ) ; Um programa JDBC mínimo A Listagem 2 apresenta um programa JDBC mínimo.Ele conecta ao banco de dados,reduz os preços de todos os produtos da categoria “Eletro Eletrônico” em 12%, e encerra a conexão.A redução nos preços é inteiramente realizada por um comando SQL update, sem qualquer ajuda de códigos Java. Para compilar e executar esse programa esse programa, use o comando javac e java no mesmo prompt de comandos onde foi antes configurado o classpath para incluir o driver JDBC do HSQLDB (ou de qualquer outro banco de dados desejado). $ javac ReduzPreco.java $ java ReduzPrecos 2 produtos tiveram seus preços reduzidos O sinal de “$” representa o prompt de comando do sistema operacional e não deve ser digitado. Caso apenas o comando funcione mais não o segundo, ou você não configurou classpath corretamente ou o banco de dados não foi inicializado de forma correta. Para que confirma que o programa reiniciou o preço dos produtos e eletroeletrônicos, e não os produtos de outras categorias, execute o Database Manager e consulte todos os registros na tabela de produtos, conforme exemplificado pela Figura 4.

Figura 4. Situação do banco de dados de exemplo após a execução do programa ReduzPreco.java

Ø Sempre encerre o Database Manager do HSQLDB antes de executar qualquer dos programas Java deste artigo, pois no modo stand-alone apenas uma aplicação pode acessar os arquivos do banco de dados.No modo servidor do HSQLDB, esta restrição é eliminada,mas não queremos distrair o leitor com configurações de rede e firewall.

Vamos agora seguir programa mínimo, linha a linha, para entender o seu funcionamento:

Page 30: Java Magazine 41

1. A chamada a Class.forName() garente que o driver JDBC do banco de dados esteja carregado na memória do JVM.Há varias outras formas de se fazer isso,mas esta é a mais simples.

2. A chamada a DriverManager.getConnection() cria uma conexão (interface Connection) ao banco de dados indicado pela string passada como primeiro argumento.O login e senha para acesso ao banco de dados são passados no segundo e terceiro argumentos do método.

3. O objeto Connection retornado pelo DriverManager é utilizado para se criar um comando SQL (objeto do tipo Statement) pela chamada a createStatement().

4. O objeto Statement é então utilizado para enviar ao banco de dados o comando SQL update que modifica os preços chamando o método executeUpdate()

5. O comando SQL e a conexão ao banco de dados são encerrados pela chamada aos respectivos métodos close().

Todo programa JDBC segue a mesma seqüência geral de passos: (1) Conexão (2) Execução de comando SQL e (3) Encerramento. Um mesmo objeto de conexão (Connection) pode ser utilizado para executar vários comandos. Na verdade, um mesmo objetos de comando (Statement) pode ser utilizado para executar vários comandos SQL,sendo que a execução de um novo comando descarta os resultados gerados pela execução do comando anterior.

ü “A forma de conexão ao banco de dados apresentada neste exemplo, utilizando Class.forName() e DriverManager.getConnection(),é adequada para aplicação Java SE,isto é, desktop ou em modo texto.Mas não será a mais eficiente para aplicações Java EE. Em servlets,páginas JSP EJBs devem utilizados objetos DataSource,configurados dentro do próprio servidor de aplicações ou container web.Veja as referencias ao final do artigo para mais informações.”

A classe DriverManager,além das interfaces Connection e Statement, são partes da API JDBC. Elas e todas as outras classes e interfaces do JDBC que serão vistas nesse artigo estão dentro do pacote java.sql. Listagem 2: ReduzPrecos.java,programa JDBC mínimo que modifica os preços de vários produtos no banco de dados exemplo. import java.sql.*; public class ReduzPrecos { public static void main(String[] args) throws Exception { Class.forName(“org.hsqldb.jdbcDriver”) Connection conexao = DriverManager.getConnection( “jdbc:hsqldb:file:vendas;shutdown=true”, “sa” , “”) ; Statement commando = conexao .createStatement () ; int registrosAfetados = comando.executeUpdate ( “update produto set preco = preco – (preco * 0.12) “ + “where categoria = ‘Eletro Eletrônicos ‘ “ ) ; System.out.println(registrosAfetados + “produtos tiveram seus preços reduzidos , “ ) ; comando.close ( ) ;

Page 31: Java Magazine 41

conexão.close ( ) ; } } Configurando a aplicação para mudar de banco Nosso primeiro exemplo de código Java não é na verdade independente do banco de dados, porque ele fixa no código a URL de conexão e o usuário e senha. Desse modo, adaptar a aplicação para outro banco de dados, ou simplesmente modificar o diretório dos arquivos do banco de dados, exigiria a modificação do código-fonte da aplicação e a sua recompilação. É bem melhor deixar estes parâmetros de conexão ao banco como informações de configuração da aplicação. A forma padrão de se fazer isto em Java é por meio de arquivos e propriedades (arquivos de texto simples contendo linhas no formato nome=valor).Estes arquivos são carregados com recursos da JVM².A vantagem de se tratar arquivos de propriedades como recurso é que a aplicação pode ser instalada em qualquer local do disco rígido, pois os recursos são localizados no classpth. A Listagem 3 apresenta um segundo evento de aplicação Java, que desse vez se limita a inserir o novo registro de venda no banco de dados de exemplo.O trabalho do banco é realizado pelo comando SQL insert.Mas o que nos interessa agora é a forma como é utilizada a classe Properties para obter os parâmetros para conexão ao banco de dados.(Note que esta classe não faz parte da API JDBC,mas sim um pacote java.util). Note que o arquivo banco.properties deve estar no mesmo diretório que os arquivos.class do exemplo deste arquivo.Se você estiver usando o JDK e a linha de comando para compilar e executar os exemplos,isto sera natural.Já se você estiver usando um IDE, a maioria deles ira copiar automaticamente qualquer arquivo não- Java nos diretórios de fonte para o diretório de classe (ou binários). Um objeto Properties pode ser inicializado a partir de um InputStream qualquer,isto é , de um arquivo –texto,conexão de rede, arquivo zip ou qualquer outro tipo de stream suportado pela API java.io.No exemplo, foi utilizado a própria classe que representa o programa (InsereProduto.class)para criar um stream que acessa o conteúdo do arquivo banco.properties,localizado no classpath da JVM, pela chamada ao método getResourceAsStream( ).

ü Outra forma de ser obter uma referencia à própria classe para a carga de recursos é usando this.get.Class().

Com o objeto de propriedades inicializado na variável parametrosConexao, sucessivas chamadas a getPropertie () retornam os parâmetros. Que são armazenadas em variáveis e depois passadas para a chamada a getConnection).Dai em diante,o exemplo é igual ao programa anterior,exceto por executar um comando SQL diferente. Alguns desenvolvedores colocam também os comandos SQL nos arquivos de propriedades, para facilitar o porte ou otimização da aplicação para diferentes banco de dado. Novamente, para verificar os efeitos do programa de exemplo sobre banco de dados, será necessário iniciar o Database Manager do HSQLDB e executar um comando SQL select.Afinal ainda não vimos como executar consultas SQL via JDBC.Esse é o assunto do próximo tópico. Listagem 3. InsereProduto.java:programa que utiliza JDBC para inserir novos dados de vendas de um produto(foram destacadas as linhas que utilizam um arquivo de propriedades para obter os parâmetros de conexão).

Page 32: Java Magazine 41

import java.sql. *; import java.util. *; public class InsereProduto { public static void main(String[] args ) throws Exception { Properties parametrosConexao = new Properties ( ); parametrosConexao.laod(InsereProduto.class.getResourceAsStream( “banco.properties”)); String driver = parametrosConexao.getProperty (“driver”); String url = parametrosConexao.getProperty (“url”) String login = parametrosConexao.getProperty (“login”) String senha = parametrosConexao.getProperty (“senha”) Class.forName(driver); Connection conexao = DriverManager.getConection( url, login, senha ) ; Statement commando = conexao.createStatement ( ); int resgistrosAfetados = comando.executeUpdate ( “insert into produto (id, nome, preco, categoria) values (“ + “5, ‘DVD Playerportatil’ ,799.00, ‘Eletro Eletrônicos ‘)”) ; System.out.println(registrosAfetados + “produto inserido com sucesso, “) ; comando.close (); conexão.close(); } } Executando consultas SQL A interface Statement do JDBC permite executar um comando SQL qualquer, desde comandos de DDL,como create table,até os comandos de DML,como insert e select. Comandos SQL que retornam dados devem ser executados por meio do método executeQuery(), e os que modificam dados (ou que não retornam dados )devem ser executados via executeUpdate(),como fizemos nos exemplos anteriores. O método executeQuery( ), retorna um objeto de resultados (interface ResultSet),que permite ler, linha a linha , os resultados da consulta. Assim com o a conexão (connection) e o comando (Statement), o objeto de resultado deve ser fechado pelo método close () quando não estiver mais em uso.

ü Objetos Connection,Statement e ResultSet (além de sua interfaces especializadas,como PreparedStatement, que será apresentada na segunda parte) devem ser fechados o mais cedo possível, pois esses objetos representam recursos do sistema operacional e do banco de dados. É um erro comum supor que o Java, por meio da coleta de lixo ou de métodos destrutores,irá fazer automaticamente esta “limpeza”, o que não é verdade. Além disso , quanto mais tempo ele os recursos mais forem mantidos abertos na aplicação, maior será o “peso” dela para a rede e para usuário.

Page 33: Java Magazine 41

A Listagem 4 apresenta um programa Java que lista o valor total de vendas de cada produto individualmente,seguido pelo total de todos os produtos somados. Este exemplo exige Java SE 5,0 ou mais recente, pois usa o novo método de formatação(printf( )),para gerar uma saída textual alinhada em colunas. Foi usada também a interface java.text.numberFormat, para a formatação dos valores monetário (esta interface existe em versões mais antigas do Java). Observe ainda que os valores monetários foram armazenados em um BigDecimal (pacote java.math)em vez de em um double. Valores monetários não devem ser armazenados nem manipulados como doubles seus tipos assemelhados (como float e Double). Para mais informações, veja o quadro “Tipos de dados Java X Tipos de dados SQL”. O método next( ) de ResultSet é utilizado para acessar a próxima linha do resultado. Note que este método deve ser chamado antes que se possa acessar a primeira linha. Desde forma uma consulta que retorna um resultado vazio pode ser tratada da mesma forma que uma que retorna dados. A interface ResultSet fornece ainda métodos como getInteger( ), getString( ) ou getDate( ) para a recuperação dos valores armazenados nas colunas/campos de cada linha do resultado. O JDBC irá converter automaticamente os tipos de dados do SQL para o Java e vice-versa, incluindo conversões como de tipos numéricas para tipos de texto. A coluna desejada pode ser indicada tanto pela sua posição numérica no resultado, iniciando pela posição 1 (e não a zero) quanto pelo nome da coluna.

ü Em alguns bancos de dados, por exemplo, no MySQL, será preferível usar a posição em vez do nome da coluna. Assim haverá um pequeno ganho de performance.

Listagem 4.Totais Vendidos.java,exemplo que gera um “relatório” de totais de vendas por produto.Foram destacadas as linhas que recuperam o resultado da consulta SQL. import java.sql. *; import java.util.*; import java.math.*; import java.text.*; public class TotaisVendidos { public static void main(String[] args ) throws Exception { Properties parametrosConexao = new Properties ( ); parametrosConexao.load(TotaisVendidos.class.getResourceAsStream( “banco.properties”)); //... conexão igual à InsereProduto.java Statement commando = conexao.createStatement ( ); ResultSet resultado = commando.executeQuery ( “selecione nome, sum(preco * qtde) as total “ + “from produto, vendas “ + “where produto.id = vendas.produto “ + “group by nome” ) ;

NumberFormat nf = new Decimal Format (“##,##,#0.00”) ; int registrosLidos = 0 ; BigDecimal totalGeral = new BigDecimal (0); while ( resultado.next( ) ) {

Page 34: Java Magazine 41

String nome = resultado.getString (1) ; BigDecimal total = resultados.getBigDecimal (2) ; // As duas linhas seguintes são equivalentes às duas anteriores // String nome = resultado.getString (“nome”) ; System.out.printf (“%-30s:\t%15s\n”, nome, nf.format (total)) ; totalGeral = totalGeral.add(total) ; registrosLidos++; } System.out.println (“\nTotal geral: “ + registrosLidos + “produtos, “ + “R$ “ + nf.format(totalGeral) ) ; resultado.close ( ); comando.Close ( ); conexao.close ( ); } }

Versões da API JDBC Aqui é apresentado um resumo das novas funcionalidades introduzidas a cada versão da API JDBC JDBC 1.0

• Versão original da API,embutida no Java 1.1,já trazia todas as classes apresentadas neste cargo.

JDBC 2.0/Java 2.0

• Interface DataSource,para conexões gerenciadas por container Java EE; • Suporte a transações distribuídas; • Interface RowSet,que permite fazer o cachê do resultado de consultas no

cliente JDBC 2.1/Java 1.2

· Atualizações em batch, permitindo uma forma independente do banco de enviar vários comandos SQL de uma só vez; • Suporte a resultados roláveis (scrollable) e atualizáveis, que permitem a

navegação livre por um ResultSet; • Atualização direta do registro corrente em resultados (ResultSet),sem

necessidade de enviar comandos SQL update; • Mapeamento de tipos definidos pelo usuário.

JDBC 3.0/Java 1.4

• Save points, que permitem o rollback parcial de uma transação; • Parâmetros nomeados em procedimentos armazenados; · Forma padronizada de ser recuperar o valor das colunas geradas automaticamente pelo banco, por exemplo colunas identity e autoincrement.

JDBC 4.0 (proposed final draft)

· Suporte ao tipo rowid, que identifica fisicamente registro em alguns bancos, como o Oracle; • Manipulação de dados XML no banco e suporte a extensões do SQL para dados

XML; • Maior controle sobre pools de conexões e comandos;

Page 35: Java Magazine 41

· Novas exceções, de modo que não é mais necessário lidar com o sqlCode nas situações mais comuns; · Novas interfaces Query e DataSet, que apoiadas por anotações,permitem mapear diretamente operações sobre o banco de para método e atributos de classes Java.

Tipos de dados Java X SQL Seja nos valores lidos de um resultado (ResultSet), ou nos parâmetros fornecidos a um PrepareStatement, o programador Java deve se preocupar sempre em usar o tipo certo de dados corretos, ou mais próximo do tipo de dados SQL que foi definida para cada coluna no banco de dados. Na maioria dos casos, a correspondência é intuitiva,como number ou int32 do SQL para int(ou integer) do Java; ou char e varchar do SQL para String do Java.Mas há dois casos especiais aos quais o programador deve estar atento: Tipos numéricos O tipo SQL decimal é um numero fracionário com representação exata. Isto é bem diferente de um float ou double do Java, que são números em pontoflutuante. Em particular a aritmética com decimal não gera erros de arredondamento. O tipo Java correto é java.math.BigDecimal. Fazer aritmética com BigDecimal é um tanto trabalhoso, pois exige chamar métodos como add () e multipy em vez de usar os operadores +e*.Masse for feita a conversação de BigDecimal para double, de modo a simplificar o código, serão introduzidos erros de arredondamento. Em um sistema financeiro, isto pode levar a situação onde o fluxo de caixa de caixa não zera, pois centavos são perdidos de modo aparentemente aleatório e essas perdas podem se somar e gerar discrepâncias significativas. Tipos de Data/Hora Os tipos data e time do SQL são diferentes dos tipos java.util.Date ou java.util.Calendar do Java.Ambos seriam equivalentes ao tipo timestamp do SQL,pois três armazenam tanto uma data quanto uma hora. Parater um tipo que armazene apenas data ou apenas hora, use java.sqlDate e java.sql.Time, respectivamente. Conclusão Nesta parte começamos a apresentar os fundamentos da API JDBC, uma das mais importantes para aplicações tanto Java SE como Java EE. Na próxima parte serão vistos tópicos importantes para a produção de aplicação em qualquer das edições do Java, tais como tratamento de erros,transações e comandos preparados (PreparedStatements), além de funcionalidades para obtenção e utilização de metade dos do banco. Para saber mais MySQL para programadores Java Fernando Lozano.Edição 40 Fundamentos da administração. O novo HSQLDB Fernando Lozano. Edição 30 Apresenta em detalhes o HSQLDB, banco de dados livre 100% Java para uso embarcado. Persistência Turbinada II Osvaldo Doederlein.Edição 26 Recursos avançados do JDBC Persistência Turbinada I

Page 36: Java Magazine 41

Osvaldo Doederlein.Edição 25 Como usar o JDBC de modo eficiente para criar classes DAO. JDBC 3 Maiko Rocha. Edição 07 Apresenta os novos recursos do JDBC 3. LINKS: jcp.org/em/jsr/detail?od=221 Especificação JDBC 4.0,em estágios finais de aprovação. jcp.org/em/jsr/detail?id=54 Especificação JDBC 3.0, em vigor desde JDK 1.4 java.sun.com/javase/tecnologies/database.jsp Pagina da Sun sobre JDBC java.sun.com/docs/books/tutorial/jdbc/ Trilha sobre JDBC do Java Tutorial hsqldb.org Site Oficial do HSQLDB mysql.com Site oficial do MySQL e do seu driver JDBC postegresql.org Site oficial do PostgreSQL jdbc.postgresql.org Site oficial do driver JDBC do PostgreSQL Java EE 5 na Prática Criando uma aplicação passo a passo com EJB 3.0, JPA e NetBeans 5.5 Construindo uma aplicação de três camadas com EJB 3, utilizando os recursos do mais novo NetBeans e conhecendo práticas e técnicas importantes para aplicações reais A tradicional plataforma J2EE sempre teve fama de ser poderosa, porém difícil, mas podemos argumentar que este julgamento é discutível. Difícil comparado com o que? Os críticos mais severos deveriam ter experimentado construir aplicações com recursos como distribuição, concorrência, persistência, clustering, transações e segurança, lá por 1999, antes da introdução do J2EE 1.0 e EJB 1.0! Mas 1999 está longe. Uma tecnologia é sempre comparada ao estado de arte do seu campo, e à sua concorrência. Hoje os Entity Beans são comparados ao Hibernate, os servlets/JSP ao Rails; a linguagem Java a Ruby ou Python, as plataformas Java SE e EE a .NET ou a LAMP, e assim por diante. Por isso o Java não pode parar. E é interessante notar que o foco da competição nem sempre é o mesmo. Em alguns momentos, é o desempenho – como nos anos 90, antes das JVMs modernas, quando o Java tinha grande

Page 37: Java Magazine 41

desvantagem contra linguagens como C/C++. Em outros momentos, é a funcionalidade – como há poucos anos na “febre” dos web services, quando todos os fornecedores de tecnologia se atropelavam para implementar suporte a SOAP e padrões relacionados. Agora estamos num momento em que a prioridade geral é a facilidade de desenvolvimento. Isso sem dúvida é resultado do amadurecimento e do conhecimento acúmulo de funcionalidades das plataformas de desenvolvimento hoje m uso – seja no Java, no .NET, ou mesmo em opções tradicionais (como C/C++ com os SDKs da Microsoft, ou em Unix com todas as suas bibliotecas). As conseqüências de toda a sofisticação hoje existente são assustadoras; todos os SDKs são enormes. Kits de documentação podem ocupar centenas de megabytes no seu disco, e alguns IDEs recentes têm gigabytes. Como mais complexidade em princípio implica maior esforço de aprendizado e custo de desenvolvimento mais alto, é natural que a corrida hoje seja pela produtividade. A resposta do Java para este desafio vejo como o Java EE 5, que vimos no artigo “Java EE 5” na Edição 39, destaca-se pelas inovações direcionadas a uma maior produtividade. Guiaremos o leitor pelo desenvolvimento de uma aplicação de três camadas, usando Session Beans do EJB 3.0, persistência em banco de dados com JPA e uma interface web. Não nos entendermos sobre APIs com JPA, recém coberta em “Persistência no Java EE 5 (Edição 39). Ao invés disso, vamos analisar o ciclo de desenvolvimento como um todo. E utilizaremos o IDE NetBeans 5.5, que no momento em que este artigo é escrito, tem o suporte integrado mais completo à plataforma Java EE 5. Instalando o NetBeans e o servidor de aplicações Para acompanhar este tutorial, você precisará, além do NetBeans (5.5 ou superior), de um servidor de aplicações suportado por este IDE. Recomendo o Sun Java System Application Server 9.0 (SJSAS – também conhecido como Java EE 5 SDK) – ou sua distribuição open source Glassfish (v1 Milestone 7) – por ser o mais bem suportado pelo NetBeans. O GlassFIsh/SJSAS é também o único destes que atualmente implementa de forma completa e certificada o Java EE 5. Outros servidores já suportados pelo NetBeans são JBoss, WebLogic e Jonas (este exigindo o plug-in JOnbAS). Em caso de dúvida, o mais fácil é baixar um dos downloads integrados: estão disponíveis downloads do NetBeans + SJSAS, e NetBeans + JBoss e o WebLogic (bem como outros servidores) possuem suporte parcial ao Java EE 5, em pré-release. Mas quando você estiver lendo, é bem provável que a lista de servidores certificados Java EE 5 tenha aumentado e que mais destes sejam suportados pelo NetBeans. Iniciando com a aplicação Vamos começar a aplicação no NetBeans. Selecione File/New Project>Enterprise/Enterprise Application. Este assistente cria um grupo de projetos de aplicação Java EE. Na aba Name and location, preencha o Project Name, ex.: JavaMagazine, e seu diretório-raiz, e aceite do defaults para as demais opções, como na Figura 1. O resultado será uma estrutura de três projetos: JavaMagaziner-ejb, que irá gerar o EJB-JAR; JavaMagazine-war que gera o WAR; e JavaMagazine, que gera o EAR. Que já construiu aplicações J2EE tradicionais verá que o padrão de deployment de aplicações, e conseqüentemente o de organização de projetos, não parece ter mudado com o Java EE 5. Na verdade, o padrão de deployment teve simplificações, como já vimos nesta coluna, com a remoção ou simplificação de descritores. Por exemplo, em JavaMagazine-ejb/Configuration Files, você verá que o NetBeans irá criar somente um arquivo: o MANIFEST.MF, cuja inclusão é uma “boa prática” para qualquer tipo de JAR. O Java EE 5 não exige mais este arquivo para incluir no classpath do módulo os JARs empacotados no mesmo EAR; portanto raramente será necessário. O tradicional ejb-jar.xml (descritor de EJB padrão) não será mais criado. Mas dependendo das operações que você fizer no projeto, o NetBeans poderá criar o sun-ejb-jar.xml (descritor específico do SJSAS/GlassFish), ou talvez outro descritor proprietário se você tiver utilizado outro servodor. Pode parecer estranho não precisamos mais de um descritor padrão e continarmos usando o proprietário. Mais veja porque isso faz sentido. O ejb-jar.xml é opcional porque as informações que antes se encontravam nele agora podem ser determinadas por anotações em código. Porém, o

Page 38: Java Magazine 41

código-fonte deve ser mantido portável. Você não iria querer utilizar anotações proprietárias – que continuarão existindo para opções de runtime (ex.: números de porta, tamanhos de pools, timeouts etc.) que sempre serão específicas a cada servidor. Por isso, os descritores proprietários poderão continuar sendo necessários, ainda que raramente.

Figura 1. Criando um projeto Java EE no NetBeans. Implementando as entidades persistentes Vamos agora à camada de back-end da aplicação, gerada pelo nosso projeto JavaMagazine-ejb. Nossa aplicação tem apenas duas entidades, Edição e Artigo, persistidas pela Java Persistence API (JPA). E teremos um Session Bean chamado JavaMagazine com métodos para consultar e modificar estas entidades. Pela descrição, vemos que é uma aplicação bastante simples. Em casos como esse, a reclamação antiga da plataforma J2EE é que, embora a burocracia de suas APIs e descritores pudesse se justificar para aplicações ambiciosas, cheia de requisitos avançados como clustering, transações distribuídas ou segurança, essas exigências eram um “canhão para matar mosca” no caso de aplicações mais simples sem estes requisitos. É a “escabilidade de complexidade”: aplicações simples devem resultar em codificação simples, ainda que a plataforma também suporte aplicações muito mais complexas. Vejamos então se o Java EE 5 cumpre a promessa de ser escapável no quesito de complexidade. A entidade Edicao Sem nos aprofundar na JPA, vamos direto ao ponto com nossas duas entidades, aproveitando as facilidades do NetBeans. Com o projeto JavaMagazine-ejb selecionado, acione New>Entity Class, que traz o assistente ilustrado na Figura 2. Preencha o nome da classe com Edição, e o pacote com vo (de “value objects”). Aceite o default de Long para tipo da chave primária numérica artificial (gerada automaticamente).

Page 39: Java Magazine 41

Figura 2. Criando uma entidade persistente. Como esta é a primeira entidade persistente do projeto, o assistente exibirá um botão Create Persistence Unit. Você deve executar essa tarefa antes de conseguir criar qualquer entidade. Acione o botão e será exibido o dialogo da Figura 3. Uma Persistence Unit (“unidade de persistência”, ou UP) é um conjunto de configurações de persistência. Cada entidade é associada a um UP. Uma aplicação Java EE pode possuir várias UPs, se necessário, para melhor organizar mapeamentos muito complexos (num EAR com vários módulos EJB, cada módulo EJB terá sua própria UP, que por sua vez, poderá definir várias entidades). Você deve escolher um nome qualquer para a unidade de persistência, e determinar qual runtime será usado e qual o nome do datasource para acesso ao bando de dados. Se você estiver usando o SJSAS ou o GlassFish, selecione TopLink(default). O TopLink é um produto de mapeamento objeto-relacional da Oracle que possui uma versão open source, o TopLink Essencials. Na aplicação de exemplo usaremos o TopLink Essencials como provedor de JPA. Para o datasource, selecione a opção predefinida jdbc/sample, que aponta para um banco de dados Derby embutido neste servidor de aplicações. (Usuários de outros servidores deverão determinar qual implementação de persistência deve ser usada, e podem ter que criar uma bas de dados vazia e o datasource.)

Page 40: Java Magazine 41

Figura 3. Criando uma Unidade de Persistência

ü Também é possível ignorar o provedor de JPA default do seu servidor e instalar seu provedor favorito, por exemplo o Hibernate Entity Manager em SJSAS/GlassFish. Esta é outra vantagem do Java EE 5: a JPA suporta provedores “plugáveis”, diferentemente do EJB/CMP, em que tipicamente cada servidor impunha sua implementação de CMP (persistência gerenciada pelo container). Para fazer essa instalação, abra o arquivo JavaMagazine-ejb/Configuration Files/persistence.xml: este é o descritor principal de persistência, que pode conter detalhes de mapeamento, ou apontar para arquivos de mapeamento externos (inclusive proprietários: quem tiver legado do Hibernate, pode aproveitar seus tradicionais “.hbm”!) Não faremos isso neste artigo, pois definiremos o mapeamento somente com anotações. Mas você pode ver, no persistence.xml mínimo gerado pelo assistente do NetBeans, uma tag importante:

<provider>oracle.toplink.essentials.ejb.cpm3.Entity

ManagerFactoryProvider</provider> Esta tag <provider> determina qual é o provedor de JPA a utilizar. Também pode haver tags de propriedades especificas a cada provedor. Para usar outro provedor, consulte sua documentação. Tipicamente, será preciso instalar os JARs do provedor no diretório lib do servidor de aplicações, e preencher o persistence.xml com os valores corretos para a tag <provider> (ambas as tarefas poderão ser feitas por plug-ins que suportem provedores específicos no IDE). Voltando ao tutorial, o código gerado pelo assistente está na Listagem 1. Temos um POJO (objeto Java comum, que não implementam ou herda nenhuma API), no qual destacamos algumas características:

• Usamos algumas anotações da JPA: @Entity identifica a entidade persistente, @Id seleciona o atributo usado como chave-primária, e @GereratedValue customiza o mesmo atributo para que o valor da chave-primária seja criado de forma automática.

• A entidade deve possuir um construtor default, além de getters e setters no estilo JavaBeans para seus atributos ou relacionamentos persistentes.

• É comum utilizar objetos como Long, ao invés de tipos primitivos como long, para representar atributos persistentes, por isso nos permite suportar colunas nulaveis. Mas isso não é uma exigência da JPA. Poderíamos ter declarado nossa chave-primária como long-id, o que é mais eficiente (e como chaves-primárias nunca podem ser nulas, neste caso não haveria desvantagem).

O Hibernate aceita construtores privados, e também getters e setters privados para propriedade que o programa não quer expor. Mas na JPA não tem esta capacidade; os construtores e métodos devem ser public ou protected. Na listagem, destacamos em negrito as partes obrigatórias do código: apenas uma dúzia de linhas, sem contar os imports e os comentários. Todo o resto – hashCode(), equals(), toString(), anotações @Override – é bônus do assistente do NetBeans. São boas práticas de programação, mas não são exigências da JPA (e para simplificar, vamos omitir essas partes em listagens futuras). A JPA também não exige que as entidades sejam serializáveis (mas chaves-primárias sim). Porém, na prática smpre implementaremos Serializable, pois isso nos permite usar menos objetos com DTOs (data transfer objects), por exemplo em inovações de EJBs, cujos argumentos e valores de retorno devem ser serializáveis.

Page 41: Java Magazine 41

ü Os métodos hashCode() e equals() de uma entidade não são utilizadas pelo JPA, pois esta API utiliza somente o atributo de chave-primária (marcado com @Id) para finalidades de identificação da entidade.

Listagem 1. Código inicial gerado para entidade Edicao. package vo; import Java.io.Serializable; import javax.persistence.*; @Entity public class Edicao implements Serializable {

@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; public Edicao() { } public Long getId() {

return this.id; } public void setId(Long id) {

this.id = id; } @Override public int hashCode() {

int hash = 0; hash += (this.id != null ? this.id.hashCode() : 0); return hash;

} @Override public Boolean equals(Object object) {

if (!(object instanceof Edicao)) { return false;

} Edicao other = (Edicao) object; if (this.id != other.id && (

this.id ==null l l !this.id.equals( other.id))) return false;

return true; } @Override public String toString() {

return “vo.Edicao[id=” + id + “]”; }

} A entidade Artigo Vamos agora implementar a entidade Artigo, que possui uma relação muitos-para-um com Edicao. Para complicar um pouco, decidi que o Artigo deve ter uma chave-primária natural, sendo composta por duas propriedades: o título do artigo e o nome do autor. Assim diferentes autores podem, eventualmente, escrever artigos com nomes exatamente iguais, mas espera-se que pelo menos o mesmo autor não repita SUS próprios títulos, ainda que em edições distintas.

Page 42: Java Magazine 41

Na JPA, chaves-primárias compostas são suportadas por entidades “embutíveis” (embelddable). Neste exemplo, precisaremos criar uma classe, ArtigoPK, que encapsula os componentes da chave – as propriedades titulo e autor (PK abrevia “Primary Key”). O NetBeans não possui um assistente específico para isso, portanto, temos que implementar à mão o código da Listagem 2. Este código também é um POJO e segue muitas das regras anteriores (classe Serializable. Construtor default etc.). A novidade é a anotação @Embeddable. Esta anotação denota objetos que não são persistentes de forma independente – são apenas agrupamentos de propriedades de outro objeto persistente. Uma observação importante sobre classes de chave-primária: uma implementação correta e eficiente dos métodos hashCode() e equals() é obrigatória, pois a JPA irá depender muito destes métodos, por exemplo para gerenciar caches de objetos em memória. Aqui não recomendo usar “facilidades” como as classes HashCodeBuilder e EqualsBuilder do projeto Jakarta Commons Lang, que automatizam estes métodos, mas com um custo significativo em desempenho. Resolvido o problema da PK composta, podemos usar novamente o assistente de Entity Class. Preencha o nome da classe Artigo, e o pacote novamente com vo. Mas agora, no campo Primary key type, selecione o botão “...” à direita e então escolha a ArtigoPK entre as classes listadas (também seria possível digitar o nome completo). Novamente, o assistente irá criar boa parte do código que precisamos. Mas ele comete um erro (talvez por ter sido usada uma versão beta do NetBeans 5.5): as tags @Id e @GeneratedValue não são válidas para chaves compostas. Você deve substituir ambas as anotações por uma única anotação @Embeddedld. O assistente também gera um atributo id e métodos getId()/setId(), mas não recomendo este template de implementação. Para chaves “embedded”, é melhor encapsular a classe de PK, e expor construtores e getters para seus componentes individuais, como se fossem atributos diretos da classe de entidade. Assim, se alguém tiver um Artigo art, poderá obter o nome do autor simplesmente com art.getAutor(), sem ter que fazer duas chamadas como em art.getId().getAutor().

ü A estrutura da chave-primária (PK) é um detalhe de implementação da entidade. O encapsulamento deste tipo de detalhe é uma regra elementar de design orientação a objetos. Ao lidar diretamente com JPA, é difícil evitar a manipulação explícita de objetos de PK, por exemplo, ao invocar métodos como EntityManager. Find(Class classe, Objects chave). Mas ao “exportar” esta funcionalidade para clientes do módulo, por exemplo via métodos de Session Beans, você deve evitar expor as PKs. Assim, nunca crie um método localizaArtigo(ArtigoPK pk); prefira localizaArtigo(String titulo, String autor). O problema de expor a PK é que esse é o tipo de detalhe de esquema de dados, que muitas vezes pode mudar. Digamos que um dia o DBA do projeto constante que não existe nenhuma repetição da coluna Artigo.titulo. O DBA irá querer alterar a tabela, usando apenas a coluna titulo como PK (o que é mais eficiente!). Como fazer isso sem quebrar código pré-existente? Métodos que expõem a PK quebrariam clientes, pois códigos como newArtigoPK(titulo,autor) não funcionariam mais, já que ArtigoPK deixa de ter o autor – ou melhor, deixa de existir totalmente, pois não precisamos de uma classe para encapsular uma PK não-composta! Por outro lado, um método localizaArtigo(String titulo, String autor) continuaria funcionando com perfeição.

Voltando ao código da entidade Artigo: agora basta completar a classe, que também possui uma propriedade não-chave texto e um relacionamento com Edicao. Fazendo isto, seu IDE estará similar à Figura 4. Observe que neste ponto o NetBeans apresenta uma marca de advertência em amarelo, ao lado da declaração de Edicao edicao. A mensagem “entity relation not defined” reclama que este atributo parece ser um relacionamento com outra entidade, mas que falta uma anotação obrigatória para especificá-lo.

Page 43: Java Magazine 41

Clicando na lâmpada ao lado você terá varias opções de correção do problema. Selecione Create bidirecional “Many/ToOne” relationship, o que apresenta o assistente da Figura 5. Como o relacionamento desejado é bidirecional, a JPA exige que a entidade localizada no outro lado do relacionamento (Edicao) tenha um atributo que aponte o Artigo. Se este atributo já existisse, o assistente permitiria selecioná-lo; como não existe, ele se oferece para criá-lo. Aceite o default de Create new Field com o nome “artigos” (gerado automaticamente , igual ao nome da entidade em minúsculas e no plural). O resultado será a Listagem 3, com anotação @ManyToOne. Nossa classe Edicao também estará completa agora,devendo estar parecida com a Listagem 4. Note que o assistente de criação de relacionamento gerou um atributo List<Artigo>artigos, com uma anotação @OneToMany(mappedBy=”edicao”). Mas temos que modificar esta anotação para incluir mais alguns atributos, ficando assim: @OneToMany(mappedBy=”edicao”, cascade=CascadeType.ALL,fetch=FetchType.EAGER) private List<Artigo>artigos; O atributo mappedBy=”edicao” é opcional se na entidade Artigo só existir uma propriedade com o tipo Edicao (que é o nosso caso). Mas como você poderia ter no Artigo vários relacionamentos independentes com objetos Edicao (ou acrescentá-los futuramente), o assistente por segurança sempre gera o mappedBy. Se quiser, você pode eliminar este parâmetro. Explicaremos os atributos cascade e fetch mais adiante.

Figura 4. O NetBeans indicando a falta de anotações de persistência, e oferecendo-se para criá-las.

Page 44: Java Magazine 41

Figura 5. Assistente de criação de relacionamento Listagem 2. Classe de chave-primária composta para a entidade Artigo. package vo; import Java.io.Serializable; import Java.util.Date; import javax.persistence.*; @Embeddable public class ArtigoPK implements Serializable {

private String titulo; private String autor; protected ArtigoPK () {} public ArtigoPK (String titulo, String autor) {

this.titulo = titulo; this.autor = autor;

} public String getAutor() {

return autor; } public String getTitulo() (

return titulo; } public void setAutor(String autor) {

this.autor = autor; } public void setTitulo(String titulo) {

this.titulo = titulo; } public int hashCode() {

return titulo.hashCode() ^ autor.hashCode(); } public boolean equals(Object obj) {

if (obj instanceof ArtigoPK) { ArtigoPK a = (ArtigoPK)obj; return titulo.equals(a.titulo) && autor.equals(a.autor);

}

Page 45: Java Magazine 41

else return false;

} } Listagem 3.A entidade Artigo completa. package vo; import java.io.Serializable; import javax.persistence.*; @Entity public class Artigo implements Serializable {

@Embeddedld private ArtigoPK id; private String texto; @ManyToOne private Edicao edicao; protected Artigo () { } public Artigo (

Edicao edição, String titulo. String autor, String texto) { this.id = new ArtigoPK(titulo, autor); this.edicao = edicao; this.texto = texto;

} public ArtigoPK getId () {

return this.id; } public void setId (ArtigoPK id) {

this.id = id; } public String getTitulo () {

return id.getTitulo(); } public String getAutor () {

return id.getAutor(); } public String getTexto () {

return texto; } public void setTexto (String texto) {

this.texto = texto; } public Edicao getEdicao () {

return edicao; } public void setEdicao (Edicao edicao) {

this.edicao = edicao; }

}

Page 46: Java Magazine 41

Listagem 4. A entidade Edicao completa. package vo; import java.io.Serializable; import java.util.List; import javax.persistence.*; @Entity public class Edicao implements Serializable {

@Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToMany(mappedBy = “edicao”, cascade=CascadeType.ALL, fetch=FetchType.EAGER) private List<Artigo> artigos; public Edicao () { } public Long getld () {

return this.id; } public void setld (Long id) {

this.id = id; } public List<Artigo> getArtigos () {

return artigos; } public void setArtigos (List<Artigo> artigos) {

this.artigos = artigos; }

} Testando o deploy A parte de persistência da aplicação está completa. Para testar, vá na janela Runtime e inicie o servidor. Depois, no projeto JavaMagazine, acione o comando Deploy project. Após algum tempo, você deverá ver os logs de sucesso da operação nas janelas de console do servidor e da tarefa JavaMagazine(run-deploy). Como, ao criar a Persistence Unit, utilizamos a opção Table Generation Strategy = Drop and Create, o deploy também irá criar as tabelas necessárias. Se as tabelas já existirem mas forem diferentes do desejado (devido a alterações das entidades desde o ultimo deploy), elas serão destruídas e recriadas. A inteligência e os limites das anotações Como neste artigo também queremos das uma visão crítica da facilidade de programação do Java EE 5, não poderíamos terminar esta seção deixando parecer que todo o trabalho de programação de persistência será tão simples quanto em nosso exemplo (que tem uma complexidade necessariamente limitada). Ao ouvir falar de anotações pela primeira vez, muitos desenvolvedores acham que só mudamos as coisas de lugar. Ou seja, metadados que antes ficavam em descritores (arquivos XML) agora ficam em anotações. E essas anotações têm que ser escritas do mesmo jeito, ainda que sua vinculação com as classes facilite a edição e previna erros de sincronia (mudar o código e esquecer de mudar os metadados). Mas como podemos deduzir pelo exemplo, a vantagem das anotações vai além disso, porque não precisamos usar anotações para todos os metadados que antes ficariam em descritores. Por

Page 47: Java Magazine 41

exemplo, você não viu nenhuma anotação declarando que a propriedade Artigo.texto é persistente. Nem viu uma anotação para declarar o nome da tabela usada por Artigo. Estas anotações (e muitas outras que não mencionamos) existem. Por exemplo, poderíamos ter escrito: @Entity @Table(name=”T_Artigo”,schema=”JM”) public class Artigo implements Serializable { … @Column(name=”TEXTO”,nullable=false,length=512). private String texto; … Estas anotações são opcionais porque, na sua falta, a JPA (assim como o EJB 3.0 em geral, e várias APIs incrementadas por anotações) seleciona defaults a partir do próprio código. Na ausência de @Table, o nome da tabela será igual ao nome da classe. Na ausência de @Column, todo atributo será considerado persistente e a coluna correspondente terá o nome do atributo. (Se você quiser o POJO possua algum atributo não-persistente, deve anotar este atributo com @Transient, que tem um significado semelhante ao do modificador transient do Java SE para serialização).

ü Em teoria, um sistema baseado em arquivos de configuração, como o EJB/CMP ou mesmo o Hibernate tradicional, poderia também deduzir defaults do código. Mas a ligação forte entre código e anotações, e a existência de APIs e ferramentas especiais no Java SE para manipular anotações, tornam esta estratégia mais simples e eficiente.

Outra “moleza” do nosso exemplo é que não precisamos escrever um script SQL para criar as tabelas. Isso será feito automaticamente pala JPA, que na falta de anotações, utilizará defaults razoáveis para detalhes como nulabilidade, tamanho de colunas, etc. Graças à geração automática de tabelas e à base de dados Derby embutida do SJSAS, você pode desenvolver a aplicação inteira sem nem saber que existe um gerenciador de banco de dados por trás! Estas facilidades da JPA e das ferramentas de desenvolvimento permitem avançar rapidamente no desenvolvimento sem tais preocupações. Do alô mundo ao mundo real... Poderíamos perguntar: até que ponto um exemplo como o da nossa aplicação JavaMagazine é realista? Em aplicações reais, é comum a existência de restrições quanto ao esquema de dados. Por exemplo, você pode ter que usar tabelas legadas pré-existentes, ou então definir suas tabelas seguindo padrões impostos pelo cliente, por exemplo com o prefixo “T_” ilustrado anteriormente. Assim, os nomes de tabelas e colunas não servirão bem para nomear classes e atributos. E você poderá ter que usar anotações @Table em todas as tabelas e anotações @Column em todos os atributos. Outro motivo comum para não podermos nos beneficiar de defaults do EJB 3 é o ajuste fino de opções com impacto em desempenho (como a opção fetch dos relacionamentos) ou com efeito sobre a integridade (como constraints). Tudo isso significa que, no pior caso, sua classe da entidade acabará possuindo muitas anotações, com praticamente todas as informações que antes estavam num descritor ou script SQL. Mas isso não significa que a facilidade proporcionada pelos comportamentos default do EJB 3 seja ilusória. Significa, apenas, que o esforço de codificação é proporcional à complexidade da aplicação. Se você puder criar o esquema do zero e não tiver que seguir nenhum padrão de estilo, poderá usar os defaults de nome de tabelas e colunas. Se não tiver grandes necessidades de desempenho, poderá usar defaults da JPA para fatores de performance (como leitura de dados otimizada por outer joins). E isso responde à maior reclamação sobre o J2EE tradicional: que a tecnologia era complicada não só para fazer coisas complicadas, mas até mesmo para coisas simples.

Page 48: Java Magazine 41

Implementando o Session Bean Liquidadas as entidades persistentes, nosso próximo passo será implementar um Stateless Session Bean com métodos para localizar e manipular as entidades. Selecionando o projeto JavaMagazine-ejb, comece acionando New>Session Bean. No assistente de criação, preencha EJB name com “JavaMagazine” e Package com “business”, aceitando os defaults para as opções Stateless e interface Local, mas marcando também a opção de interface remota. O assistente criará um esqueleto de Session Bean, formado pela classe JavaMagazineLocal e JavaMagazineRemote. Você deverá complementar estes esqueletos para conter o código das Listagens 5, 6 e 7. A Listagem 5 mostra a interface local do Session Bean, JavaMagazineLocal. Observe que temos uma anotação @Local, mas não precisamos mais herdar a interface EJBLocalObject. Em paralelo aos POJOs, chamamos de POJI (Plain Old Java Interface) a interfaces que não possuem dependências de APIs, não precisando herdar nenhuma outra interface. A interface JavaMagazineRemote (não mostrada aqui) será igual à local: basta copiar as declarações de métodos, e substituir a anotação @Local por @Remote. Na Listagem 6, vemos a implementação do nosso Stateless Session Bean, qualificação como tal pela anotação @Stateless. Note que a classe JavaMagazineBean implementa as duas interfaces, JavaMagazineLocal e JavaMagazineRemote. Isto não gera nenhum conflito, pois os métodos comuns a ambas são iguais: na interface remota, os métodos não precisam mais lançar a RemoteException. Digo “métodos comuns ambas” porque não precisamos expor todos os métodos do bean em ambas as interfaces. Assim como nas versões anteriores do EJB, temos a liberdade de publicar alguns métodos somente para acesso local e alguns métodos somente para acesso remoto. Uma boa idéia é criar uma nova interface JavaMagazine com todos os métodos em comum e herdá-la em ambas as interfaces local e remoto. (Mas não é possível criar uma interface única e anotá-la com @Local e @Remote simultaneamente.) De qualquer forma, com a possibilidade de implementar todas as interfaces de acesso na classe do bean, ganhamos a tipagem forte que faltava no EJB. Quantas vezes você já definiu um novo método numa interface remota de EJB mas esqueceu de implementá-lo na classe, ou talvez digitou sua assinatura diferente por engano (ex.: m(A a,B b) na interface, e m(B b, A a) no bean)? Embora alguns IDEs possuam validadores que capturam este erro antes de termos todo o trabalho de tentar um deploy, é bem melhor se isto for feito pelo compilador, editor, e outras ferramentas básicas. O EJB 3.0 acaba com este problema, e também ganhamos outras vantagens da tipagem forte, como a possibilidade de fazer refactoring (ex.: ao renomear um método na interface, sua implementação na classe também será renomeada). Agora vamos à implementação do bean. Temos uma anotação @PersistenceContext que faz a injeção de dependência do EntityManager (objeto da JPA responsável por coordenar a persistência). Quando qualquer método de JavaMagazineBean for executado, o atributo EntityManager em unidade de persistência do modulo JavaMagazine-ejb). O método consultaEdicoes() executa uma query que retorna todos os objetos Edicao do banco de dados. Observe que estes objetos já virão preenchidos com as coleções de artigos, devido ao uso da opção fetch = FetchType.EAGER na anotação deste relacionamento. Esta opção informa que desejamos que, sempre um objeto Edicao seja lido, todos os seus artigos sejam lidos no mesmo momento. Esta é uma otimização de desempenho importante (se os artigos forem acessados com freqüência), pois a JPA poderá utilizar uma query única com um outer join, ao invés de queries separadas para o objeto principal e para o relacionamento. Mas o fetch também tem uma utilidade importante quando quisermos usar os objetos com Vos. Enquanto os objetos lidos pela JPA estão associados ao EntityManager e à transação na qual foram lidos, estes objetos são gerenciados pela JPA. Assim, com fetch = FetchType.LAZY (que é o default), a query poderia retornar as instâncias de Edicao com todos os atributos List<Artigo>artigos não inicializados (coleções vazias). Somente quando você utilizasse estas

Page 49: Java Magazine 41

coleções pela primeira vez, por exemplo com edicao.getArtigos().iterator(), a JPA iria fazer uma nova query para preencher a coleção. O problema é que, se você transferir uma Edicao com valor de retorno de uma invocação ao Session Bean JavaMagazine, a aplicação que invocou o bean não recebera um objeto gerenciado pela JPA, e sim objetos comuns – daí, edicao.getArtigos() iria retornar uma coleção vazia. Uma solução seria ter um novo método JavaMagazine.consultaArtigos(Edicao edicao), mas essa solução é complicada e ineficiente. Este problema é bem conhecido dos usuários do Hibernate, onde os objetos que deixam de ser associados a uma sessão persistente são chamados “desvinculados” (detached), e a solução é a mesma. Com a opção fetch=FetchType.EAGER, garantimos que os objetos associados a uma entidade principal sejam lidos, podendo ser retornados com segurança pela interface do Session Bean (ou por outro middleware que não possa transmitir o objeto original, precisando copiá-lo, serializá-lo ou convertê-lo para outro formato: RMI, CORBA, SOAP etc.). Você não precisa mais fazer lookup de interfaces Home de CMPs, converter dados de CPMs para POJOs, e usar DAOs para controlar a bagunça do velho EJB/CMP. Listagem 5.lnterface do Session Bean. package business; import java.util.List; import javax.ejb.Local; import vo.Artigo; import vo.Edicao; @Local public interface JavaMagazineLocal {

void criaEdicao (Edicao edicao); List<Edicao> consultaEdicoes ();

} Listagem 6.lmplementação do SessionBean. package business; import java.util.List; import javax.annotation.Resource; import javax.ejb.Stateless; import javax.persistence.EntityManager; import vo.Artigo; import vo.Edicao; @Stateless public class JavaMagazineBean

implements business.JavaMagazineRemote, business.JavaMagazineLocal {

@PersistenceContext private EntityManager em; public JavaMagazineBean () { } public void criaEdicao (Edicao edicao) {

em.persist(edicao); } public List<Edicao> consultaEdicoes () {

return em.createQuery(“select e from Edicao e”).getResultList();

Page 50: Java Magazine 41

} } Quem mexeu no meu ciclo de vida? Assim como no caso da JPA, também vale para os Sessions Beans a regra que sua codificação será tanto mais simples ou complexa quanto for a necessidade de cada artefato de código. Por exemplo, os EJBs tradicionais definiam vários métodos de ciclo de vida, como ejbCreate(), ejbRemove(), ejbActivate() e setSessionContext() para Session Beans. Todo Session Bean era obrigado a implementar este quadro métodos. Para Entity Beans, eram sete. Já no nosso bean de exemplo, não temos nenhum método dessa natureza. Isso não quer dizer que não existam facilidades de ciclo de vida. Elas existem, mas são identificadas por anotações. Por exemplo, se você quiser executar algum código após a ativação de um bean, basta criar um método como: @PostActivate public void postActivate(InvocationContext ctx) { //Ações a executar após a ativação } A assinatura do método deve ser public void nome (InvocationContext), onde o nome é arbitrário, e deve possuir a anotação @PostActivate. Diferentemente das entidades da JPA, no entanto, ao programar EJBs veremos que na enorme maioria dos casos, não precisamos implementar estes métodos opcionais ou usar as anotações correspondentes. Na minha primeira experiência com o velho J2EE, a grande maioria dos EJBs – mesmo em aplicações complexas – usa implementações vazias da maioria dos métodos das interfaces SessionBean e EntityBean. Portanto, o modelo de programação do EJB 3.0 tende a produzir código muito mais enxuto para EJBs, mesmo no caso de aplicações mais ambiciosas. Implementando o cliente web Para testar nosso servidor, criaremos um módulo web com o projeto JavaMagazine-war. Veremos esta parte mais rapidamente, pois não haverá muitas novidades em relação ao que já falamos. Vamos começar configurando o projeto do NetBeans para utilizar a JSF. Na página de propriedades do projeto Javamagazine-war, acione Frameworks>Add, escolha “JavaServer Faces” e confirme. Isto preencherá o projeto com os arquivos JAR e configurações exigidas pela JSF. A aplicação web terá uma página JSP que permite criar edições e artigos e listar os já cadastrados no banco de dados. Esta página JSP, construída com as taglibs de componentes da JSF, é mostrada na Listagem 7, e sua execução aparece na Figura 6.

Page 51: Java Magazine 41

Figura 6. Executando a interface web. A página JSP utiliza um managed bean que pode ser criado pelo assistente New>JSF Managed Bean. Basta preencher o nome da classe com ClienteJM e do pacote com web, e aceitar os demais defaults. Um managed bean é um objeto que interage com uma página da JSF para implementar a funcionalidade por trás da apresentação, desempenhando o papel de controller na arquitetura MVC. Para quem conhece o Apache Struts, é algo equivalente às Actions – com a vantagem que os managed beans são POJOs (só precisam ser registrados no faces-config.xml, o arquivo de configuração do JSF). Métodos destes beans podem ser invocados para tratar eventos de componentes da JSF; por exemplo, no componente h:commandButton, a diretiva action=”#{ClienteJM.criaDados}” faz o método criaDados() do bean ser executado quando este botão dor submetido. Na Listagem 8, podemos ver que nosso código de controller é mais simples do que o costumeiro em programação J2EE. Não precisamos usar a JNDI para fazer o lookup do Session Bean: basta declarar um atributo JavaMagazineRemote (pois queremos acessar a interface remota, para permitir deploy da aplicação EJB e do cliente web em maquinas diferentes) e anotá-lo com @EJB, que aciona a injeção de dependência. E não precisamos tratar de exceções RemoteException, embora tenhamos utilizado uma interface remota, pois esta exceção não é mais obrigatória. Observe como implementamos o método criaDados(): criamos um objeto Edicao contendo vários Artigos, e invocamos o método criaEdicao() do Session Bean JavaMagazine. Este método é trivial; só invoca o método persist() do EntityManager, passando a Edicao. Seus Artigos serão persistidos “por alcance”, ou seja, objetos referenciados por um objeto persistido também devem ser persistidos. Mas para isso funcionar, temos que declarar o relacionamento Edicao.artigos com a opção cascade=CascadeType.ALL. A regra de cascade determina quais operações de persistência (como inserção, deleção etc.) persistentes são propagadas por alcance; com CascadeType.ALL ativamos todas, que é a opção mais comum para relacionamentos de agregação por valor. Para testar a aplicação finalizada, faça um novo deploy e navegue para a página: http://localhost/JavaMagazine-war/faces/index.jsp Clique duas vezes no botão. A cada vez uma edição será criada, e a página exibirá uma tabela atualizada com todas as edições e artigos cadastrados. Se você tiver utilizado a base de banco de dados Derby do SJSAS, você pode conferir as tabelas facilmente. Na janela,Runtime do NetBeans, no nó jdbc:derby://localhost:1527/sample [app on APP], execute Connect, preenchendo o login e senha como “app”. Você poderá ver a estrutura das tabelas criadas pela JPA, e os registros correspondentes aos objetos que já tiver criado.

Page 52: Java Magazine 41

Listagem 7. Página JSP da GUI. <%@ taglib uri=”http://java.sun.com/jsf/html” prefix="h" %> <%@ taglib uri=”http://java.sun.com/jsf/core” prefix=“f" %> <html> <head>

<title>Java Magazine - Cadastro de Artigos</title> </head> <body>

<f:view> <h:form id="criaDados">

<h:commandButton value="Cria dados de teste" action=“#{ ClienteJM.criaDados}"/>

</h:form> <h:dataTable value="#{

ClienteJM.edicoes}" var=“edicao” border="1"> <h:column>

<f:facet name="header"> <h:outputText value="ID"/>

</f:facet> <h:outputText value="#{edicao.id}"/>

</h:column> <h:column>

<f:facet name="header"> <h:outputText value="Artigos"/>

</f:facet> <h:dataTable value="#{

edicao.artigos}" var="artigo" border=”1”> <h:column>

<f:facet name="header"> <h:outputText value="Titulo"/>

</f:facet> <h:outputTextvalue="#{artigo.titulo}”/>

</h:column> <h:column>

<f:facet name="header"> <h:outputText value="Autor"/)

</f:facet> <h:outputText value="#{artigo.autor}”/>

</h:column> <h:column>

<f:facet name="header"> <h:outputText value="Texto"/>

</f:facet> <h:outputText value="#{artigo.texto}”/>

</h:column> </h:dataTable>

</h:column> </h:dataTable>

</f:view> </body> </html>

Page 53: Java Magazine 41

Listagem 8. Managed bean da GUI. package web; import business.JavaMagazineRemote; import java.util.ArrayList; import java.util.List; import javax.ejb.EJB; import vo.Artigo; import vo.Edicao; public class ClienteJM {

@EJB private JavaMagazineRemote jm; private static int proxArtigo = 1; public ClienteJM () { } public void criaDados () {

Edicao edicao = new Edicao(); List<Artigo> artigos = new ArrayList<Artigo>(); for (int j = 1; j <= 5; ++j) {

artigos.add(new Artigo(edicao, "Titulo" + proxArtigo, "Autor" + proxArtigo. "Texto"));

++proxArtigo; } edicao.setArtigos(artigos); jm.criaEdicao(edicao);

} public List<Edicao> getEdicoes () {

return jm.consultaEdicoes(); }

} Olha, sem JNDI! Talvez você tenha estranhado que até aqui não mostramos nenhum nome JNDI, nem mesmo em lugares onde ocorre lookup (como a anotação @EJB). É que também neste caso, o EJB 3.0 gera valores default para os beans e resource references. Se você quiser, pode ignorar os defaults e forçar nomes JNDI explícitos; por exemplo, @Stateless(name=”ejb/session/JavaMagazine”) e @EJB(name=”ejb/session/JavaMagazine”). Também neste caso, a simplificação feita pelo Java EE 5 tem grande aplicabilidade, pois raras aplicações enterprise edition exigem um padrão especifico e rigoroso de nomes JNDI. Depuração facilitada Durante a programação deste tutorial, houve oportunidades para avalidar a facilidade de uso do Java EE 5 no diagnóstico de problemas. O SJSAS gera logs de erro extremamente volumosos (várias páginas de dumps de muitas exceções em cascata), o que dificulta um pouco localizar as mensagens importantes. Mas praticamente todos os erros são muito claros, notadamente os da JPA; por exemplo: Exception[TOPLINK-4002] (Oracle TopLink Essentials – 2006.4 (Build 060412)): Oracle.toplink.essencials.exceptions.DatabaseException

Page 54: Java Magazine 41

Internal Exception: org.apache.derby.client.am.SqlException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identifield by ‘SQL060928093043370’ defined on ‘ARTIGO’.Error Code: -1 Call:INSERT INTO ARTIGO

(TEXTO, TITULO, AUTOR, EDICAO_ID) VALUES(?,?,?,?) bind =>[Texto, Titulo1, Autor1, 1]

Query: InsertObjectQuery(vo.Artigo@936a66) Se tivéssemos usado uma API de baixo nível como a JDBC, só receberíamos uma SQLException apontando a violação de um constraint de chave-primária. Teríamos que verificar no nosso script de criação do banco a qual a tabela pertence o constraint, inserir logs no programa para capturar a query ou os valores que causam a violação. Com a JPA, todas essas informações são apresentadas de forma automática e bem estruturada. Portanto, o aumento de produtividade vale até quando programamos mal! Conclusões O EJB 3.0 é um dos carros-chefes da história de melhoria de produtividade do Java EE 5. O resultado ainda não é uma curva de aprendizado trivial, pois afinal de contas o EJB continua sendo uma sistema de componentes sofisticados com um número enorme de funcionalidades e conceitos. Mas o EJB 3.0 consegue avanços importantes em várias áreas:

1. Aprendizado incremental. Por exemplo, um iniciante poderá escrever suas primeiras aplicações EJB completas sem nem saber que existe alguma coisa chamada JNDI, pois a plataforma possui comportamentos default inteligentes.

2. Codificação proporcional à complexidade dos problemas – também devido ao uso de comportamentos default.

3. Eliminação das regras “linha-dura” como a exigência da RemoteException. 4. Injeção de dependências, que economiza muito código repetitivo para obtenção de recursos

e conexão com serviços. 5. Substituição de descritores por anotações, o que facilita manter código e configurações em

sincronia, e permite omitir muitas configurações para as quais se pode extrair bons defaults do próprio código.

6. Um sistema de persistência (JPA) completamente novo e muito melhor que o anterior (Entity Beans), unificando as vantagens do Hibernate (persistência de POJOs), do modelo de programação de anotações (nada de arquivos de mapeamento complexos), e do Java EE (como controle automático de transações).

7. Ferramentas como o IDE NetBeans, com recursos com o assistentes para criar entidades e relacionamentos, e validação, auto-correção e auto-complemento de anotações.

Para quem evitava a J2EE devido à sua complexidade, com o Java EE 5 é chegada a hora de reavaliar essas escolhas. Embora as necessidades de muitas aplicações mais simples possam ser satisfeitas pela plataforma Java SE + algumas bibliotecas (como Hibernate), middlewares não-EJB (como RMI ou COBRA), e/ou containers mais simples (como o Tomcat), a integração destes componentes costuma dar algum trabalho e dor de cabeça que não existem ao adotar um servidor Java EE completo. E todos os recursos avançados do Java EE, mesmo que não necessários na primeira versão da sua aplicação, estarão à disposição para uso imediato assim que a aplicação prosperar e crescer. Para saber mais Java EE 5. Edição 39 Visão geral do Java EE 5: conceitos, novidades, funcionalidades. Persistência no Java EE. Edição 39

Page 55: Java Magazine 41

Mais detalhes sobre a Java Persistence API e um exemplo passo a passo. LINKS: jcp.org/em/jsr/detail?id=244 Especificação do Java EE 5. netbeans.org IDE NetBeans, cuja versão 5.5 suporta o Java EE 5. java.sun.com/javaee/downloads Java EE 5 SDK/ SJSAS, a Implementação de Referência do Java EE 5. glassfish.dev.java.net Projeto GlassFish, distribuição open source do mesmo servidor. Agendamento de tarefas em Java Usando o melhor que o Java SE e EE oferecem Tire o máximo proveito dos recursos de agendamento disponíveis da Java SE e Java EE, e veja como criar uma aplicação Swing de reservas usando vários design patterns Neste artigo, vamos abordar os recursos de agendamento de tarefas disponíveis em Java SE e Java EE. Apresentaremos as classes Timer e TimerTask, presentes na distribuição padrão do Java SE desde a versão 1.3, e o serviço de Timer (Timer Service) acrescentado ao EJB na versão 2.1. Para ilustrar a utilização, criaremos um sistema completo de reservas com interface Swing. Agendamento de tarefas O agendamento de tarefas permite programar tarefas (Jobs) para execução em momentos específicos, ou periodicamente. Este recuso é suportado nos próprios sistemas operacionais, por exemplo através do agendador de tarefas do Windows ou utilitário cron do Linux/Unix.Veja alguns cenários onde o agendamento de tarefas é útil:

· Realizar um backup periodicamente · Criar diariamente um relatório consolidado de vendas para a gerência · Enviar um e-mail para os usuários do sistema cuja senha tenha expirado · Verificar a cada cinco minutos se foram copiados novos arquivos para um

determinado diretório. O Java oferece elementos para o trabalho com agendamento de tarefas, tanto em sua plataforma Standard como na Enterprise, como veremos em detalhes a seguir. O estudo de caso Para ilustrar a utilização do agendamento de tarefas com Java, criamos uma aplicação de exemplo que permite a criação de tarefas de verificação automática de expiração de reservas. Poderíamos utilizar este aplicativo em qualquer sistema que trabalhe com o conceito de reserva, como por

Page 56: Java Magazine 41

exemplo hotéis, restaurantes, teatros ou empresas aéreas. A idéia é permitir que o próprio aplicativo verifique e atualize os status de reservas expiradas. Cada reserva é representada por um objeto da classe br.com.globalcode.model.Reserva. Esta classe tem como atributos um código, uma data de expiração, um status (PENDENTE,CONFIRMADA OU CANCELADA) e um id, que corresponde à chave primária no banco de dados. Veja o código completo na Listagem1. O acesso aos dados persistidos para as reservas é realizado através da classe br.com.globalcode.dao.ReservasDAO. Além dos métodos diretamente relacionados à manipulação de reservas, esta classe contém os métodos para obtenção e fechamento da conexão com o banco de dados. Usualmente, estes métodos são implementados em uma classe separada, de maneira a serem compartilhados entre os diversos DAOs do aplicativo, mas no exemplo, por simplificação estão na própria ReservasDAO. A figura 1 apresenta o diagrama de classe de ReservasDAO. O código completo desta classe e de todo resto da aplicação está disponível no site da Java Magazine. Além de reserva também criamos a classe br.com.globalcode.model.Agendamento (Listagem 2), que contém apenas uma descrição e a data e o horário do agendamento. No nosso exemplo o agendamento é sempre diário; por isso esta classe não contém o intervalo para repetição. Note também que, como a classe Agendamento não precisa ser persistida, não criamos um DAO para ela.

Figura 1. Diagrama de classe de ReservasDAO Listagem 1. Reserva. package br.com.globalcode.model; import java.text.SimpleDateFormat; import java.util.Date; public class Reserva {

public static final SimpleDateFormat FORMATADOR_DATA_USUARIO = new SimpleDateFormat("dd/MM/yyyy hh:mm");

private String codigo; private Date dataExpiracao; private int id; private int status = PENDENTE; public static final int PENDENTE= 1; public static final int CANCELADA= 2; public static final int CONFIRMADA= 3; public Reserva() { } public Reserva(String codigo. Date dataExpiracao){

this.codigo = codigo;

Page 57: Java Magazine 41

this.dataExpiracao = dataExpiracao; } public Reserva(int id, String codigo, Date dataExpiracao){

this(codigo,dataExpiracao); this.id = id;

} public Reserva(int id, String codigo, Date dataExpiracao. int status){

this(codigo,dataExpiracao); this.id = id; this.status = status;

} public String toString(){

return "Codigo = " + codigo + " data expiração = " + FORMATADOR_DATA_ USUARIO.format(dataExpiracao) + " id= " + id + "status = " + status;

} //getters e setters omitidos

} Listagem 2.Agendamento. package br.com.globalcode.model; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; public class Agendamento implements Serializable{

public static final SimpleDateFormat FORMATADOR_DATA_USUARIO = new SimpleDateFormat("dd/MM/yyyy hh:mm");

private String info; private Date data;

public Agendamento() { } public Agendamento(String info, Date data){

this.info = info; this.data = data;

} public String toString(){

return info + " " + FORMATADOR_DATA_USUARIO.format(data); } //método equals, hashcode, getters e setters omitidos

} Agendamento de tarefas com Java SE

Page 58: Java Magazine 41

Desde a versão 1.3, estão disponíveis duas classes para o agendamento de tarefas no Java SE: java.util.Timer e java.util.TimerTask. java.util.TimerTask A classe abstrata TimerTask representa uma tarefa a ser agendada. É uma implementação de java.lang.Runnable. Portanto para criar tarefas devemos criar subclasses de TimerTask e implementar o método run(), da mesma forma que é feito quando trabalhamos com operações a serem executadas por threads. A classe permite também cancelar a tarefa (cancel()) e obter a data da sua última execução (scheduledExecutionTime()). A classe br.com.globalcode.tasks.VerificadorReservasExpiradas ilustra um exemplo de tarefa baseada em TimerTask. Essa tarefa tem o objetivo de alterar o status de todas as reservas que expiraram até o momento. Ela utiliza a classe ReservasDAO que implementa o design pattern DAO contendo os métodos para interação com o banco de dados. Veja mais no quadro “Patterns Utilizados”. O código da classe está na Listagem 3. Listagem 3. VerificadorReservasExpiradas. package br.com.globalcode.tasks; import br.com.globalcode.dao.ReservasDAO; import java.sql.SQLException; import java.util.*; public class VerificadorReservasExpiradas extends TimerTask{

public void run() { ReservasDAO dao = new ReservasDAO(); try {

dao.cancelarReservasExpiradas(new Date()); } catch (Exception ex) {

String msg = " Não foi possível executar o cancelamento de reservas expiradas";

System.out.println("[VerificadorReservasExpiradas] ERRO : " + msg); ex.printStackTrace() ;

} }

} java.util.Timer A classe Timer é responsável por realizar o agendamento das tarefas (objetos TimerTask). Tarefas podem ser agendadas basicamente de duas maneiras: executar a tarefa uma única vez em um instante determinado, ou executar a tarefa periodicamente. Podemos configurar o instante da primeira execução usando um objeto java.util.Date ou definido um intervalo de tempo (em milissegundos) relativo ao momento atual:

· Schedule (TimerTask tarefa, Date início) · Schedule (TimerTask tarefa, long atrasoAtelnicio)

Para os agendamentos periódicos há duas situações possíveis. O período pode ser considerado a partir da última execução da tarefa (atraso fixo ou fixed-delay):

· Schedule (TimerTask tarefa, Date início,long período) · Schedule (TimerTask tarefa, long atrasoAtelnicio,long período)

Ou o período pode ser contado a partir da data de início (taxa fixa ou fixed-rate):

Page 59: Java Magazine 41

· scheduleAtFixedRate (timerTask tarefa, Date inicio, long periodo) · scheduleAtFixedRate (TimerTask tarefa, long atrasoAtelnicio, long

periodo)

Exemplificando os agendamentos periódicos, suponha que temos um agendamento com início às 00:00:00 e periodicidade de 10 segundos, e que por alguma razão houve um atraso que só permitiu que a tarefa das 00:00:10 fosse iniciada às 00:00:12. Se for usado o agendamento com fixed-delay, as próximas execuções seriam às 00:00:22, 00:00:32, 00:00:42, etc. O intervalo entre as execuções permanece constante. Esse tipo de agendamento pode gerar distorções ao longo do tempo, ou seja uma tarefa que deveria ser executada todos os dias às 2:00 pode acabar sendo executada às 2:10, digamos. Já se for usado o agendamento do tipo fixed-rate, as próximas execuções serão às 00:00:20, 00:00:30, 00:00:40 etc. sem atrasos. Note que, neste caso, entre a primeira e a segunda execuções ocorreu um intervalo de 8 segundos-menor que o configurado. Ou seja, esse tipo de agendamento não gera distorções de horário ao longo do tempo, mas pode “encavalar” execuções de tarefas se uma delas atrasar demais. A cada objeto Timer corresponde uma thread rodando em segundo plano, que irá chamar o método run() de cada TimerTask à medida que seus respectivos intervalos forem expirando. Esta thread por padrão não é do tipo daemon, ou seja, mantém a JVM no ar mesmo que não existam outras threads do aplicativo executando além do Timer. Caso desejemos utilizar threads daemon, podemos instanciar o timer passando true no construtor. Além disso, podemos especificar no construtor o nome da Thread que vai ser criada (Java SE 5 ou superior). E podemos cancelar um Timer (método cancel()), eliminando todos os agendamentos de uma vez, ou limpar de sua lista de tarefas aquelas que foram canceladas (método purge()). A classe br.com.globalcode.GerenciadorAgendamentosJavaSE ilustra em seu método agendar Horario(), um exemplo de criação de um timer que gerencia os agendamentos para a tarefa VerificadorReservasExpiradas.GerenciadorAgendamentosJavaSE possui um Map estático que contém todos os agendamentos realizados, bem como métodos para recuperar todos os agendamentos para esta tarefa, agendar novos horários e cancelar horários previamente agendados. Veja o código dessa classe na Listagem 4. Listagem 4. GerenciadorAgendamentosJavaSE. package br.com.g1oba1code; import br.com.g1oba1code.dto.Agendamento; import br.com.g1oba1code.tasks.VerificadorReservasExpiradas; import br.com.g1oba1code.dto.*; import java.util.*; public class GerenciadorAgendamentosJavaSE {

public static Map agendamentos = new HashMap();

public void agendarHorario(Date dataAgendamento, String descricao){ Agendamento agendamento = new Agendamento(descricao,dataAgendamento); System.out.println(

"[GerenciadorAgendamentosJavaSE agendarHorario ] " + agendamento);

Timer timer = new Timer(); // Agendamento diário, ou seja periodo de repetição = 24 horas timer.schedule(new VerificadorReservasExpiradas(),

dataAgendamento, 24*60*60*1000);

Page 60: Java Magazine 41

agendamentos.put(agendamento, timer); }

public Collection getAgendamentos() {

return agendamentos.keySet(); }

public void cancelarAgendamento(Agendamento agendamento) {

System.out.println( "[GerenciadorAgendamentosJavaSE cancelarAgendamento] " + agendamento);

Timer timer = (Timer)agendamentos.remove(agendamento); if (timer != null){

timer.cancel (); }

} } Agendamento de Tarefas com Java EE No Java Enterprise Edition, desde o J2EE 1.4, está disponível o serviço de timer para EJBs. O objetivo é especificar um serviço gerenciado pelo servidor de aplicações, que permite o registro de EJBs para serem chamados como tarefas agendadas. Neste modelo de agendamento, temos três elementos básicos (EJB), o agendamento (uma instância de javax.ejb.Timer) e o agendador (instância de javax.ejb.TimerService). Veja a Figura 2.

Figura 2. Elementos do agendamento de tarefas em Java EE Desta forma, não precisamos criar uma classe especial para funcionar como tarefa agendada. Basta configurar um método especial no EJB (chamado de método de timeout ou callback) para que aceite chamadas do serviço de timer do container. Para isto, temos duas abordagens, que variam de acordo com a versão de EJB utilizada:

· EJB 2.1 – Implementamos a interface javax.ejb.TimedObject e redefinimos o método ejbTimeout(javax.ejb.Timer).

· EJB 3.0 – Em vez de implementar a interface javax.ejb.TimedObject (o que também é suportado), podemos anotar o método de timeout com @Timeout ou configurar um elemento <timeout-method> no descritor de deployment. O método pode ter qualquer nome, mas deve receber como parâmetro uma distância de javax.ejb.Timer. Cada bean pode ter somente um método de timeout.

Javax.ejb.Timer A interface Timer representa um agendamento. Cada servidor de aplicações possui a sua implementação específica.Os principais métodos de Timer são:

· voidcancel() – Cancela os segmento. · Date getNextTimeout() – Retorna a data da próxima execução da

tarefa. · long getTimeRemaining() – Retorna o tempo em milissegundos que

falta para a próxima execução da tarefa.

Page 61: Java Magazine 41

· TimerHandle getHandle() – Retorna uma referência serializável do timer, que pode ser utilizada, por exemplo, para armazenamento em sessões web.

· Serializable getinfo() – Quando um agendamento é criado, podemos passar um objeto serializável qualquer para ser utilizado ao processamento da tarefa. Este método permite que o EJB recupere tal objeto.

javax.ejb.TimerService A interface TimerService representa o agendador (cada servidor de aplicações tem a sua implementação específica). São oferecidos métodos para criar e listar agendamentos criados e, de maneira análoga aos agendamentos do Java SE, é possível executar uma tarefa uma vez só em um determinado instante, ou repetidamente de acordo com um intervalo configurado. Os métodos de agendamento de TimerService são:

· Timer createTimer ( Date inicio,Serializable info) · Timer createTimer (long atrasoAtelnicio,Serializable info) · Timer createTimer (Date inicio, long period, Serializable info) · Timer createTimer (long atrasoAtelnicio,long periodo,Serializable

info)

Neste ponto, o leitor pode estar se indagando como é feita a amarração entre o Timer e o componente EJB. Em Java SE, é passado um objeto TimerTask no método de agendamento, mas com TimerService só passamos o período e um objeto serializável (ou null, se não existir nenhum). O que ocorre é que o próprio TimerService faz o registro do EJB que chamou o método de criação do agendamento, de maneira implícita. Para obter uma referência para um TimerService, o desenvolvedor do EJB tem três opções:

· Utilizar o método getTimerService() do seu contexto (EJBContext) · Fazer um lookup JNDI · Usar injeção de dependência utilizando a anotação @Resource

Após obter a referência para um TimerService, o EJB pode se registrar através de um dos métodos createTimer(). Em Java SE tínhamos o agendamento periódico fixed-delay (criado através do método Schedule()) e de taxa fixa (criado com scheduleAtFixedRate()). O agendamento periódico do TimerService tem funcionamento idêntico ao tipo fixed-rate do Java SE, ou seja, o intervalo é relativo à data inicial do Timer. Além disso, é possível utilizar o TimerService para obter uma lista dos Timers configurados para um determinado EJB, usando o método getTimers(). As Listagens 5, 6, 7 e 8 contêm as classes e interfaces necessárias para a criação de um componente EJB (versão 2.1) utilizando o serviço de agendamento do Java EE. Este EJB implementa funcionalidades equivalentes à classe VerificadorReservasExpiradas apresentada no exemplo para Java SE. As seguintes classes e interfaces foram criadas:

· VerificadorReservasBeanRemote (Listagem 5) – Interface remota do EJB (permite que o bean seja acessado remotamente, ou seja, por componentes externos ao container EJB) · VerificadorReservasBeanRemoteBusiness (Listagem 6) – Interface que define os métodos de negócio do EJB. Foram definidos métodos para criação de agendamentos para verificação de reservas expiradas (método agendarHorario()), recuperação dos agendamentos realizados (getAgendamentos()) e cancelamento de agendamentos (cancelarAgendamento()). · VerificadorReservasBeanHome (Listagem 7) – Interface home do EJB, que contém os métodos para criação dos EJBs.

Page 62: Java Magazine 41

· VerificadorReservasBean (Listagem 8) – Implementação dos métodos de criação do EJB, dos métodos exigidos pela interface TimedObject, além dos métodos de negócio definidos na interface VerificadorReservasBeanRemoteBusiness.

Listagem 5. VerificadorReservasBeanRemote. package br.com.globalcode.ejb; import javax.ejb.EJBObject; public interface VerificadorReservasRemote extends EJBObject,

VerificadorReservasRemoteBusiness {} Listagem 6. VerificadorReservasBeanRemoteBusiness. package br.com.globalcode.ejb; import br.com.globalcode.model .Agendamento; import java.rmi.RemoteException; import java.util.*; public interface VerificadorReservasRemoteBusiness {

void agendarHorario(Date dataInicio, String descricao) throws RemoteException;

Collection getAgendamentos() throws RemoteException;

void cancelarAgendamento(Agendamento agendamento)

throws java.rmi.RemoteException; } Listagem 7. VerificadorReservasBeanHome. package br.com.globalcode.ejb; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface VerificadorReservasRemoteHome extends javax.ejb.EJBHome {

VerificadorReservasRemote create() throws javax.ejb.CreateException, RemoteException;

} Listagem8. VerificadorReservasBean. package br.com.globalcode.ejb; import java.rmi.RemoteException; import java.sql.SQLException; import javax.ejb.*;

Page 63: Java Magazine 41

import java.util.*; import br.com.globalcode.dao.*; import br.com.globalcode.model.Agendamento; public class VerificadorReservasBean implements

SessionBean, VerificadorReservasRemoteBusiness, TimedObject

{ private SessionContext context;

public void setSessionContext(

SessionContext aContext) { context = aContext;

} public void ejbActivate() {} public void ejbPassivate() {} public void ejbRemove() {} public void ejbCreate() {} public void cancelarReservasExpjradas(Date dataAtual)

{ ReservasDAO dao = new ReservasDAO(); try {

dao.cancelarReservasExpiradas(dataAtual) ; } catch (Exception ex) {

System.out.println( "Erro ao cancelar reservas

expiradas dataAtual = "+ dataAtual); ex. printStackTrace();

} }

public void ejbTimeout (Timer timer) {

cancelarReservasExpiradas(new Date()); }

public void agendarHorario(

Date dataAgendamento, String descricao) { TimerService timerService = context.getTimer

Service();

timerService.createTimer( dataAgendamento,24*60*60*1000,descricao);

System.out.println("Agendando servico de timer (“ + descricao+ ")diario as "+ dataAgendamento);

}

public Collection getAgendamentos() { TimerService timerService =

context.getTimerService(); Collection agendamentos = new ArrayList(): Iterator iteratorTimers =

timerService.getTimers().iterator();

Page 64: Java Magazine 41

System.out.println(" Os seguintes agendamentos de serviços foram localizados

("+timerService.getTimers().size()+")"); while (iteratorTimers.hasNext()){ Timer timer = (Timer)iteratorTimers.next(); agendamentos.add(new Agendamento(

(String)timer.getInfo(), timer.getNextTimeout()));

}

return agendamentos; }

public void cancelarAgendamento(

Agendamento agendamento) { TimerService timerService =

context.getTimerService(); Collection agendamentos = new ArrayList(); Iterator iteratorTimers =

timerService.getTimers().iterator(); while (iteratorTimers.hasNext()){

javax.ejb.Timer timer = ( javax.ejb.Timer)iteratorTimers.next();

if (timer.getInfo() .equals( agendamento.getlnfo())){

timer.cancel (); }

} }

} Vantagens e limitações de cada modelo Tendo discutido e demonstrado os recursos de agendamento fornecidos pelo Java SE e EE, vamos comentar algumas vantagens e limitações de cada um deles. A Tabela 1 apresenta um resumo. Recurso Vantagens Limitações Timer e TimerTask

• Simplicidade; • Não é necessário nada além do JRE.

• Perdem-se os agendamentos em caso de queda da JVM • Só permite dois tipos de agendamento: execução futura uma única vez ou com intervalo fixo.

TimerSerice e EJBs

• Podemos reaproveitar um EJB já existente; • Utilizamos os serviços do container EJB de controle de transações e pooling de objetos; • Há a recuperação automática de agendamentos no caso da

• Exige um container EJB/servidor de aplicações; • Só permite dois tipos de agendamento: execução futura uma única vez ou intervalo fixo. • Não permite agendamento via arquivo de configuração (agendamento declarativo).

Page 65: Java Magazine 41

queda do servidor. Tabela 1. Resumo das vantagens e limitações do agendamento padrão em Java SE e EE. No Java SE A forma mais simples de criar agendamentos é utilizar as classes Timer e TimerTask do Java SE. Não precisamos de nenhum recurso adicional de infra-estrutura, bastando haver uma instalação do JRE. Porém, para que o agendamento ocorra, precisamos manter a JVM em execução. Em caso de queda da JVM será necessário refazer todos os agendamentos. Além disso, há apenas dois tipos de agendamento suportados. Podemos executar a tarefa uma única vez em um instante determinado, ou executá-la periodicamente com intervalo fixo. Caso seja necessário um agendamento mais complexo, como por exemplo gerar um relatório de segunda a sexta às 17:00, mas não aos sábados ou domingos, temos que fazer malabarismos no código. Outra limitação é que não existe um mecanismo padrão para configurar o agendamento de maneira declarativa, ou seja, através da leitura de um arquivo. As alternativas envolvem colocar os períodos fixos no código ou criar um mecanismo proprietário de leitura. No Java EE Quando utilizamos o serviço de Timer do Java EE, precisamos de um servidor de aplicações J2EE/Java EE. Em compensação, usufruímos de todos os demais serviços de infra-estrutura fornecidos pela plataforma Java EE, além do agendamento em si. Assim temos, por exemplo:

· Gerenciamento automático de transações – O método de callback pode ser executado dentro de uma transação. Caso a transação seja cancelada (rollback), o container tenta novamente a execução. Além disso, tipicamente a criação de um Timer e seu cancelamento também ocorrem dentro de uma transação, permitindo que as operações sejam desfeitas no caso de um rollback. · Gerenciamento de recursos – Um agendamento fica associado à classe do EJB (com exceção de Entity Beans), e não a uma determinada instância, permitindo que o container selecione qualquer objeto disponível para o atendimento do Timer. Isso permite uma melhor utilização dos objetos disponíveis no servidor – não há desperdícios com objetos alocados sem fazer nada, esperando que o Timer expire para executarem. No caso de um Entity Bean, o agendamento fica associado à chave primária da entidade. Caso a entidade associada seja removida do banco de dados, o seu Timer é automaticamente cancelado. Isto evita a alocação de Timers para entidades que não existem mais no banco de dados, desperdiçando recursos.

Outra vantagem da utilização do TimerService é sua maior robustez, que permite que os agendamentos sobrevivam a quedas do servidor. Quando o servidor é reiniciado, os agendamentos são recuperados. Porém, assim como em Java SE, só há suporte para dois tipos de agendamento, e não existe um padrão para configuração declarativa de agendamentos. Caso precisaremos destes recursos podemos utilizar frameworks de agendamento, como o excelente projeto open source Quartz (opensymphony.com/quartz), que é apresentado em outro artigo nesta edição. Aplicação Swing para teste dos agendamentos Para demonstrar as várias formas de agendamento, usando Java SE e EE, criamos uma aplicação em Swing utilizando o editor visual do NetBeans 5.5 (e o gerenciador de layoutn GroupLayout). Veja o resultado final na Figura 3.

Page 66: Java Magazine 41

Figura 3. Tela principal da aplicação Swing para agendamentos de serviços Camada de controle Para deixar a aplicação Swing independentemente de uma implementação específica de agendamento com Java EE ou Java SE, utilizamos os patterns Business Delegate, Service Locator e Factory (veja mais sobre estes e outros design patterns no quadro ´´Patterns utilizados``). Foram implementadas as seguintes classes e interfaces:

· GerenciadorAgendamentosDelegate (Listagem 9) – Interface que define os métodos utilizados pelo cliente Swing. · GerenciadorAgendamentosDelegateJavaEE (Listagem 10) – Implementação de interface GerenciadorAgendamentosDelegate para agendamentos de tarefas utilizando Java EE. · GerenciadorAgendamentosDelegateJavaSE (Listagem 11) – Implementação da interface GerenciadorAgendamentosDelegate para agendamentos de tarefas utilizando Java SE. · ServiceLocator – Classe utilizada para obter uma referência para o EJB que implementa o agendamento de tarefas com Java EE. Sua listagem encontra-se disponível no download deste artigo. · GerenciadorAgendamentosFactory (Listagem 12) – Implementação do padrão Factory utilizando para permitir que a aplicação Swing possa criar objetos dos tipos GerenciadorAgendamentosDelegateJavaEE ou GerenciadorAgendamentosJavaSE indiretamente.

Para implementação do agendamento Java EE foi utilizado o servidor de aplicações JBoss. Para o acesso do aplicativo Swing ao servidor foi criado o arquivo de configuração jndi.properties, que deve estar localizado no diretório raiz das classes do aplicativo. A Listagem 13 mostra o conteúdo deste arquivo. Além disso, é necessário disponibilizar no classpath do aplicativo, o conjunto de classes de acesso ao servidor. Estas classes estão localizadas no arquivo jbossall-client.jar, disponível no diretório de bibliotecas-cliente do JBoss (client). Listagem 9. GerenciadorAgendamentosDelegate. package br.com.globalcode.delegate; import br.com.globalcode.model.Agendamento; import java.util.*;

Page 67: Java Magazine 41

public interface GerenciadorAgendamentosDelegate { void agendarHorario(Date dataAgendamento,

String descricao) throws DelegateExeeption;

void cancelarAgendamento(Agendamento agendamento) throws DelegateExeeption;

Collection getAgendamentos()

throws DelegateExeeption; } Listagem 10. GerenciadorAgendamentosDelegateJavaEE. package br.com.globalcode.delegate; import br.com.globalcode.ejb.VerificadorReservasRemote; import br.com.globalcode.locator.ServiceLocator; import br.com.globalcode.model.Agendamento; import java.rmi.RemoteException; import java.Util.*; import java.util.Date; public class GerenciadorAgendamentosDelegateJavaEE

implements GerenciadorAgendamentosDelegate {

public void agendarHorario( Date dataAgendamento, String descricao)

throws DelegateException {

try { VerificadorReservasRemote verificador =

ServiceLocator.getInstance( ).lookupVerificadorReservasBean();

verificador.agendarHorario( dataAgendamento, descricao);

} catch (RemoteException ex) { throw new DelegateException(

"Não foi possível agendar o serviço " + descricao + " para a data" + dataAgendamento, ex);

} }

public void cancelarAgendamento(

Agendamento agendamento) throws DelegateException{ try{

VerificadorReservasRemote verificador = ServiceLocator.getInstance().

lookupVerificadorReservasBean(); verificador.cancelarAgendamento(agendamento);

} catch (RemoteException ex) { throw new DelegateException(

"Não foi possível cancelar o agendamento "

Page 68: Java Magazine 41

+ agendamento, ex); }

}

public Collection getAgendamentos() throws DelegateException

{ try {

VerificadorReservasRemote verificador = ServiceLocator.getInstance(

).lookupVerificadorReservasBean(); return verificador.getAgendamentos();

} catch (RemoteException ex) { throw new DelegateException ( "Não foi possível obter todos os agendamentos", ex);

} }

} Listagem 11.GerenciadorAgendamentosDelegateJavaSE. package br.com.globalcode.delegate; import br.com.globalcode.GerenciadorAgendamentosJavaSE; import br.com.globalcode.model.Agendamento; import java.util.*; public class GerenciadorAgendamentosDelegateJavaSE

implementsGereneiadorAgendamentosDelegate {

public void agendarHorario( Date dataAgendamento, String descricao)

{ GerenciadorAgendamentosJavaSE gerenciador =

new GerenciadorAgendamentosJavaSE(); gerenciador.agendarHorario(

dataAgendamento, descricao); }

public void cancelarAgendamento(

Agendamento agendamento) {

GerenciadorAgendamentosJavaSE gerenciador = new GerenciadorAgendamentosJavaSE();

gerenciador.cancelarAgendamento(agendamento); }

public Collection getAgendamentos() {

GerenciadorAgendamentosJavaSE gerenciador = new GerenciadorAgendamentosJavaSE();

return gerenciador.getAgendamentos(); }

Page 69: Java Magazine 41

} Listagem 12. GerenciadorAgendamentosFactory. package br.com.globalcode.factory; import br.com.globalcode.GerenciadorAgendamenkosJavaSE; import br.com.globalcode.delegate.*; public class GerenciadorAgendamentosFactory {

public static int GERENCIADOR_AGENDAMENTOS_JAVASE = 1; public static int GERENCIADOR_AGENDAMENTOS_JAVAEE = 2; private static GerenciadorAgendamentosFactory instance =

new GerenciadorAgendamentosFactory();

private GerenciadorAgendamentosFactory() {}

public GerenciadorAgendamentosDelegate createGerenciadorAgendamentos (int tipo)

{ if (tipo == GERENCIADOR_AGENDAMENTOS JAVAEE) {

return new GerenciadorAgendamentosDelegateJavaEE(); } else if (tipo == GERENCIADOR_AGENDAMENTOS JAVASE){

return new GerenciadorAgendamentosDelegateJavaSE(); } else return null;

}

public static GerenciadorAgendamentosFactory getInstance() { return instance;

} } Listagem 13. jndi.properties. java.naming.provider.url=localhost java.naming.factory.initial =

org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=

org.jboss.naming:org.jnp.interfaces

Camada de Apresentação Para maior modularização da aplicação, separamos o painel de agendamento e a janela principal. Para o painel de agendamentos criamos uma classe derivada de JPanel chamada PanelAgendamentoTarefas que é incluída pela janela principal, representada pela classe Gui. Esta ultima herda de JFrame. A classe PanelAgendamentoTarefas (Listagem 14) é responsável por listar os agendamentos ativos. Para isto utilizamos um componente JList. Quando um agendamento específico é selecionado, são exibidos seus atributos na janela.

Page 70: Java Magazine 41

Cada agendamento é vinculado ao formulário através da chamada ao método bind() que recebe um objeto da classe Agendamento e preenche seus dados nos campos do formulário. A leitura dos dados do formulário e a criação de um novo objeto Agendamento são realizadas através do método unbind(). Veja mais na Figura 4. Para iniciar o aplicativo devemos executar a classe Gui. No método init() desta classe, indicamos qual gerenciador de agendamentos (implementação com Java SE ou Java EE) deverá ser utilizado. O objeto GerenciadorAgendamentos é criado através da fábrica GerenciadorAgendamentosFactory, e configurado no PanelAgendamentoTarefas através do método setGerenciadorAgendamentos(). Os seguintes trechos de código ilustram a utilização das implementações baseadas em Java SE e Java EE: Veja o código da classe Gui na Listagem 15.

Figura 4. Sobre a implementação da tela principal Listagem 14. PanelAgendamentoTarefas. package br.com.globalcode.gui; import br.com.globalcode.delegate.*; import br.com.globalcode.model.Agendamento; import java.io.Serializable; import java.text.*; import java.util.*; import javax.swing.JList; public class PanelAgendamentoTarefas

extends javax.swing.JPanel implements Serializable {

//...algumas declaraçôes de variáveis e métodos omit. private GerenciadorAgendamentosDelegate

Page 71: Java Magazine 41

gerenciadorAgendamentos; private Agendamento entity; public static final

SimpleDateFormat FORMATADOR_DATA_USUARIO = new SimpleDateFormat("dd/MM/yyyy hh:mm");

public void configurarEstadoBotoes(

boolean salvar, boolean cancelar, boolean novo) {

buttomSalvar.setEnabled(salvar); buttomNovoAgendamento.setEnabled(novo); buttonCancelarAgendamento.setEnabled(cancelar);

}

public void bind(Agendamento agendamento){ this.entity = agendamento; tfAgendamentolnfo.setText(entity.getlnfo()); tfDataInicio.setText(

entity.getDataFormatada().substring(0,10)); tfHorarioAgendamento.setText(

entity.getDataFormatada().substring(11,16)); }

public Agendamento unbind(){

Date dataAgendamento; String strDataAgendamento = tfDataInicio.getText()

+ " " + tfHorarioAgendamento.getText(); try {

dataAgendamento = FORMATADOR_DATA_USUARIO.parse( strDataAgendamento);

entity = new Agendamento( tfAgendamentoInfo.getText().dataAgendamento);

tfAgendamentolnfo.setText(""); tfDataInicio.setText(""); tfHorarioAgendamento.setText(""); mensagemErro.setText("");

} catch (ParseException ex) { ex.privtStackTrace(); mensagemErro.setText("Data inválida");

} return entity;

} public void atualizarListaAgendamentos(){

jListAgendamentos.setListData(( Vector)getAgendamentos());

} public void configurarNovoAgendamento(

Agendamento agendamento){

try { gerenciadorAgendamentos.agendarHorario(

agendamento.getData(), agendamento.getlnfo());

Page 72: Java Magazine 41

mensagemErro.setText(""); } catch (DelegateException ex) {

ex.printStackTrace(); mensagemErro.setText("Erro; " + ex.getMessage());

} atualizarListaAgendamentos();

}

public void cancelarAgendamento( Agendamento agendamento){

try { gerenciadorAgendamentos.cancelarAgendamento(

agendamento); mensagemErro.setText("");

} catch (DelegateException ex) { ex.printStackTrace(); mensagemErro.setText("Erro: " + ex.getMessage());

} }

//declarações de variável e código gerado //automaticamente pelo NetBeans omitidos private void buttomHojeActionPerformed(

java.awt.event.ActionEvent evt) {

Date hoje = new Date(); SimpleDateFormat formatador =

new SimpleDateFormat("dd/MM/yyyy"); this.tfDatalnicio.setText(formatador.format(hoje));

}

private void buttonCancelarAgendamentoActionPerformed( java.awt.event.ActionEvent evt)

{ entity = unbind(); cancelarAgendamento(entity); atualizarListaAgendamentos(); mensagemErro.setText(""); configurarEstadoBotoes(false, false, true);

}

private void buttomSalvarActionPerformed( java.awt.event.ActionEvent evt) {

System.out.println("[buttomSalvarActionPerformed]"); Agendamento agendamento = unbind(); configurarNovoAgendamento(agendamento); configurarEstadoBotoes(false, false, true);

}

private void buttomNovoAgendamentoActionPerformed( java.awt.event.ActionEvent evt)

{ System.out.println(

Page 73: Java Magazine 41

"[buttomNovoAgendamentoActionPerformed]"); this.entity = null; this.tfAgendamentolnfo.setText(""); tfDataInicio.setText(""); tfHorarioAgendamento.setText(""); configurarEstadoBotoes(true,false, false);

}

private void jListAgendamentosValueChanged{ javax.swing.event.ListSelectionEvent evt)

{ JList listaAgendamentos = (JList) evt.getSource(); Agendamento agendamento = ( Agendamento) listaAgendamentos.getSelectedValue(); System.out.println("[jListAgendamentosValueChanged] Agendamento Selecionado" + agendamento );

if (agendamento!=null){

bind(agendamento); } configurarEstadoBotoes(false, true, true);

} } Listagem 15. Gui. package br.com.globalcode.gui; import br.com.globalcode.factory.GerenciadorAgendamentosFactory; public class Gui extends javax.swing.JFrame {

public Gui () { initComponents();

panelAgendamentoTarefas.setGerenciadorAgendamentos(

GerenciadorAgendamentosFactory.getlnstance() .createGerenciadorAgendamentos(GerenciadorAgendamentosFactory

.GERENCIADOR_AGENDAMENTOS_JAVASE)); /* panelAgendamentoTarefas.setGerenciadorAgendamentos(

GerenciadorAgendamentosFactory.getInstance() .createGerenciadorAgendamentos (GerenciadorAgendamentosFactory

.GERENCIADOR_AGENDAMENTOS_JAVAEE)); */

} // ... Omitidos: declarações de variáveis e código // gerado automaticamente pelo NetBeans

} Conclusões O recurso de agendamento de tarefas possui aplicações, e Java oferece classes que permitem sua utilização tanto nas plataformas SE (através de java.util.Timer e java.util.TimerTask), quanto EE (através de javax.ejb.TimerService). Em ambas as plataformas a criação das tarefas e de

Page 74: Java Magazine 41

seus agendamentos é bastante simples, permitindo que mesmo um usuário iniciante possa desenvolver aplicativos que aproveitem este recurso. LINKS: theserverside.com/tt/article.tss?!=MonsonHaefel-Column4 Página que apresenta um artigo de Richard Monson-Haefel sobre TimerService opensymphony.com/quartz Site do framework de agendamento Quartz Patterns Utilizados O estudo de caso criado neste artigo faz uso de cinco design patterns importantes no desenvolvimento corporativos com Java: DAO (Data Acess Object) Pattern para desacoplamento entre o código de negócio e o código de persistência. Envolve a criação de uma classe separada, com todo o código de acesso ao mecanismos de persistência. Isto traz maior flexibilidade, permitindo a troca do mecanismo de persistência (por exemplo, alterar o banco de dados usado, ou mesmo, de banco de dados para outros mecanismos, como arquivos XML), sem que seja necessária uma alteração em outras classes do aplicativo. No nosso estudo de caso, utilizamos a classe ReservasDAO para todas as operações de acesso ao banco de dados relacionadas à entidade Reserva. Factory Adaptação do pattern Abstract Factory, em que removemos a responsabilidade pela criação de objetos da classe que os utiliza. No nosso estudo de caso, criamos uma Factory para os objetos que realizam o agendamento, permitindo que a mesma interface gráfica fosse utilizada para acesso aos componentes de Java SE e Java EE. A Factory é responsável por decidir qual objeto deve ser instanciado e retornado ao cliente. Business Delegate Pattern que minimiza o acoplamento entre a camada de apresentação e a camada de negócio. A idéia básica é centralizar o acesso à camada de negócio em um objeto. Desta maneira, todos os componentes que precisarem acessar métodos de negócio o farão através deste objeto. Com isso, alterações na camada de negócios implicam em alterações somente no Business Delegate, ao invés de diversas classes espalhadas pela camada de apresentação. Além disso, o Business Delegate permite que características específicas da camada de negócio sejam isoladas do aplicativo cliente. No nosso exemplo, isto se manifestou ao criarmos exceções de aplicativo customizadas, não propagando para a interface gráfica RemoteException, que é um detalhe da tecnologia de componentes de negócio especifica utilizada (EJB). Singleton Pattern para controle do número de instâncias, normalmente utilizado para garantir que somente uma instância de uma classe é criada. Aplicações comuns desse pattern são evitar o uso de recurso excessivos criando mais objetos do que o necessário, ou centralizar o acesso em um único objeto (isso simplifica por exemplo a criação de cache de dados a serem compartilhadas pelo aplicativo). Service Locator Padrão que isola o aplicativo dos detalhes de obtenção de referência para um determinado componente. São criados métodos getXxx() para cada um dos componentes desejados e o Service Locator se encarrega da obtenção da referência, que normalmente ocorre utilizando APIs mais complexas, como JNDI.

Page 75: Java Magazine 41

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, SCIP e SCWCD. Atualmente é Instrutora e Diretora Educacional da Globaldode, criadora e coordenadora de diversos cursos das carreiras Academia do Java e Academia do Web Developer. Menos Bugs com FindBugs Encontrando Defeitos Automaticamente em Código Java Conheça uma ferramenta capaz de analisar seus programas e listar possíveis defeitos na programação. Buscamos sempre formas de melhorar a qualidade e a performance do nosso código, e para isso utilizamos vários tipos de testes e outras técnicas automatizadas. Mas, ainda que sejam usadas técnicas sofisticadas de testes, partes do código podem não ser testadas ou se comportar de maneira indevida em condições atípicas. Isso se torna impossível ( ou extremamente caro) retirar todos os defeitos de um software somente através da realização de testes. A ferramenta que veremos neste artigo, o FindBugs, auxilia na detecção de defeitos/bugs em código Java, através da análise do bytecode. Podemos considerar o FindBugs uma ferramenta complementar aos testes (automatizados e manuais) para a garantia da qualidade em nossas aplicações. Conheça a ferramenta Obtendo e executando O FindBugs pode ser obtido no site findbugs.sf.net. Após o download, descompacte o arquivo findbugs-1.0.0.zip (ou versão mais recente) para uma pasta qualquer. Para executar a ferramenta, basta entrar na subpasta bin e executar o arquivo findbugs.bat no Windows ou findbugs no Linux/Unix. A tela principal é muito simples, contendo inicialmente apenas uma barra de menus. Tipos de defeitos e seus significados O FindBugs classifica os defeitos encontrados por tipos, que são nomeados com abreviações. Veja alguns exemplos de tipos de defeitos:

• BC: Defeitos relacionados com cast ou instanceof como um cast que nunca poderá ocorrer e sempre irá lançar uma exceção, ou um instanceof que sempre retornará true ou false.

• DE: Tratamento incorreto de exceções, como capturar e ignorar uma exceção sem executar ao menos um comando no bloco catch.

• Dm: Indica problemas de performance com código desnecessário ou incorreto. Por exemplo, executar o método substring() informando o valor 0, o que retorna a mesma string; ou chamar um método da classe Java.util.Calendar, informando como mês em valor fora do intervalo de 0 a 11.

Page 76: Java Magazine 41

• EC: Problemas na chamada do método equals(), que podem ser por exemplo a comparação de um array usando o método equals() de um deles, o que não irá comparar o conteúdo dos arrays, mas sim verificar se ambos são a mesma referência (o equivalente ao uso do operador ==).

• ES: Utilização do operador == para comparar duas strings. • INT: Indica problemas na manipulação de valores inteiros como, por exemplo,

o código a seguir, que vai sempre resultar em verdadeiro, pois a operação var % 1 sempre resulta em zero. Nesses casos, fica claro que foi cometido um erro de programação já que o código seria inútil: int var =... if(var % 1 ==0){ ... }

• NP: Possível ocorrência de uma NullPointerException devido à manipulação

de uma referência nula. Este tipo compreende mais de 15 defeitos diferentes, indicando desde possíveis ocorrências em determinados cenários, até trechos de código onde certamente ocorrerá uma NullPointerException.

• OS: Defeitos desse tipo são identificados quando classes que representam streams de dados (java.io.InputStream e java.io.OutputStream) são instâncias, mas o método close() não é invocado ou não há garantias de que sempre será invocado.

• SA: Quando uma variável é atribuída a ela mesma. • SQL: Erros na manipulação d APIs em java.sql como, por exemplo, obter um

campo de um java.sql.ResultSet informando como índice o valor 0. • UwF: Um atributo nunca é inicializado e pode não ser realmente necessário. • UrF: Um atributo nunca é lido e talvez possa ser removido. • UuF: Um atributo nunca recebe um valor ou é lido, e pode ser removido. • SBSC: Ocorrência de concatenação de strings dentro e loops utilizando o

operador +, o que indica um potencial problema de performance já que os objetos String são imutáveis e concatenações seguidas geram vários objetos desnecessariamente. O correto neste caso seria utilizar a classe java.lang.StringBuffer ou, melhor ainda, Java.lang.StringBuilder a partir do Java 5.

Estes são alguns dos tipos de defeitos mais comuns. Mais detalhes sobre os defeitos encontrados pelo FindBugs podem ser vistos em findbugs.sf.net/bugDescriptions.html. Realizando a análise Para compreender melhor as informações fornecidas pelo FindBugs, analisaremos um projeto open source de fácil acesso. Escolhemos o Jakarta Commons Lang, por este projeto não ser muito extenso e não ter dependências com outros projetos. Preparação Para melhor demonstração o FindBugs, vamos obter das distribuições binárias e de código-fonte do Jakarta CommonsmLang (outra opção é baixar apenas os fontes e fazer a compilação com ant dist). O FindBugs sempre efetua a análise a partir de código compilado, presente em arquivos JAR, WAR, EAR ou .class. Mesmo assim, será muito útil indicar a localização do código-fonte, para que o FindBugs seja capaz de exibir o trecho problemático, facilitando a correção. Obtenha os pacotes source e binary de jakarta.apache.org/site/downloads/downloads_commons-lang.cgi e descompacte ambos a mesma pasta. Será gerada a subpasta commons-lang-2.1. Em seguida basta indicar ao FindBugs como localizar os fontes e o JAR para iniciar nossa análise.

Page 77: Java Magazine 41

Nosso projeto Abra o FindBugs e selecione File/New Project. Neste momento, deverá ser apresentada a tela para criação do novo projeto. Clique em Browse e selecione a pasta ou JAR desejado e depois clique em Add. No primeiro grupo de elementos (Archives/Directories) deverão ser indicados os arquivos (JAR, WAR ou EAR) ou diretório contendo os elementos compilados; e no segundo grupo (Source directories) deverão ser especificados os diretórios ou um arquivo ZIP contendo os fontes. No nosso exemplo não será necessário indicar entradas para o classpath, já que não há dependências. Ao fazer análise de um projeto com dependências (que geralmente serão arquivos JAR), você pode selecioná-los através do terceiro grupo de elementos, adicionando itens à lista Classpath entries. Após selecionar o JAR do projeto (commons-lang-2.1.jar) e o diretório dos fontes, a tela deverá ficar similar à Figura 1. Com isso, nosso projeto está configurado e pronto para ser analisado, o que faremos clicando no botão Find Bugs.

Figura 1. Projeto configurado e pronto para ser analisado no FindBugs. Analisando o relatório Após a análise de um projeto, o resultado é um relatório contendo todos os defeitos encontrados. São geradas diversas visualizações para agrupar os defeitos, como “By Class” e “By Package”. Para a nossa análise, recomendo selecionar a visão “By Bug Type”, que nos fornece uma visão agregada por tipo de defeito, conforme a Figura 2. Analisando um defeito O primeiro defeito apresentado é o tipo Dm, ou seja, um método com possíveis problemas de performance. Para obter mais informações sobre o defeito, ele deve ser selecionado assim como a aba inferior “Details”, que detalha o que significa o defeito. A Figura 2 apresenta esse cenário. O defeito apresentado se deve ao uso do construtor da classe java.lang.Boolean, onde o correto seria utilizar o método valueOf() que retorna as constantes Bollean.TRUE ou Bollean.FALSE. Isso economiza memória e processamento, pois não é necessário instanciar um novo objeto. Note que o erro foi localizado na classe org.apache.commons.lang.ArrayUtils, no método add(boolean[], int, boolean), o que é informado pelo FindBugs e também pode ser observado na Figura 2.

Page 78: Java Magazine 41

Para facilitar a investigação do problema e identificar onde ele se localiza, é possível selecionar a aba inferior, Source Code, exibindo o trecho do código-fonte que gerou o defeito selecionado (isso só funciona quando o FindBugs é capaz de localizar o código-fonte). A Figura 3 mostra o código do defeito. Repare na instanciação de Boolean através do construtor, uma melhoria é alterar new Boolean(element) para Boolean.valueOf(element), como explica a dia do FindBugs e como pregam as boas práticas de programação Java. O segundo agrupamento de defeitos é do tipo ES e indica duas strings sendo comparadas através do operador ==, o que na maioria dos casos é um erro, pois o uso desse operador compara apenas referências (o correto seria utilizar o método equals()). A Figura 4 ilustra esse agrupamento de defeitos expandido, exibindo o único defeito do tipo localizado e o trecho do código onde ele se encontra.

Figura 2. Descrição de um defeito localizado.

Page 79: Java Magazine 41

Figura 3. Trecho do código onde o defeito foi encontrado.

Figura 4. Defeito do tipo ES e o trecho onde ocorreu. Configurando os detectores É provável que nem todos os detectores de defeitos do FindBugs sejam úteis em seus projetos. Prevendo isso, o FindBugs permite configurar os detectores a serem executados. Para isso, podemos voltar à tela de projetos, selecionando os menus View/View Project Details e então selecionar Settings/Configure Detectors (veja a Figura 5). Isso permite selecionar os detectores a serem aplicados ou não. São também fornecidos detalhes sobre sua performance. Após selecionar os detectores desejados, basta clicar novamente no botão Find Bugs para refazer a análise do projeto.

Page 80: Java Magazine 41

Figura 5. Tela para configuração e detalhe dos detectores. Salvando os relatórios de defeitos É possível salvar os defeitos obtidos através dos menus File/Save Bugs. Isso permite analisar os defeitos futuramente, imprimir, enviar um relatório para o responsável pelas correções etc. Para abrir um arquivo salvo, basta acessar File/Load Bugs, escolher o arquivo gravado, clicar em Load Bugs e reiniciar a análise. Conclusões A homologação de uma aplicação através da execuçao de testes é essencial, porém numa temos a garantia que nossos testes cobriram 100% do código-fonte, nem que testamos todas as condições possíveis para um trecho de código. Através do uso do FindBugs com ferramenta auxiliar para garantia de qualidade, podemos dormir um pouco mais tranqüilos sabendo que nosso código foi também analisado em busca de padrões que indicam possíveis problemas na programação. LINKS: findbugs.sf.net Projeto FindBugs findbugs.sf.net/bugDescriptions.html Descrição detalhada de todos os defeitos e seus tipos. Tarefas na Hora com Quartz Use o framework Quartz para criar e agendar tarefas no Java Como utilizar passo a passo, a utilizar o framework open source Quartz. Dos conceitos fundamentais a um exemplo completo.

Page 81: Java Magazine 41

Ao desenvolver aplicações corporativas, é comum encontrar requisitos exigindo que determinados processos sejam executados em intervalos regulares (ou em horários específicos), sem intervenção humana. Esse planejamento, denominado agendamento de tarefas ou job scheduling, pode ser alcançado através de comandos do sistema operacional ou utilizando-se aplicativos especializados. O framework open source Quartz oferece um pacote completo de funcionalidades para esse fim, permitindo a criação e a manipulação de tarefas agendadas. Neste artigo explicaremos conceitos essenciais do agendamento de tarefas e detalhes sobre a utilização do Quartz, através de um exemplo completo. Agendamento no Java SE A partir da versão 1.3 o Java passou a oferecer uma opção para agendamento de tarefas, através das classes java.util.Timer e java.util.TimerTask. Essa solução é bastante simples possuindo uma série de limitações, sendo adequada apenas em algumas situações (veja detalhes no artigo “Agendamento de tarefas em Java”). Ao utilizar essa API certas restrições se tornam evidentes:

6. O mecanismo de agendamento é pouco flexível, baseando-se apenas na data inicial e no intervalo de repetição das tarefas;

7. Cada objeto Timer cria uma nova thread em segundo plano, o que não é desejável em aplicações Java EE;

8. Não há a possibilidade de persistir tarefas, permitindo continuar a execução da tarefa posteriormente (ex.: quando o sistema reiniciar).

O Quartz disponibiliza funcionalidades para criação e execução de tarefas em Java, e resolve as limitações impostas pela API Timer/TimerTask. Além disso, oferece bastante flexibilidade e múltiplas opções de uso. O framework Quartz O Quartz permite criar e executar tarefas de muitos tipos e traz implementações para tarefas comuns (como envio de e-mails, execução de EJBs, execução de comandos nativos etc.). O framework baseia-se em três conceitos fundamentais: job (tarefa), trigger (“gatilho”) e scheduler (agendador). Um job representa uma tarefa que se deseja executar, e é normalmente descrito através da interface org.quartz.Job. Essa interface inclui apenas um método encapsulando a lógica da tarefa: public void execute (JobExecutionContext context) throws JobExecutionException; Um trigger define os momentos em que a tarefa deve ser executada e dispara sempre que chega o instante de execução. O Quartz disponibiliza diversos tipos de triggers, tratando desde as situações mais simples, com a classe org.quartz.SimpleTrigger; até situações complexas, incluindo intervalos de tempo e dias específicos, usando org.quartz.CronTrigger (veja mais no quadro “Entendendo a classe CronTrigger”). O scheduler possui um registro de todos os triggers e tarefas programadas e coordenada a sua execução. Não existe nenhum vínculo direto entre tarefas e triggers: ambos podem ser manipulados separadamente. A separação entre a tarefa e o seu agendamento é um dos pontos fortes do framework Quartz. Isso permite que uma mesma tarefa seja executada em situações completamente diferentes, e facilita o reaproveitamento da lógica das tarefas e dos triggers. Primeiros passos Para agendar uma tarefa usando o Quartz, o primeiro passo consiste (naturalmente) em criar a tarefa. O código abaixo cria uma tarefa que imprime a hora atual: public class Tarefa implements Job { public void execute (JobExecutionContext context)

Page 82: Java Magazine 41

throws JobExecutionException { System.out.println (“Disparou:” + new Date()); } } O segundo passo é criar o agendador. Para isso utilizamos a fábrica org.quartz.impl.StdSchedulerFactory, que cria uma instância do agendador a partir das propriedades descritas no arquivo quartz.properties (este arquivo será detalhado posteriormente): Scheduler agendador = StdSchedulerFactory.getDefaultScheduler(); A seguir deve-se instanciar o detalhe da tarefa (um objeto org.quartz.JobDetail). O Quartz utiliza esse objetivo para armazenar os atributos da tarefa que será executada (incluindo eventuais parâmetros). Aqui utilizamos o JobDetail simplesmente para nomear a tarefa e atribuí-la a um grupo. O uso de nomes e grupos permite identificar unicamente a tarefa (ou o trigger) no agendador e possibilita a criação de categorias de tarefas e triggers. Como não existe necessidade de categorias a tarefa, usamos um grupo default: JobDetail detalhe = new JobDetail(“Tarefa exemplo”, Scheduler.DEFAULT_GROUP,Tarefa.class); Uma vez criado o agendador, devemos criar um trigger. Neste exemplo e construímos um trigger com nome “Trigger exemplo” que dispara a cada 10 segundos e inicia imediatamente. Para simplificar a criação e a configuração do trigger usamos a classe org.quartz.helpers.TriggerUtils, que oferece diversos métodos utilitários com esse propósito: Trigger trigger = TriggerUtils.makeSecondlyTrigger(10); trigger.setName(“Trigger exemplo”); trigger.setStartTime(new Date()); Agora é só agenda a tarefa e iniciar o agendador: agendador.scheduleJob(detalhe,trigger); agendador.start(); Até aqui vimos como manipular o agendamento via programação, codificando a tarefa e o trigger. O Quartz também permite que as tarefas sejam configuradas de forma declarativa em um arquivo XML. Utilizando essa estratégia, é possível modificar o agendamento das tarefas sem precisar alterar o código da aplicação. Na próxima seção veremos como configurar o framework tanto na forma programática como na declarativa, e como utilizá-lo em container web. Apesar de o Quartz poder ser usado indistintamente em ambientes Java SE e EE, sem dúvida o uso no ambiente corporativo é mais comum e merece maior atenção. A configuração do Java SE não apresenta diferenças substanciais e poderá ser feita pelo próprio leitor a partir dos exemplos apresentados aqui. Preparação inicial Antes de iniciar o uso do Quartz, é necessário configurar o ambiente de desenvolvimento. Para este artigo usamos o JBoss 4.0.4 e o Eclipse 3.2. Como a geração dos pacotes e o deployment da aplicação são feitos através do Ant, qualquer IDE que ofereça suporte ao Ant pode ser utilizada. O download do Quartz pode ser feito em opensymphony.com/quartz/download.action. Ao escrever este artigo, a versão mais recente do framework era 1.5.2, disponível no arquivo quartz-1.5.2.zip.

Page 83: Java Magazine 41

Este ZIP inclui todas as bibliotecas necessárias, além dos javadocs. Após descompactar o arquivo, é necessário referenciar a biblioteca do Quartz (quartz-all-1.5.2.jar) no seu projeto. Um exemplo completo: Quartz no container web Em uma aplicação web, as configurações do Quartz normalmente são carregadas através de um servlet, que é configurado para iniciar junto com a aplicação. Apesar de essa ser a opção mais comum, também é possível usar o utilitário em conjunto com outros frameworks, como o Struts (para mais detalhes veja o quadro “Integrando Quartz e Struts”). O Quartz oferece uma vasta gama de tarefas, aplicáveis às mais diversas situações. Para facilitar o entendimento do exemplo vamos separar as tarefas do Quartz em duas categorias: tarefas comuns e tarefas EJB. Em uma tarefa comum toda lógica que se deseja realizar é codificado em uma classe que implementa a interface org.quartz.Job, conforme mostrado nos exemplos anteriores. Já em uma tarefa EJB, a lógica reside em um EJB, que é executado a partir de uma classe especial do Quartz: org.quartz.jobs.ee.ejb.EJBInvokerJob. Nas próximas seções, descreveremos mais três possibilidades para configuração e utilização do Quartz através se servlets, cobrindo todas as opções possíveis. São elas:

• Configuração declarativa e tarefa comum; • Configuração declarativa e tarefa EJB; • Configuração programática e tarefa EJB.

O quadro “Entendendo a aplicação de exemplo” detalha a estrutura dessa aplicação (que demonstra as três configurações) e o procedimento de build adotado para geração. Todos os arquivos da aplicação de exemplo estão disponíveis para download no site da Java Magazine. Configurando uma tarefa simples ou q Veja na Listagem 1 a classe ConfiguradorServlet, que é responsável pela configuração do Quartz. Como sabemos, o método init() de um servlet é disparado automaticamente pelo container web sempre que o servlet é colocado em serviço. Usaremos este método para configurar e iniciar o Quartz.

Ø A configuração do agendamento via servlet também pode ser feita através da classe org.quartz.ee.servlet.QuartzInitializerServlet. Essa classe cria a fábrica de agendadores (uma instância de org.quartz.impl.StdSchedulerFactory) e a armazena no contexto do servlet, permitindo sua recuperação posterior.

Como especificado no web.xml (Listagem 2), o servlet é instanciado quando a aplicação é carregada. O descritor web também informa o valor do parâmetro tipoConfig, passado ao servlet. Analisando a implementação do método int(), percebemos que ele apenas captura o tipo de configuração desejado (o parâmetro tipoConfig é definido na constante Configurador.TIPO_CONFIG) e delega a configuração à classe Configurador (Listagem 3), que é um singleton (classe com instância única): String tipo = getInitParameter(Configurador.TIPO_CONFIG); Configurador.getInstance().configurar(tipo); O método configurar() da classe Configurador é quem realmente realiza todo o “trabalho pesado”, iniciando o Quartz de acordo com o tipo de configuração escolhido pelo desenvolvedor. São suportados três tipos de configuração: ejb-manual (configuração declarativa da tarefa que executa em EJB) e comum-arquivo (configuração declarativa de uma tarefa comum). O método configurar() analisa o tipo de configuração informando no parâmetro tipo e dispara o método adequado. Configurações declarativas são instanciadas através do método configArquivo(), que efetua o parse do arquivo apropriado (tarefa-ejb.xml ou tarefa-comum.xml)

Page 84: Java Magazine 41

e inicia o agendador. A configuração manual é feita através do método configEJBManual(), que configura a tarefa, o trigger e o agendador “manualmente”. Voltando ao código do web.xml (Listagem 2), é possível perceber que o parâmetro tipoConfig foi definido como “comum-arquivo”. Isso implica que o arquivo tarefa-comum.xml será utilizado para configurar o Quartz e iniciá-lo: <init-param> <param-name>tipoConfig</param-name> <param-value>comum-arquivo</param-value> </init-param> A Listagem 4 mostra um trecho do arquivo tarefa-comum.xml. Uma tarefa é definida através do tag <job>, que possui dois tags “filhos”: <job-detail> e <trigger>. Em <job-detail> é especificado o nome (“EnviarLog”), o grupo (“DEFAULT”), a descrição (“Envio de log por email”) e a classe que implementa a tarefa: <job-detail> <name>EnviarLog</name> <group>DEFAULT</group> <description>Envio de log por e-mail</description> <job-class>BR.com.jm.quartz.util.ExecutorAnt</job-class> <job-data-map> … </job-data-map> </job-detail> Como definido no tag <job-class>, a classe BR.com.jm.quartz.util.ExecutorAnt será instanciada e executada quando for necessário iniciar a tarefa. Esta classe, mostrada na Listagem 5, permite a execução de um script Ant diretamente de uma aplicação Java, aceitando a atribuição de valores a propriedades do script. A classe br.com.jm.quartz.util.ExecutorAnt é uma classe utilitária, que pode ser utilizada em qualquer aplicação. Não existe nenhum vinculo entre ela e o Quartz. O tag <job-data-map> possui um conjunto de tags <entry>. Estas tags informam os parâmetros que serão passados à classe ExecutorAnt. Os três parâmetros (arquivo, alvo e destinatario) são definidos através de tags <key> e <value>. Veja um exemplo: <job-data-map> <entry> <key>arquivo</key> <value>/WEB-INF/classes/envio-log.xml</value> </entry> <entry> <key>alvo</key> <value>enviar-log</value> </entry> <entry> <key>destinatario</key> <value>[email protected]</value> </entry> </job-data-map>

Page 85: Java Magazine 41

O parâmetro arquivo informa o caminho do arquivo de script, enquanto o parâmetro destinatário é utilizado para definir a propriedade correspondente do script. Voltando à Listagem 5, vemos que o método executarAlvo() executa o alvo (target) Ant definido na propriedade alvo. Assim, o alvo enviar-log do script /WEB-INF/classes/envio-log.xml será executado com o parâmetro destinatario preenchido com [email protected]. A Listagem 6 é um exemplo de um script Ant (envio-log.xml), que compacta arquivos de log encontrados em um diretório específico e os envia por e-mail para o destinatário indicado na propriedade destinatario. Para que a tarefa seja executada é necessário vinculá-la a um trigger. Na configuração declarativa, o trigger é descrito através do tag <trigger>. Para essa tarefa foi associado um trigger simples, por meio do tag <simple>. Esse tag especifica que o Quartz deve criar um objeto da classe org.quartz.SimpleTrigger. O trigger foi nomeado “TriggerEnviarLog” e executa a cada minuto (60 mil microssegundos): <trigger> <simple> <name>TriggerEnviarLog</name> <group>DEFAULT</group> <job-name>EnviarLog</job-name> <job-group>DEFAULT</job-group> <repeat-count>-1</repeat-count> <repeat-interval>60000</repeat-interval> </simple> </trigger> O valor -1 em <repeat-cont> indica que o trigger continuará sendo disparado até a aplicação ser finalizada. Listagem 1. Servlet para configuração do Quartz. package br.com.jm.quartz.servlet; import javax.servlet.*; import br.com.jm.quartz.util.Configurador; public class ConfiguradorServlet extends HttpServlet {

public void init() throws ServletException { String tipo = getInitParameter(Configurador.TIPO_CONFIG); Configurador.getInstance().configurar(tipo);

} } Listagem 2. web.xml com configuração declarativa usando um Servlet. <!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app> <servlet>

<servlet-name>config</servlet-name> <servlet-class>br.com.jm.quartz.servlet.

ConfiguradorServlet</servlet-class> <init-param>

Page 86: Java Magazine 41

<param-name>tipoConfig</param-name> <param-value>comum-arquivo</param-value>

</init-param> <load-on-startup>1</load-on-startup>

</servlet> </web-app> Listagem 4. Configuração declarativa da tarefa que executa Scripts Ant. <?xml version="1.0" encoding="UTF-8"?> <quartz xmlns="http://www.opensymphony.com/quartz/JobSche- dulingData" xmlns:xsi="http;//www.w3.org/2001/XMLSchema- instance" overwrite-existing-jobs="true">

<job> <job-detail>

<name>EnviarLog</name> <group>DEFAULT</group> <description>Envio de log por e-mail</description> <job-class>br.com.jm.quartz.util.

ExecutorAnt</job-class> <job-data-map>

<entry> <key>arquivo</key> <value>/WEB-INF/classes/envio-log.xml</value>

</entry> <entry>

<key>alvo</key> <value>enviar-log</value>

</entry> <entry>

<key>destinatario</key> <value>[email protected]</value>

</entry> </job-data-map>

</job-detail> <trigger>

<simple> <name>TriggerEnviarLog</name> <group>DEFAULT</group> <job-name>EnviarLog</job-name> <job-group>DEFAULT</job-group> <repeat-count>-1</repeat-count> <repeat-interval>60000</repeat-interval>

</simple> </trigger>

</job> </quartz> Listagem 5. Tarefa para execução de scripts Ant. package br.com.jm.quartz.util; import java.io.*;

Page 87: Java Magazine 41

import java.util.*; import org.quartz.*; import org.apache.tools.ant.*; public class ExecutorAnt implements Job {

private Project projeto = null; public void execute(JobExecutionContext context)

throws JobExecutionException {

JobDataMap mapa=context.getJobDetail().getJobDataMap(); String arquivo = mapa.getString("arquivo"); String alvo = mapa.getString("alvo"); configurarProjeto(arquivo, mapa); executarAlvo(alvo);

} private void configurarProjeto(String arquivo,

JobDataMap propriedades) {

if (projeto == null) { projeto = new Project(); projeto.fireBuildStarted(); projeto.init(); String file =

getClass().getResource(arquivo).getFile(); ProjectHelper projHelper = new ProjectHelperImpl (); projHelper.parse(projeto, new File(file)); PrintStream out = System.out; BuildLogger logger = new DefaultLogger(); logger.setMessageOutputLevel(Project.MSG_DEBUG); logger.setOutputPrintStream(out); logger.seVErrorPrintStream(out); projeto.addBuildListener(logger); configurarPropriedades(propriedades);

} } private void configurarPropriedades(JobDataMap

propriedades) { if (propriedades != null) {

lterator it = propriedades.entrySet().iterator(); while (it.hasNext()) {

Map.Entry entry = (Map.Entry) it.next(); Strtng chave = entry.getKey() + ""; String valor = entry.getValue() + ""; projeto.setProperty(chave, valor);

} }

} private void executarAlvo(String alvo) {

if (alvo == null) { alvo = projeto.getDefaultTarget();

Page 88: Java Magazine 41

} projeto.executeTarget(alvo);

} } Listagem 6. Script Ant para envio de logs por e-mail. <?xml version="1.0" encoding-"UTF-8"?> <project basedir="." default="enviar-log"

name="Tarefa exemplo"> <property name="log.dir"

location="/java/jboss-4.0.4.GA/server/default/log" /> <target name="compactar">

<echo>Compactando arquivos de log</echo> <zip destfile-"logs.zip">

<fileset dir="${log.dir}" includes="**/*.log" /> </zip>

</target>

<target name="enviar-log" depends-"compactar"> <echo>Enviando e-mail para ${destinatario}</echo> <mail mailhost="smtp.uol.com.br"

user="ad-rocha" password-"xxxx" files="logs.zip" subject="Enyio de log">

<from address="[email protected]" /> <to address="${destinatario}" /> <message>Arquivo compactado</message>

</mail> <delete file="logs.zip" /> <echo>OK</echo>

</target> </project> Configurando uma tarefa EJB de forma declarativa O uso de um EJB a partir do Quartz não é muito diferente do que vimos até aqui. No modelo declarativo, a única diferença é o arquivo XML utilizado para configuração da tarefa. A Listagem 7 exibe a configuração da tarefa que dispara um EJB para imprimir a hora atual no console (tarefa-ejb.xml). As classes e interfaces do EJB podem ser vistas nas Listagens 8, 9 e 10. O Quartz já incorpora uma classe para execução de EJBs: org.quartz.jobs.ee.ejb.EJBInvokerJob, que deve ser indicada no tag <job-class>. Essa classe exige o preenchimento de alguns parâmetros especiais, que são descritos em <java-data-map>:

• java.naming.factory.initial indica a fábrica que será usada para montar o contexto;

• java.naming.provider.url indica o provedor de serviço que será utilizado; • ejb é usado para descrever a localização JNDI da interface home do EJB e • method define o método do EJB que será executado.

Com base nesses parâmetros o Quartz é capaz de localizar o EJB e dispará-lo no momento adequado.

Page 89: Java Magazine 41

Listagem 7. Configuração declarativa da tarefa que executa um EJB. <?xml version="1.0" encoding="UTF-8"?> <quartz xmlns=" http://www.opensymphony.com/quartz/JobSche- dulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema- instance" overwrite-existing-jobs="true">

<job> <job-detail>

<name>MostrarDataAtual</name> <group>DEFAULT</group> <description>Invoca um EJB que mostra a data

atual</description> <job-class>org.quartz.jobs.ee.ejb.EJBInvokerJob

</job-class> <job-data-map>

<entry> <key>java.naming.factory.initial</key> <value>

org.jnp.interfaces.NamingContextFactory </value>

</entry> <entry>

<key>java.naming.provider.url</key> <value>jnp://localhost:1099</value>

</entry> <entry>

<key>ejb</key> <value>ejb/quartz</value>

</entry> <entry>

<key>method</key> <value>mostrarData</value>

</entry> </job-data-map>

</job-detail> <trigger>

<cron> <name>TriggerDataAtual</name> <group>DEFAULT</group> <job-name>MostrarDataAtual</job-name> <job-group>DEFAULT</job-group> <cron-expression>0 0/1 * * * ?

</cron-expression> </cron>

</trigger> </job> </quartz> Listagem 8. EJB para imprimir a hora atual. package br.com.jm.quartz.ejb; import javax.ejb.*;

Page 90: Java Magazine 41

import java.util.Date; public class OuartzBC implements SessionBean {

public void setSessionContext(SessionContext context) throws EJBException {} public void ejbCreate() { } public void ejbRemove() throws EJBException { } public void ejbActivate() throws EJBException { } public void ejbPassivate() throws EJBException { } public void mostrarData() {System.out.println("Data atual: "+ new Date());}

} Listagem 9. lnterface remota para o EJB. package br.com.jm.quartz.ejb; import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface QuartzInterface extends EJBObject {

public void mostrarData() throws RemoteException; } Listagem10. lnterface home para o EJB. package br.com.jm.quartz.ejb; import javax.ejb.*; import java.rmi.RemoteException; public interface QuartzHome extends EJBHome (

public OuartzInterface create() throws CreateException, RemoteException; } Configurando uma tarefa EJB de forma programática Também é possível configurar a execução da tarefa que dispara o EJB via programação, como mostra o método configEJBManual() da classe Configurador (Listagem 3). Neste caso todas as propriedades da tarefa e dados do trigger são preenchidos “à mão”, sem necessidade de arquivos XML. Na configuração manual os parâmetros do EJB são representados através de quatro constantes da classe EJBInvokerJob: INITIAL_CONTEXT_FACTORY, PROVIDER_URL, EJB_JNDI_NAME_KEY e EJB_METHOD_KEY. O comportamento de duas alternativas apresentadas (declarativa e programática) é idêntico. Listagem 3. Classe utilitária para configuração do Quartz. package br.com.jm.quartz.util; import org.quartz.*; public class Configurador {

private static Configurador configurador = null; public static final String TIPO_CONFIG = "tipoConfig";

Page 91: Java Magazine 41

public static Configurador getInstance() { if (configurador == null) {

configurador = new Configurador(); } return configurador;

}

private Configurador() { } public void configurar(String tipo) {

if ("ejb-manual".equals(tipo)) { configEJBManual();

} else if ("ejb-arquivo".equals(tipo)) {

configEJBArquivo(); } else if ("comum-arquivo".equals(tipo)) {

configComumArquivo(); } else {

throw new IllegalArgumentException( "Tipo de tarefa nao suportada: " + tipo);

} }

private void configEJBArquivo() {

configArquivo("/tarefa-ejb.xml"); } private void configComumArquivo() {

configArquivo("/tarefa-comum.xml"); } private void configArquivo(String arquivo) {

try { String file = getClass(

).getResource(arquivo).getFile(); Scheduler scheduler =

StdSchedulerFactory.getDefaultScheduler(); JobSchedulingDataProcessor xmlProcessor =

new JobSchedulingDataProcessor(); xmlProcessor.processFileAndScheduleJobs(

file, scheduler, false); scheduler.start();

} catch (Exception e) {

e.printStackTrace(); }

} private void configEJBManual() {

try { JobDetail detail = new JobDetail(

Page 92: Java Magazine 41

"MostrarDataAtual", Scheduler.DEFAULT_GROUP,

EJBlnvokerJob.class);

detail.getJobDataMap( ).put( EJBInvokerJob.INITIAL_CONTEXT_FACTORY,

"org.jnp.interfaces.NamingContextFactory"); detail.getJobDataMap( ).put(

EJBInvokerJob.PROVIDER_URL, "jnp://localhost: 1099"); detail.getJobDataMap( ).put(

EJBInvokerJob.EJB_JNDI_NAME_KEY, "ejb/quartz"); detail.getJobDataMap( ).put(

EJBInvokerJob.EJB_METHOD_KEY,"mostrarData"); SimpleTrigger trigger = new SimpleTrigger(

"TriggerDataAtual", Scheduler.DEFAULT_GROUP); trigger.setRepeatCount(

SimpleTrigger.REPEAT_INDEFINITELY); trigger.setRepeatInterval(60000L); Scheduler scheduler =

StdSchedulerFactory.getDefaultScheduler(); scheduler.scheduleJob(detail, trigger); scheduler.start();

} catch (SchedulerException e) {e.printStackTrace();}

} } Configurando o Quartz Além dos parâmetros relativos ao agendador, às tarefas e ao triggers, o Quartz contempla propriedades que podem ser alteradas para adaptar o framework a situações específicas. Essas propriedades são definidas no arquivo quartz.properties (dentro do JAR do framework), que é utilizado pelo Quartz durante a criação do agendador. A distribuição já incorpora um arquivo com as opções padrão (o que será suficiente para a maioria das aplicações), mas o desenvolvedor pode criar seu próprio arquivo e alterar as configurações de acordo com a sua necessidade. Neste caso, para que as alterações possam ser aplicadas é necessário colocar o novo arquivo no classpath da aplicação. O arquivo contido na distribuição do Quartz é bastante simples, e comtempla configurações do agendador, threads, RMI e tarefas. O arquivo completo é mostrado na Listagem 11. Abordaremos apenas duas das principais: número de threads e persistência de tarefas. Mais detalhes sobre as demais propriedades podem ser encontrados na documentação do Quartz. Listagem 11. Arquivo quartz.properties padrão. org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.rmi.export = fatse org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.wrapJobExecutionInUserTransaction = false org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

Page 93: Java Magazine 41

org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore Número de threads O framework utiliza um pool de threads para executar as tarefas agendadas. O tamanho do pool determina a quantidade de tarefas que podem ser executadas simultaneamente, e constitui um parâmetro da configuração importante. Caso não exista alguma thread disponível no pool no momento agendado para execução, a tarefa terá que aguardar. O padrão definido no Quartz é de dez threads, mas esse número pode ser alterado através da chave org.quartz.threadPool.threadCount. Persistência O recurso de persistência de tarefas do Quartz permite que o estado de uma tarefa seja salvo e posteriormente recuperado; assim as tarefas não serão canceladas se houver um reinício ou uma falha da aplicação. Por default, o estado das tarefas é mantido em memória através da classe org.quartz.simpl.RAMJobStore, sendo perdido quando a aplicação é reiniciada. É possível alterar a classe responsável pela persistência e assim armazenar os dados em um banco de dados. Essa configuração deve utilizar algumas das classes de persistência baseada em JDBC oferecidas pelo Quartz: org.quartz.simpl.JDBCJobStoreTX ou org.quartz.simpl.JDBCJobStoreCMP. Outros recursos O Quartz oferece ainda mais dois recursos que merecem atenção: listeners e plug-ins. Listeners O Quartz permite o registro de listeners para tarefas, triggers e agendadores. Objetos listeners são disparados sempre que um evento importante ocorre como, por exemplo, quando uma trigger dispara (ou não dispara quando deveria). Através de um listener é possível forçar a re-execuçao de uma tarefa ou efetuar alguma manobra de contingência, tratando o erro de forma adequada. Os listeners estão descritos nas interfaces org.quartz.JobListener, org.quartz.TriggerListener e org.quartz.SchedulerListener. Plug-ins O framework pode ainda ser estendido pela criação de plug-ins que tornam possível codificar funcionalidades não existentes no Quartz. Para desenvolver um novo plug-in, basta criar uma classe que implemente a interface org.quartz.spi.SchedulerPlugin e codificar os três métodos necessários. O plug-in também deve ser configurado no quartz.properties. Conclusões O Quartz é um utilitário bastante poderoso e simples de usar. Sua arquitetura permite a utilização tanto em aplicações SE quanto EE, sem diferenças substanciais de configuração. O Quartz já incorpora classes que executam tarefas corriqueiras e possui um mecanismo de agendamento, baseado em triggers que é bastante robusto. Da próxima vez que você necessitar agendar tarefas em sua aplicação considere usar o Quartz; ele oferece vantagens que certamente pouparão muitas horas de desenvolvimento. LINKS opensymphony.com/quartz Página do Quartz opensymphony.com/quartz/api/org/quartz/CronTrigger.html Documentação da classe CronTrigger

Page 94: Java Magazine 41

opensymphony.com/quartz/api/org/quartz/CronExpression.html Documentaçao da classe CronExpression nighttale.net/OpenSource/QuartzXML-RPCplugin.html Plug-in do Quartz para executar web services Entendendo a classe CronTrigger A classe org.quartz.CronTrigger é utilizada para disparar tarefas em intervalos definidos através de expressões cron*, que são processadas e avaliadas pela classe org.quartz.CronExpression. Uma expressão cron é algo como “0 0 12**?”, que neste caso significa “dispare todos os dias, às 12:00h PM”. Veja na Tabela Q1 alguns exemplos de expressões cron (retirados da documentação do Quartz). Uma expressão é definida através de sete campos que representam intervalos de tempo, como mostrado na Tabela Q2. Em cada campo da expressão cron pode ser utilizada uma gama de valores, ou caracteres coringa, que possuem significado especial, como mostrado (resumidamente) na Tabela Q3.

Tabela Q1. Exemplos de expressões cron

Tabela Q2. Campos de expressões cron

Tabela Q3. Caracteres coringas do cron Entendendo a aplicação de exemplo

Page 95: Java Magazine 41

A Figura Q1 exibe a estrutura do projeto de exemplo no Eclipse. Além da biblioteca do Quartz (quartz-all-1.5.2.jar) o exemplo utiliza alguma bibliotecas (ant.jar,ant.auncher.jar e ant-javamail.jar) para permitir a execução dinâmica do Ant. Esses JARs estão disponíveis na pasta plug-ins do Eclipse. Além do Ant, é usado o Struts e, por isso, a biblioteca struts.jar deve estar disponível par ao projeto. Web.xml e o build Os arquivos web-ejbcarquivd.xml, web-ejb-manua.xml, web-servlet-arquivo.xml e web-struts-arquivo.xml são configurações do descritor web (web.xml) para cada uma das possíveis configurações descritas no exemplo. Durante o build esses arquivos são renomeados para "web.xml" pelo alvo que cria cada aplicação de exemplo: . exemplo_servletarquivo: cria aplicação Web utilizando a configuração declarativa do Quartz; . exemplo_ejb_arquivo: cria a aplicação Web+EJB utilizando a configuração declarativa do Quartz; . exemplo_ejb_manual: cria a aplicação Web+EJB utizando a configuração programática do Quarrz; . exemplo_struts_arquivo: cria aplicação Web+Struts utilizando a configuração declarativa do Quartz.

Figura Q1. Estrutura do projeto. Integrando Quartz e Struts A integração entre o Quartz e o Struts é bastante simples. Supondo que o Struts já esteja configurado na aplicação, dois passos são necessários para habilitar o Quartz: criar o plug-in de inicializaçao e configurar o struts-config.xml.

Page 96: Java Magazine 41

A Listagem Q1 mostra a classe ConfiguradorStruts, que inicia o Quartz. A classe é um plug-in do Struts (implementação da interface org.apache.struts.action.Plugin). Esse plug-in implementa o método init(), responsável pela configuração do Quartz. A Listagem Q2 exibe a configuração do struts-config.xml para comtemplar o novo plug-in. A sintaxe é simples, bastando adicionar o tag <plug-in> e referenciar a classe que o implementa. As prioridades startOnLoad e startupDelay também devem ser setadas. Plug-ins do Struts funcionam de forma semelhante a servlets, mas os parâmetros passados ao plug-in são codificados como atributos da classe. Note que a prioridade tipoConfig, passada como parâmetro, é mapeada em um atributo de mesmo nome na classe ConfiguradorStruts. Listagem Q1. Plug-in para configuração do Quartz. package br.com.jm.quartz.struts; import javax.servlet:ServletException; import org.apache.struts.action.*; import br.com.jm.quartz.util.Configurador; public class ConfiguradorStruts implements PlugIn {

private String tipoConfig;

// ... setters e getters omitidos

public void init(ActionServlet servlet. ModuleConfig config) throws ServletException { Configurador.getInstance(

).configurar(getTipoConfig()); } public void destroy() {}

} Listagem Q2. Configuração do struts-config.xml para suportar o plug-in de configuração do Quartz. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts-config PUBLIC"-//Apache Software Founda tion//DTD Struts Configuration 1.1//EN" ''http://jakarta. apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config>

<plug-in className= "br.com.jm.quartz.struts.ConfiguradorStruts">

<set-property property= "startOnLoad" value="true" />

<set-property property= "startupOelay" value="3000" />

<set-property property= "tipoConfig" value="comum-arquivo" />

</plug-in> </struts-config>

Page 97: Java Magazine 41

Componentes Avançados do Swing Parte 1: Explorando e Customizando o JCom boBox/JList Aprenda a customizar os componentes de exibição e seleção de conjuntos de valores do Swing para incrementar o visual das suas aplicações Nesta série em duas partes ,mostramos como incrementar a aparência de componentes visual do Swing para incluir imagens e outros recursos. Vemos conceitos fundamentais referentes à arquitetura de componentes visuais, e exemplos de como customizar dois dos componentes mais sofisticados do Swuing:JComboBox e JTable. Nesta parte, enfocamos o JComboBox. Os exemplos Os exemplos deste artigo são partes de uma aplicação hipotética de monitoração de redes. A aplicação deve indicar o sistema operacional (SO) de cada servidor e estação de trabalho, exibindo logotipos referentes ao SO em vez de apenas o seu nome, além de outras customizações visuais utilizadas em aplicações profissionais. Vamos partir de um conjunto de classes de domínio que representam as informações e conceitos utilizados. A Listagem 1 apresenta estas classes, Host e SistemaOperacional, ambas no pacote de dados. Para simplificar e focar melhor na parte visual, a classe SistemaOperacional será apenas uma enumeração (Java 5) que fornece os valores pré-fixados Linux, Windows e MACOSX, e o conjunto de Hosts será fornecido em uma coleção Java, em vez de lido de um arquivo ou banco de dados. Vamos construir duas telas simples para a visualização destas informações, cada qual em seu próprio exemplo, que pode ser compilado e executado em separado. Veremos adiante e na segunda parte que ambas as telas irão exigir a customização de componentes Swing, mais especificamente JComboBox e JTable, e estas customizações serão tornadas reusáveis pela criação de novas classes Swing. Uma tela exibe uma tabela com todos os Hosts e suas propriedades, mostrando o sistema operacional como ícone. A outra exibe apenas um combobox para escolha do sistema operacional, mostrando tanto o ícone quanto o nome do sistema. Iniciaremos pela tela mais simples, a segunda. Listagem 1 Objetos de domínio da aplicação de exemplo.

Page 98: Java Magazine 41

SistemaOperacional.java package dados: public enum SistemaOperacional { LINUX, WINDOWS, MACOSX } HOST.java package dados: public class Host { private String nome ; private String ip ; private SistemaOperacional so ; private in memoriaRAM . // … construtores, metodo get/set omitidos } A tela do Sistema Operacionais/ JCombobox

Page 99: Java Magazine 41

Para exibir o combobox de sistema operacionais, primeiro criamos uma classe de modelo que estende DefaultComboBoxModel e é inicializada com todos os valores possíveis de enumeração SistemaOperacional. A Listagem 2 apresenta esta classe, assim como a janela que faz uso dela (JanelaPrincipal). Todas as classes desta tela estão no pacote comboBox. Entre elas, a classe Principal apenas instancia uma JanelaPrincipal e a torna visível. “A classe DefaultComboBoxModel encapsula um java.util.Vector. Dessa forma, qualquer tipo de objeto pode ser adicionado como elemento de JcomboBox (o mesmo vale para um JList). Cada item individual é “formatado” pelo chamado ao seu método toString()” O resultado é apresentado na Figura 1. Não é ainda muito elegante, mas demonstra o que acontece se não for feita nenhuma customização visual do componente. Para fazer esta customização, é necessário conhecer mais sobre a arquitetura do JComboBox,o que faremos a seguir: Figura 1.Primeira versão do exemplo de Sistema Operacionais / JComboBox. Listagem 2. classes para exemplo de JComboBox. SistemaOperacionalComboBoxModel.java package combobox; import dados.SistemaOperacional ; public class SistemaOperacional ComboBoxModel

Page 100: Java Magazine 41

extends DefaultComboBoxModel { public SistemOperacional ComboBoxModel ( ) { addElement(SistemaOperacional.LINUX); addElement(SistemaOperacional.WINDOWS); addElement(SistemaOperacional.MACOSX); } } JanelaPrincipal.java package combobox; import java.awt.Border Layout; import javax.swing.*; public class JanelaPrincipal extends javax.swing.JFrame { private JComboBox comboSO; private JLabel labelSO; public JanelaPrincipal ( ) { labelSO = new JLabel ( );

Page 101: Java Magazine 41

comboSO = JComboBox ( ) ; setDefaultOperation( WindowConstants.DISPOSE_ON_CLOSE) ; setTitle (“Java Magazine – Demo de JComboBox”); labelSo.stText(“Sistema Operacional:”) ; getContentPane( ).add(labelSO, BorderLayout.WEST); comboSO.setModel ( new SistemaOperacionalComboBoxModel()); getContentPane( ).add(comboSO, BorderLayout.CENTER); pack(); } } Principal.java package combobox ; public class Principal public static void main(String[] s ) { (new JanelaPrincipal ( ) ) .setVisilible (true) } }

Page 102: Java Magazine 41

Arquitetura do JComboBox A arquitetura do componente JComboBox é apresentada na Figura 2. Observe que o JComboBox reusa muito da arquitetura e da funcionalidade do componente JList. Por exemplo, o modelo de um JComboBox (no diagrama, DefaultComboBoxModel) deve implementar a interface ComboBox. Esta interface, por sua vez estende a interface ListBoxModel. A diferença entre os componentes JList e JComboBox é que no JComboBox o próprio ComboBox gerencia o item que este selecionado. Já no componente JList é possível que não haja nenhum item selecionado, ou que haja uma seleção múltipla;por isso a seleção é gerenciada por uma classe de modelo separada(ListSelectionModel) O JComboBox(assim como vários outros componentes do Swing)utiliza um tipo de classe de apoio conhecida como cell renderer. Sua função é desenhar na tela um objeto de dados. Modificar a aparência do nosso combobox para exibir os ícones dos sistemas operacionais exige a criação de uma nova classe que implementa a interface ListCellRenderer. O observe que a implementação padrão desta interface, o DefaultListCellRenderer, é uma especialização de um JLabel.Nada obriga a cellrenderer a herdar de um Jlabel, mas como um Jlabel é capaz de exibir textos, quebrar linhas, mudar cores e fontes e também exibir imagens, ele será adequado para a maioria das situações. Entretanto um,DefaultListCellRender (assim como outros cell renderes do Swing)não é um JLabel comum. Uma única instância dele é usada como um “molde” para desenhar todos os itens do JComboBox,d e modo a evitar que o componente se torna pesado pelo excesso de objetos agregados. Um JLabel comum não permite este uso, de modo que o DefaultListCellRenderer é obrigado a redefinir métodos herdados de java.awt.Component e javax.swing.JComponent, para garantir sua performance e evitar “sujeiras” ou “tremidas” na tela. Por isso é mais facilcriar novos XxxListCellRenderers estendo o próprio DefaultListCellRenderer,do que estender diretamente um JLabel ou mesmo um JComponent². Nos pouco casos em que JLabel não forneça funcionalidade suficiente (por exemplo, a exibição de texto formado com diferentes estilos de texto), deve ser criado do zero uma classe que implementa a interface ListCellRendere. Tal classe pode ser baseada em outro componente do Swing ou realizar todo o desenho “no braço” usando os recursos API Java 2D. Figura 2. Arquitetura simplificada componentes JList e JComboBox.Entre as classes omitidas,estão as classes de visão de ambos os componentes.Em azul temos as classes e interfaces de componente JList e em amarelo as classes e interfaces do componente

Page 103: Java Magazine 41

JComboBox.As classes dos componentes em si foram destacadas por uma tonalidade mais escuras. Customizando o desenho dos itens A listagem 3 apresenta a classe SistemaOperacionalListCellRenderer e uma classe auxiliar chamada SistemaOperacionalUIResource. O motivo para criarmos esta classe adicional é que os ícones e os textos escritos para as instância de SistemaOperacioal provavelmente serão necessários para outros componentes. Isso também a classe SistemaOperacionalUIResource³ foi colocado em um pacote separado, chamado recursos. A implementação do cell renderer é muito simples ela apenas configura o JLabel com o ícone e o texto corretos. Os ícones são carregados como recursos portando devem estar em um diretório ou JAR disponível no classpath da aplicação . A classe SistemaOperacionalUIResource, alem de encapsular os nomes e localização dos ícones, garante que não haja varias copias dele em memória. O coletor de lixo da JVM agradece, assim como o gerenciador de recursos do sistema operacional. ü Embora o caminho para os ícones parece ser um caminho absoluto inicia com “/”, ele é na verdade um caminho relativo ao classpath da JVM, pois esse é o comportamento do método getResource() Seria possível vincular o cell renderer recém-criado ao JComboBox, como parte do código da JanelaPrincipal, mas em uma aplicação real isto poderia exigir a customização manual de varias telas. Melhor então criar um novo componente embutindo estas customizações. Esse novo componente estende JComboBox e se chama SistemaOperacionalJComboBox ( também na Listagem 3). Há um “pulo do gato” neste novo componente: o método setPRtotypeDisplayValue()4 recebe o “maior” valor que pode ser exibido, e calcula o tamanho que o JComboBox necessita assumir este valor completamente. O calcuo é realizado utilizando-se o cell render do componente, garantindo o resultado correto mesmo com nossas customizações. Executando o exemplo O resultado final do exemplo de JComboBox pode ser visto na Figura 3. O leitor curioso pode experimentar o exemplo utilizando diferentes look-and-feels (LAFs), por exemplo o LAF “nativo” do GTK para i Linux, ou mesmo o Napkin LAF (veja links). Ambos são apresentados na Figura 4, e foram gerados pela seguintes linhas de comando:

Page 104: Java Magazine 41

java-Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel -cp JTable/dist/JTable.jar jcombobox.Main java-Dswing.defaultlaf=net.source.napkinlaf.NapkinLookAndFeel -cp JTable/dist/JTable.jar:/d/java/napkinlaf.jar jcombobox.Main Figura 3.Tela de teste do componente customizado,agora com o novo cell Figura 4.Tela de teste com os LaFs GTK do Java 1.5 e Napkin. No segundo caso, modifique os caminhos indicados na opção –cp de acordo com o local onde você instalou o Napkin LAF. O pacote Table.jar é a distribuição dos exemplos deste arquivo (incluindo os exemplos as segunda parte), gerado pelo buildfile do Ant incluso no projeto. O segredo é passar para JVM a opção –Dswing.defaultlaf especificando o nome e a classe que implementa o LAF desejado. Finalizando, o leitor adepto da modelagem UML pode encontrar na Figura 5 um diagrama de classes exibindo a arquitetura do componente recém criado, o SistemaOperacionalJComboBox. Figura 5 .Arquitetura do componente SistemaOperacionalComoBox (em amarelo) e seu “super-componente” JComboBox (em azul);setas tracejadas indicam dependências. Listagem 3. Cell renderer para sistemas operacionais.

Page 105: Java Magazine 41

SistemaOperacionalListCellRenderer.java package combobox import dados.SistemaOperacinal; import Java.awt.*; import javax.swing.*; import recursos.SistemaOperacinalUIResources; public class SistemaOperacionalListCellRenderer extends DefaultListCellRenderer { private static final SistemaOperacionalUIResources recursos = new SistemaOperacionalUIResources (); public Component getListCellRendererComponent ( JList list, Object value, int index, boolean isSelected, Boolean cellHasFocus) { JLabel label = (JLabel) super.getListCellRendererComponent (list, value, index, isSelected, cellHasFocus);

Page 106: Java Magazine 41

label.setIcon(recursos.getIcone(( SistemaOperacional) value)); label.setText(recursos.getNome(( SistemaOperacional) value)); return label; } } SistemaOperacionalUIResources.java package recursos; import dados.SistemaOperacional; import Java.util.*; import javax.swing.*; // Ícones obtidos em http://www.entity.cc/ICONS/ // requests1.php e convertidos manualmente // do formato .ico para .png public class SistemaOperacionalUIResources { protected static final Map<SistemaOperacional , ImageIcon> ícones =

Page 107: Java Magazine 41

new HashMap<SistemaOperacional, ImageIcon>(); private static final Map<SistemaOperacional, string> nomes = new HashMap<SistemaOperacional, String>(); public SistemaOperacionalUIResources() { if 9icones.size() == 0) { icones.put(SistemaOperacional.LINUX, new ImageIcon(getClass().getResource( “/recursos/icons/linux.png”))); icones.put(SistemaOperacional.WINDOWS. new ImageIcon(getClass().getResource( “/recursos/icons/windows.png”))); icones.put(SistemaOperacional.MACOSX, new ImageIcon(getClass().getResource( “/recursos/icons/macosx.png”))); } if (nomes.size() == O) { nomes.put (SistemaOperacional. LINUX, “Linux”); nomes.put (SistemaOperacional.WINDOWS, “Windows”);

Page 108: Java Magazine 41

nomes.put (SistemaOperacional.MACOSX, “Macosx”): } } public ImageIcon getIcon(SistemaOperacional value) { return ícones.get(value); } public int getIconHeight() { return ícones.get(SistemaOperacional.LINUX). getIconHeight(); } public int getIconWidth() { return ícones.get(SistemaOperacional.LINUX). getIconWidth(); } public String getNome(SistemaOperacional value) { return nomes.get(value); } } SistemaOperacionalJComboBox.java package combobox; import dados.SistemaOperacional;

Page 109: Java Magazine 41

import javax.swing.*; public class SistemaOperacionalJComboBox extends JComboBox { public SistemaOperacionalJComboBox() { setModel (new SistemaOperacionalComboBoxModel()); SistemaOperacionalListCellRenderer cellRenderer = new SistemaOperacionalListCellRenderer(); setRenderer(cellRenderer); setPrototypeDisplayValue( SistemaOperacional.WINDOWS); } } Conclusões O uso dos componentes visuais de IDEs RAD (Rapid Apllication Development) como Delphi e VB. No Swing, espera-se que o desenvolvedor escreva códigos e crie novas classes para customizar o comportamento dos componentes, em vez de se limitar à edição de propriedade. A adaptação a esta nova filosofia de trabalho não é imediata, mas os resultados compensam. No final da contas, uma aplicação Java com interface gráfica ira conter uma grande quantidade de componentes customizados, e o benefício sera sentido pela reutilização de código. As próximas telas que necessitarem de componentes semelhantes poderão ser construída pelas simple referencias aos componentes criados anteriormente,em vez de se

Page 110: Java Magazine 41

repetir o processo manual de configuração das propriedades. Também ajuda o fato de que criar novos componentes em Java é muito mais simples do que em outros ambientes de programação visual. Na segunda e ultima parte será explorado o componente JTable. Tomando como base os conceitos vistos nessa parte, criaremos uma nova tela usando um JTable com varias customizações, além de examinar em detalhes a arquitetura desse componente. Para saber mais Interfaces Gráficas com Qualidade, Parte 1 Hugo Teixeira. Edição 38 Apresenta boas práticas para a criação de interfaces gráficas em Java, com exemplos práticos usando o JGoodies Forms. Aplicação Completa com NetBeans Fernando Lozano. Edição 25, 26 e 27 Descreve passo a passo a construção de uma aplicação de ¨Lista de Tarefas¨ usando o recursosdo NetBeans e o banco de dados HSQLDB. Datas com Swing Fernando Lozano. Edição 28 Descreve o uso dos componentes do NachoCalendar para Swing, inclusive como adicioná-lo ao pallete do Eclipse VE e do NetBeans. Formulários com Swing Brno Souza. Edição 14 Explica os conceitos relacionados com os Layout Managers do AWT/Swing.

Page 111: Java Magazine 41

LINKS: java.sun.com/docs/books/tutorial/uiswing/components Trilha do Java Tutorial sobre componentes Swing. muntjak.de/hans/java/tinylaf Página oficial do TinyLAF. jgoodies.dev.java.net Página oficial do JGoodies. napkinlaf.sf.net Página oficial do Napkin LAF.