segunda-feira, 1 de outubro de 2012

JAVA + Spring + Netbeans 7.2 - Mysql + CRUD + Struts Lesson 08

Dando seqüência nos posts de JAVA + Spring, agora vou apresentar o Framework Apache Struts

(ainda não pus a legenda das imagens )
Requisitos :
- Netbeans 7.2
- spring-3.2.0.M2 -- link direto
- estar com o banco mysql springlessons, e a tabela contato criada. ver lição 4,5, 6 ou 7
- Struts 1.3.10 (utilizarei a dist que já vem com o NB)


- Ter lido :
Posts Anteriores : 
JSNBTut - Base  que é uma preparação do ambiente para o desenvolvimento

Opcionais : 
JSNBTut - Lesson 01 - introdução ao Spring
JSNBTut - Lesson 02 - introdução ao gerenciamento de dependências no  Spring
JSNBTut - Lesson 03 - acessando banco de dados Mysql com beans e injeção de dependências
JSNBTut - Lesson 04 - outro jeito rápido de utilizar o Spring e Mysql sem utilizar Arquivos XML.
JSNBTut - Lesson 05 - comparação entre as implementações e introdução ao Swing. Jtable + DefaultTableModel e implementação de uma TableModel, ...

Obrigatório:
JSNBTut - Lesson 06 - implementando um CRUD ao exemplo , que é o projeto que continuaremos neste post.
JSNBTut - Lesson 07
- Finalizando o  CRUD completo em Java   utilizando Swing e  Spring
- Implementamos um validador com org.springframework.validation.Validator
- Implementamos um Tema
- Vimos como importar /exportar arquivos XML usando XStream
- Aprendemos a usar Anotações
- Implementamos um SplashScreen
- Aprendemos a distribuir nossa aplicação e customizar o build do projeto no build.xml



Lembrando a estrutura da nossa tabela do mysql
Figura 01 - Tabela Contato
CREATE  TABLE IF NOT EXISTS `springlessons`.`contato` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `nome` VARCHAR(45) NULL ,
  `telefone` VARCHAR(45) NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB

OBS : as vezes o nome do meu servidor Mysql pode aparece como ns1 (quando estou no escritório) ou localhost (quando estou no lap). Relax, o banco é o mesmo, ok ?


Este é o 8º da série , o objetivo deste post é implementar o programa que terminamos na lição 07 na web.  Para isso utilizaremos o framework Struts, e depois integraremos com o Spring.
Algumas info antes de criar o projeto web. Quando se trata de JSP, já existem alguns padrões de implementação e organização dos arquivos :

1) Vendo a organização do projeto (arquivos e pastas)

I ) exemplo criado com o o Maven :
Comando : mvn archetype:generate -DgroupId=com.myapp -DartifactId=meuscontatos-webapp -DarchetypeArtifactId=maven-archetype-webapp
Figura 02 - Gerando um projeto baseado no Maven

Figura 03 - Arquivos gerado pelo mvn

II) o Struts fornece um exemplo também p/ iniciar um projeto, após descompactar a lib do struts veja a pasta apps tem um arquivo struts-blank-1.3.10.war (que é um zip. descompacte esse arquivo)
Figura 04 - Exemplos do struts 1

Figura 05 - Arquivos do exemplo 

Então vimos que uma webApp contém a seguinte estrutura (com variações de implementação):

Projeto
--src
  --conf : arquivo Manifest
  --java : packages e classes Java e arquivos .properties
--web ouWebContent
   ---META-INF
       --context.xml
   ---WEB-INF
       --Arquivos.xml ; normalmente aqui vão o web.xml. o beans.xml, struts-config.xml e TLDs
   --Arquivos JSP e html, images, js, css, etc..

então vamos criar a webapp no NB:

2) Criando o Projeto Web


Figura 06 - Novo Projeto

Figura 07 - Nome do projeto

Figura 08 - Defina Servidor Tomcat ou GlassFish

Figura 09 - Adicionando o Struts 1.3.10 que vem com o NB

Figura 10 - Projeto com a inclusão do Struts

Figura 11 - Teste o projeto

Figura 12 - Teste pelo Run Projet
Figura 13 - Teste pelo Run Projet pasando a URL na mão

Figura 14 - Output do NB

O q está acontecendo aqui ? (da maneira mais simples)
Nossa aplicação contém arquivos XML que definem o comportamento dos servlets e algumas configurações.

I) Ao carregar a app,
O arquivo struts-config.xml informou uma action de nome Welcome apontado /Welcome.do
Veja o nosso arquivo struts-config.xml sem os comentários :

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd">


<struts-config>
    <form-beans>
    
    </form-beans>
    
    <global-exceptions>
    
    </global-exceptions>

    <global-forwards>
        <forward name="welcome"  path="/Welcome.do"/>
    </global-forwards>

    <action-mappings>
        <action path="/Welcome" forward="/welcomeStruts.jsp"/>
    </action-mappings>
    
    <controller processorClass="org.apache.struts.tiles.TilesRequestProcessor"/>

    <message-resources parameter="com/myapp/struts/ApplicationResource"/>    
    
    <!-- ========================= Tiles plugin ===============================-->
    <!-- ..... Paths found in Tiles definitions are relative to the main context.
    -->
    <plug-in className="org.apache.struts.tiles.TilesPlugin" >
        <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" />      
        <set-property property="moduleAware" value="true" />
    </plug-in>
    
    <!-- ========================= Validator plugin ================================= -->
    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property
            property="pathnames"
            value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
    </plug-in>
  
</struts-config>

II) e de onde vem esse .do no fim da solicitação http ?
R: da configuração do arquivo web.xml, observe a inicial :
Atenção :
- Na linha <load-on-startup>2</load-on-startup> dentro da tag <servlet> 
- No conjunto <jsp-config> que adicionei as TLDs  junto com o projeto. Pode-se usar a url do site, mas e se a WebApp estiver num servidor que não acessa a Internet ?
- E o .do está sendo "resolvido" pela tag <servlet-mapping> com um pattern .do .

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <init-param>
            <param-name>debug</param-name>
            <param-value>2</param-value>
        </init-param>
        <init-param>
            <param-name>detail</param-name>
            <param-value>2</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <jsp-config>
        <taglib>
            <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
            <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
        </taglib>
        <taglib>
            <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
            <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
        </taglib>
        <taglib>
            <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
            <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
        </taglib>
        <taglib>
            <taglib-uri>/WEB-INF/struts-nested.tld</taglib-uri>
            <taglib-location>/WEB-INF/struts-nested.tld</taglib-location>
        </taglib>
        <taglib>
            <taglib-uri>/WEB-INF/struts-tiles.tld</taglib-uri>
            <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
        </taglib>
    </jsp-config>
</web-app>

III) é chamado o arquivo welcomeStruts.jsp :
Figura 15 - arquivo welcomeStruts.jsp
Observe as linhas : <beans:message> a chave (key) welcome.Propriedade

IV) Essa definições estão no arquivo ApplicationResource.properties dentro do package com.app.struts 
Figura 16 - Arquivo properties

observe as linhas finais :
welcome.title=Struts Application
welcome.heading=Struts Applications in Netbeans!
welcome.message=It's easy to create Struts applications with NetBeans.

3) Implementando nossas classes ao projeto:

Vamos manipular a tabela contato do banco, certo ? Precisamos de : 
0) adicionar o driver do Mysql ao projeto
1) um modelo Contato
2) uma interface DAO
3) uma classe que implemente a interface DAO
4) uma interface de Serviço
5) uma classe que implemente a interface de Serviço
6) das classes p/ atender os actions que serão mapeados no arquivo struts-config.xml, que extendem a classe org.apache.struts.action.Action, 
7) definir as actions e mappings no arquivo struts-config.xml
8) criar a view (interface para o usuário), definindo um form
9) criar uma página que mostre os erros quando acontecer
OBS: ainda não vou utilizar o spring, depois implementaremos:

Mãos a obra..

0) Adicione a Lib do Mysql:

Figura 17 - Adicione a lib do Mysql

1) Criando o modelo :

Figura 18 - Criando o modelo Contato

defina 3 variávess:
int id;
String nome;
String telefone , e escolha refactor>> encapsulate Fields
Figura 19 - Encapsulando a classe Contato

CODE
package com.myapp.model;

public class Contato {
    private int id;
    private String nome;
    private String telefone;

    public Contato() {
        this.id=0;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getTelefone() {
        return telefone;
    }

    public void setTelefone(String telefone) {
        this.telefone = telefone;
    }
}

2) Criando uma interface DAO

Figura 20 - Interface DAO


CODE
package com.myapp.dao;

import com.myapp.model.Contato;
import java.util.List;

public interface ContatoDao {

    public void create(Contato contato);

    public List<Contato> findAll();
}

3) Criando uma classe que implemente a interface DAO

Figura 21- Criando a implementação da  interface

Figura 22 - nome da Classe de implementação

CODE
package com.myapp.dao;

import com.myapp.model.Contato;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ContatoDaoImpl implements ContatoDao {

    static {

        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception ex) {
            Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new ExceptionInInitializerError(ex);
        }

    }

    private Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:mysql://ns1/springlessons", "springlessons", "DHADdSXcDF29WGXy");
    }

    @Override
    public void create(Contato contato) {
        Connection Con = null;
        PreparedStatement pstm = null;
        try {
            Con = getConnection();
            String sql = "INSERT INTO `springlessons`.`contato`"
                    + "(`nome`,`telefone`)"
                    + " VALUES (?,?)";
            pstm = Con.prepareStatement(sql);
            pstm.setString(1, contato.getNome());
            pstm.setString(2, contato.getTelefone());
            pstm.executeUpdate();

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                pstm.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
            try {
                Con.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
        }
    }

    @Override
    public List<Contato> findAll() {
        Connection Con = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            Con = getConnection();
            String sql = "SELECT `contato`.`id`,`contato`.`nome`,`contato`.`telefone`"
                    + " FROM `springlessons`.`contato`";
            pstm = Con.prepareStatement(sql);
            List<Contato> contatos = new ArrayList<Contato>();
            rs = pstm.executeQuery(sql);

            while (rs.next()) {
                Contato contato = new Contato();
                contato.setId(rs.getInt("id"));
                contato.setNome(rs.getString("nome"));
                contato.setTelefone(rs.getString("telefone"));
                contatos.add(contato);
            }

            return contatos;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                pstm.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
            try {
                Con.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
        }
    }
}


4) Criando uma interface de Serviço

Figura 23 - criando a interface de Serviço


CODE
package com.myapp.service;

import com.myapp.model.Contato;
import java.util.List;


public interface ContatoService {

    public void create(Contato contato);

    public List<Contato> findAll();
}


5) Criando uma classe que implemente a interface de Serviço


Figura 24 - Implemente a interface

Figura 25 - nome da implementação

CODE
package com.myapp.service;

import com.myapp.dao.ContatoDao;
import com.myapp.dao.ContatoDaoImpl;
import com.myapp.model.Contato;
import java.util.List;


public class ContatoServiceImpl implements ContatoService {
    private ContatoDao dao = new ContatoDaoImpl();
    
    @Override
    public void create(Contato contato) {
        dao.create(contato);
    }

    @Override
    public List<Contato> findAll() {
        return dao.findAll();
    }
    
}


6) Criando as classes p/ atender os actions que serão mapeados no arquivo struts-config.xml, que extendem a classe org.apache.struts.action.Action, 


dentro de um novo package ...action
- CreateContatoAction.java
- ContatoForm.java
- RedirectContatoAction.java



I) CreateContatoAction.java
Figura 26 - Criando a Action

CODE
package com.myapp.action;

import com.myapp.model.Contato;
import com.myapp.service.ContatoService;
import com.myapp.service.ContatoServiceImpl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class CreateContatoAction extends Action {

    private ContatoService userService = new ContatoServiceImpl();

    public void setContatoService(ContatoService userService) {
        this.userService = userService;
    }

    @Override
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws Exception {
//        return super.execute(mapping, form, request, response);

        ContatoForm contatoForm = (ContatoForm) form;
        Contato contato = new Contato();
        BeanUtils.copyProperties(contato, contatoForm);
        userService.create(contato);
        return mapping.findForward("list");

    }
}



II) ContatoForm.java

Figura 27 - mande o NB criar essa classe no package action

CODE
package com.myapp.action;

import org.apache.struts.action.ActionForm;

public class ContatoForm extends ActionForm {

    private String nome;
    private String telefone;

    public ContatoForm() {
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getTelefone() {
        return telefone;
    }

    public void setTelefone(String telefone) {
        this.telefone = telefone;
    }
}


III) RedirectContatoAction.java
Figura 28 - Adicionando a action de redirect


CODE
package com.myapp.action;

import com.myapp.service.ContatoService;
import com.myapp.service.ContatoServiceImpl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class RedirectContatoAction extends Action {

    private ContatoService contatoService = new ContatoServiceImpl();

    public void setContatoService(ContatoService contatoService) {
        this.contatoService = contatoService;
    }

    @Override
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws Exception {
//        return super.execute(mapping, form, request, response);
        request.setAttribute("contatos", contatoService.findAll());
        return mapping.findForward("sucess");
    }
}

7) Definindo as actions e mappings no arquivo struts-config.xml

Xml
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd">


<struts-config>
     <form-beans>
        <form-bean name="contatoForm" 
              type="com.myapp.action.ContatoForm">
        </form-bean>
    </form-beans>
    
    <global-exceptions>
        <exception 
            type="java.lang.Exception" 
            key="user.global.ex"
            path="/error.jsp" />
    </global-exceptions>

    <global-forwards>
        <forward name="welcome"  path="/Welcome.do"/>
    </global-forwards>

    <action-mappings>
        <action path="/Welcome" forward="/welcomeStruts.jsp"/>
        <action
            path="/RedirectContato"
            type="com.myapp.action.RedirectContatoAction">
            <forward 
                name="sucess"
                path="/index.jsp">
            </forward>
        </action>        
        <action
            path="/CreateContato"
            type="com.myapp.action.CreateContatoAction"
            name="contatoForm"
            input="/RedirectContato.do">
            <forward 
                name="list"
                path="/RedirectContato.do">
            </forward>
        </action>
        <!--  samples 
            <action
            path="/"
            type="com.myapp."
            name=""
            scope=""
            validate=""
            input="" />
        </action-mappings>
        -->
    </action-mappings>
    
    <controller processorClass="org.apache.struts.tiles.TilesRequestProcessor"/>

    <message-resources parameter="com/myapp/struts/ApplicationResource"/>    
     <plug-in className="org.apache.struts.tiles.TilesPlugin" >
        <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" />      
        <set-property property="moduleAware" value="true" />
    </plug-in>
    
    <!-- ========================= Validator plugin ================================= -->
    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property
            property="pathnames"
            value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
    </plug-in>
  
</struts-config>

8) Criando a view (interface para o usuário), definindo um form


Figura 29 - Arquivo index.jsp

CODE
CODE

9) criar uma página que mostre os erros quando acontecer

Figura 30 - Preparando uma página p/ mostrar os erros

Figura 31 - Página error.jsp

o projeto deve estar assim :
Figura 32 - Estrutura do projeto até esse ponto


3) Testando o Projeto Web

Run>> Project
Figura 33 - Testando a app

Figura 34 - Testando a app, mais um cadastro

Figura 35 - O programa em Swing da lição anterior acessando a mesma tabela que a pagina

Está cadastrando.
Mas está com um comportamento estranho:
1º mostra uma tela de cadastro e ao cadastrar mostra a  lista dos cadastrados.Porquê ?
Se quisermos entrar direto na lista completa precisamos acessar pelo seguinte endereço na url :
http://localhost:8080/SpringLesson08-SpringStruts/RedirectContato.do
e o link p/ acessa a pagina criada pelo NB+Struts hello, por esse http://localhost:8080/SpringLesson08-SpringStruts/Welcome.do

Como acertamos isso ? Bom tem centenas de jeitos, vamos fazer o mais simples 1º
I) defina como index.html no arquivo web.xml
<welcome-file-list>
        <welcome-file>index.html</welcome-file>
</welcome-file-list>
e no index.html aponte p/ o mapping desejado : 
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <script type="text/javascript">
            location.href='/SpringLesson08-SpringStruts/RedirectContato.do';
        </script>
    </body>
</html>

II) Salve os arquivos, o NB automaticamente faz o Deploy da app,clique em Run. veja se o apontamento do arquivo html funcionou.

Vou fornecer a versão 1 do projeto como está até aqui.
SpringLesson08-SpringStruts-v1


4) Implementando o Spring ao projeto com o struts.


1) adicione a lib do spring ao projeto
Figura 36 - Adicionando a lib do Spring

2) altere o struts-config.xml

Xml
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_3.dtd">


<struts-config>
     <form-beans>
        <form-bean name="contatoForm" 
              type="com.myapp.action.ContatoForm">
        </form-bean>
    </form-beans>
    
    <global-exceptions>
        <exception 
            type="java.lang.Exception" 
            key="user.global.ex"
            path="/error.jsp" />
    </global-exceptions>

    <global-forwards>
        <forward name="welcome"  path="/Welcome.do"/>
    </global-forwards>

    <action-mappings>
        <action path="/Welcome" forward="/welcomeStruts.jsp"/>
        <action
            path="/RedirectContato">
            <forward 
                name="sucess"
                path="/index.jsp">
            </forward>
        </action>        
        
        <action
            path="/CreateContato"
            name="contatoForm"
            input="/RedirectContato.do">
            <forward 
                name="list"
                path="/RedirectContato.do">
            </forward>
        </action>
<!--     renomeado apos adicionar o spring
         <action
            path="/CreateContato"
            type="com.myapp.action.CreateContatoAction"
            name="contatoForm"
            input="/RedirectContato.do">
            <forward 
                name="list"
                path="/RedirectContato.do">
            </forward>
        </action>
        
        <action
            path="/RedirectContato"
            type="com.myapp.action.RedirectContatoAction">
            <forward 
                name="sucess"
                path="/index.jsp">
            </forward>
        </action> 
        
        
        
        -->
        <!--  samples 
            <action
            path="/"
            type="com.myapp."
            name=""
            scope=""
            validate=""
            input="" />
        </action-mappings>
        -->
    </action-mappings>
    <controller>
        <set-property 
            property="processorClass" 
            value="org.springframework.web.struts.DelegatingRequestProcessor" />
    </controller>
    <message-resources parameter="MessageResources"/>    
    <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn" >
        
    </plug-in>
    
<!--    <controller processorClass="org.apache.struts.tiles.TilesRequestProcessor"/>

    <message-resources parameter="com/myapp/struts/ApplicationResource"/>    
    
  
    <plug-in className="org.apache.struts.tiles.TilesPlugin" >
        <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml" />      
        <set-property property="moduleAware" value="true" />
    </plug-in>
    
     ========================= Validator plugin ================================= 
    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property
            property="pathnames"
            value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
    </plug-in>-->
  
</struts-config>


3) crie o arquivo action-servlet.xml

Xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC 
"-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
    <bean name="/CreateContato"
          class="com.myapp.action.CreateContatoAction">
        <property name="contatoService"></property>
    </bean>
    <bean name="/RedirectContato"
          class="com.myapp.action.RedirectContatoAction">
        <property name="contatoService"></property>
    </bean>
    <bean id="contatoService"
          class="com.myapp.service.ContatoServiceImpl">
        <!--<property name="contatoDao"> isso causa um erro  o correto esta abaixo-->
        <property name="dao">
            <!-- injeção da dependência-->
            <bean 
                id="contatoDao" 
                class="com.myapp.dao.ContatoDaoImpl">
            </bean>
        </property>
    </bean>
</beans>


4) salve os arquivos modificados.

5) teste o programa e veja o Log do NB:

Não deve conter nenhum erro, se tiver verifique as configurações nos arquivos XML
Sep 27, 2012 1:02:18 PM org.springframework.web.struts.ContextLoaderPlugIn init
INFO: ContextLoaderPlugIn for Struts ActionServlet 'action, module '': initialization started
Sep 27, 2012 1:02:18 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing WebApplicationContext for namespace 'action-servlet': startup date [Thu Sep 27 13:02:18 BRT 2012]; root of context hierarchy
Sep 27, 2012 1:02:19 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/action-servlet.xml]
Sep 27, 2012 1:02:20 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@fe2254a: defining beans [/CreateContato,/RedirectContato]; root of factory hierarchy
Sep 27, 2012 1:02:21 PM org.springframework.web.struts.ContextLoaderPlugIn initWebApplicationContext
INFO: Using context class 'org.springframework.web.context.support.XmlWebApplicationContext' for servlet 'action'
Sep 27, 2012 1:02:21 PM org.springframework.web.struts.ContextLoaderPlugIn init
INFO: ContextLoaderPlugIn for Struts ActionServlet 'action', module '': initialization completed in 2610 ms
Sep 27, 2012 1:02:21 PM org.apache.catalina.util.LifecycleBase start
INFO: The start() method was called on component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/SpringLesson08-SpringStruts]] after start() had already been called. The second call will be ignored.


6) altere as actions no package actions
CODE
package com.myapp.action;

import com.myapp.model.Contato;
import com.myapp.service.ContatoService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class CreateContatoAction extends Action {

//    private ContatoService userService = new ContatoServiceImpl();
    // com o Spring
    private ContatoService contatoService;

    public void setContatoService(ContatoService userService) {
        this.contatoService = userService;
    }

    @Override
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws Exception {
//        return super.execute(mapping, form, request, response);

        ContatoForm contatoForm = (ContatoForm) form;
        Contato contato = new Contato();
        BeanUtils.copyProperties(contato, contatoForm);
        contatoService.create(contato);
        return mapping.findForward("list");

    }
}


CODE
package com.myapp.action;

import com.myapp.service.ContatoService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class RedirectContatoAction extends Action {

//    private ContatoService contatoService = new ContatoServiceImpl();
    // com o Spring
    private ContatoService contatoService ;
    public void setContatoService(ContatoService contatoService) {
        this.contatoService = contatoService;
    }

    @Override
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws Exception {
//        return super.execute(mapping, form, request, response);
        request.setAttribute("contatos", contatoService.findAll());
        return mapping.findForward("sucess");
    }
}


7) altere a classe dao.ContatoServiceImpl

CODE
package com.myapp.service;

import com.myapp.dao.ContatoDao;
import com.myapp.model.Contato;
import java.util.List;


public class ContatoServiceImpl implements ContatoService {
//    private ContatoDao dao = new ContatoDaoImpl();
    // depois do spring
    private ContatoDao dao;
    
    @Override
    public void create(Contato contato) {
        getDao().create(contato);
    }

    @Override
    public List<Contato> findAll() {
        return getDao().findAll();
    }
}




8) compile  e teste. veja o erro :

Log
Offending resource: ServletContext resource [/WEB-INF/action-servlet.xml]
Bean '/CreateContato'; nested exception is org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: <property> element for property 'contatoService' must specify a ref or value
Offending resource: ServletContext resource [/WEB-INF/action-servlet.xml]
Bean '/CreateContato'
 -> Property 'contatoService'
 at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68)
 at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)



9) acerte o arquivo action-servlet.xml e adicione as ref


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC 
"-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
    <bean name="/CreateContato"
          class="com.myapp.action.CreateContatoAction">
        <property name="contatoService" ref="contatoService"></property>
    </bean>
    <bean name="/RedirectContato"
          class="com.myapp.action.RedirectContatoAction">
        <property name="contatoService" ref="contatoService"></property>
    </bean>
    <bean id="contatoService"
          class="com.myapp.service.ContatoServiceImpl">
        <!--<property name="contatoDao"> isso causa um erro  o correto esta abaixo-->
        <property name="dao">
            <!-- injeção da dependência-->
            <bean 
                id="contatoDao" 
                class="com.myapp.dao.ContatoDaoImpl">
            </bean>
        </property>
    </bean>
</beans>


10) compile  e teste. veja o erro : Log

org.springframework.beans.factory.BeanCreationException: Error creating bean with name '/CreateContato' defined in ServletContext resource [/WEB-INF/action-servlet.xml]: Cannot resolve reference to bean 'contatoService' while setting bean property 'contatoService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contatoService' defined in ServletContext resource [/WEB-INF/action-servlet.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'dao' of bean class [com.myapp.service.ContatoServiceImpl]: Bean property 'dao' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
 at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:329)

A classe precisa dos Gets/Sets

11) encapsule  a classe ContatoServiceImpl
Figura 37 - Encapsule a classe ContatoServiceImpl
CODE
package com.myapp.service;

import com.myapp.dao.ContatoDao;
import com.myapp.model.Contato;
import java.util.List;


public class ContatoServiceImpl implements ContatoService {
//    private ContatoDao dao = new ContatoDaoImpl();
    // depois do spring
    private ContatoDao dao;
    
    @Override
    public void create(Contato contato) {
        getDao().create(contato);
    }

    @Override
    public List<Contato> findAll() {
        return getDao().findAll();
    }

    public ContatoDao getDao() {
        return dao;
    }

    public void setDao(ContatoDao dao) {
        this.dao = dao;
    }
    
}
compile  e teste.
Log
Sep 27, 2012 1:43:55 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing WebApplicationContext for namespace 'action-servlet': startup date [Thu Sep 27 13:43:55 BRT 2012]; root of context hierarchy
Sep 27, 2012 1:43:55 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/action-servlet.xml]
Sep 27, 2012 1:43:55 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3e898802: defining beans [/CreateContato,/RedirectContato,contatoService]; root of factory hierarchy
Sep 27, 2012 1:43:56 PM org.springframework.web.struts.ContextLoaderPlugIn initWebApplicationContext
INFO: Using context class 'org.springframework.web.context.support.XmlWebApplicationContext' for servlet 'action'
Sep 27, 2012 1:43:56 PM org.springframework.web.struts.ContextLoaderPlugIn init
INFO: ContextLoaderPlugIn for Struts ActionServlet 'action', module '': initialization completed in 921 ms
Sep 27, 2012 1:43:56 PM org.apache.catalina.core.StandardContext reload
INFO: Reloading Context with name [/SpringLesson08-SpringStruts] is completed

12) Teste. experimente fazer mais um cadastro.
Figura 38 - Testando a app de novo

13) se tivéssemos outros objetos e tabelas precisaríamos da conexão compartilhada entre os DAOs.
Figura 39 - Crie a classe de conexão ao Mysql

CODE
package com.myapp.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ConexaoMysql {

    private Connection Conexao;

    public ConexaoMysql() {
        connect();
    }

    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception ex) {
            Logger.getLogger(ConexaoMysql.class.getName()).log(Level.SEVERE, null, ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public Connection getConexao() {
        return Conexao;
    }

    public final void connect() {
        try {
            this.Conexao = DriverManager.getConnection("jdbc:mysql://ns1/springlessons", "springlessons", "DHADdSXcDF29WGXy");
        } catch (SQLException ex) {
            Logger.getLogger(ConexaoMysql.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

14) altere o contatoDaoImpl p/ usar essa conexão :
CODE
package com.myapp.dao;

import com.myapp.model.Contato;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ContatoDaoImpl implements ContatoDao {

    ConexaoMysql Conexao = new ConexaoMysql();
    @Override
    public void create(Contato contato) {
        Connection Con = this.Conexao.getConexao();
        PreparedStatement pstm = null;
        try {
//            Con = getClass();
            String sql = "INSERT INTO `springlessons`.`contato`"
                    + "(`nome`,`telefone`)"
                    + " VALUES (?,?)";
            pstm = Con.prepareStatement(sql);
            pstm.setString(1, contato.getNome());
            pstm.setString(2, contato.getTelefone());
            pstm.executeUpdate();

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                pstm.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
            try {
                Con.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
        }
    }

    @Override
    public List<Contato> findAll() {
    Connection Con = this.Conexao.getConexao();
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
//            Con = getConnection();
            String sql = "SELECT `contato`.`id`,`contato`.`nome`,`contato`.`telefone`"
                    + " FROM `springlessons`.`contato`";
            pstm = Con.prepareStatement(sql);
            List<Contato> contatos = new ArrayList<Contato>();
            rs = pstm.executeQuery(sql);

            while (rs.next()) {
                Contato contato = new Contato();
                contato.setId(rs.getInt("id"));
                contato.setNome(rs.getString("nome"));
                contato.setTelefone(rs.getString("telefone"));
                contatos.add(contato);
            }

            return contatos;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                pstm.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
            try {
                Con.close();
            } catch (Exception e) {
                Logger.getLogger(ContatoDaoImpl.class.getName()).log(Level.SEVERE, null, e);
            }
        }
    }
}


Salve, e teste:
Figura 40 - Testando a app a pós as alterações

E assim integramos o struts 1.3.10 com o Spring 3.2
Projeto NB
Figura 41a - Arquivos do projeto

Figura 41b - Arquivos do projeto

Como este projeto tem propósitos educacionais , os frameworks são libs do NB, e estão incluídas inteiras com todas as deps.
Fica como lição de casa, adicionar manualmente apenas as libs utilizadas.
Figura 42 - Libs utilizadas do projeto

Download do projeto NB
SpringLesson08-SpringStruts-v2

Resumo, o que vimos :

- Organização de projetos WEB
- Implementar o Struts 1.3.10
- Criamos uma interface contatoDAO
- Implementamos a interface com a classe ContatoDaoImpl
- Criamos uma interface de Serviço ContatoService
- Implementamos a interface  com a classe ContatoServiceImpl
- Criamos as classes p/ atender os actions que são mapeados no arquivo struts-config.xml, que extendem a classe org.apache.struts.action.Action
- Definimos as actions e mappings no arquivo struts-config.xml
- Criamos uma view através de um arquivo index.jsp
- Criamos uma vier p/ mostrar os erros
- Integramos o Spring 3 com o Struts 2
- Definimos nossos beans no arquivo  action-servlet.xml
- Vimos nos logs a ordem em que as classes são instanciadas e como localizar erros
- Compartilhamos a conexão através de uma classe ConexaoMysql
- Testamos o cadastro e vimos funcionando


humm e como ficaria essa implementação com o Struts 2?
até o  próximo post...

segunda-feira, 24 de setembro de 2012

JAVA + Spring + Netbeans 7.2 - Mysql + Swing + CRUD + XStream Lesson 07

Dando seqüência nos posts de JAVA + Spring


Requisitos :
- Netbeans 7.2
- spring-3.2.0.M2
- estar com o banco mysql springlessons, e a tabela contato criada. ver lição 4,5 ou 6
- Lib XStream , para manipular arquivos XML .

- Ter lido :
Posts Anteriores : 
JSNBTut - Base  que é uma preparação do ambiente para o desenvolvimento

Opcionais : 
JSNBTut - Lesson 01 - introdução ao Spring
JSNBTut - Lesson 02 - introdução ao gerenciamento de dependências no  Spring

JSNBTut - Lesson 03 - acessando banco de dados Mysql com beans e injeção de dependências
JSNBTut - Lesson 04 - outro jeito rápido de utilizar o Spring e Mysql sem utilizar Arquivos XML.
JSNBTut - Lesson 05 - comparação entre as implementações e introdução ao Swing. Jtable + DefaultTableModel e implementação de uma TableModel, ...

Obrigatório:
JSNBTut - Lesson 06 - implementando um CRUD ao exemplo , que é o projeto que continuaremos neste post.

Mais abaixo forneço o Projeto do NB p/ acompanhar essa lição SpringLesson07-Base
Durante o desenvolvimento do projeto é normal fazer o programa escrever no console p/ testar valores e acompanhar a execução, por isso deixo alguns trechos de código comentado nos arquivos.


Este é o 7º da série , o objetivo deste post é implementar algumas melhorias no projeto, antes de concluir essa parte do tutorial de Spring.


Melhorias no projeto :
- 1ª Validação do formulário de edição usando o Spring, neste caso , só temos 2 campos, mas e se fossem vários??
- 2ª Splash Screen
- 3ª Tema
- 4ª Exportação de arquivo XML,CSV
- 5ª Importação de arquivo XML,CSV
- 6ª Tirar o projeto do NB, permitindo que ele rode em JVMs** que estejam na mesma rede onde está o banco de dados :
- 7ª Implementar a toda a parte  WEB da aplicação

** Quando digo JVMs, penso em OSX, Linux, Windows, etc.  utilizando o client java, e fazer uma interface web  e no server (com rich faces,servlets e struts)  p/ quem não quer ou não tem java , e uma app p/ o Android e outra p/ o Iphone (que é o que tenho na rede aqui, WII e PS2 não conta,rs)

Rede Local : Servidor Linux com o mysql, um apache TomCat (webServices e Servlets), e um samba p/ compartilhar a aplicação na rede local.


Bom, após algumas refatorações e acertos básicos de layout

Download do projeto NB
SpringLesson07-Base

Vamos começar :
Verifique se o projeto está rodando e ...

1) Implementando a validação com o Spring: 

Não é necessário adicionar libs por enquanto, o que utilizaremos do Spring já está no projeto, no arquivo spring-context-3.2.0.M2.jar
Figura 01 - Classes do Spring-context

I) Vamos criar uma classe que implementa org.springframework.validation.Validator, vou chamá-la de ContatoValidator dentro de um novo package  springlesson07.validators
Figura 02 - Nova classe ContatoValidator

II) adicione o texto implements Validator na declaração da classe,
III) Aceite as sugestões do NB de :
- importar as libs necessárias
- implementar os metodos abstratos

Codigo da classe ContatoValidator, essa implementação valida apenas o objeto Contato

package springlesson07.validators;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import springlesson07.model.Contato;


public class ContatoValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
       return Contato.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        Contato C = (Contato) obj;
// esse abaixo também funciona , apenas p/ mostrar 
//        if (C.getNome()==null || C.getNome().trim().length()==0){
//            errors.rejectValue("nome", "nome.required", "Nome é obrigatório");
//
//        }
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "nome", "nome.empty","Nome é obrigatório");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "telefone", "telefone.empty","Telefone é obrigatório");
        
//        if (errors.hasErrors()){
//            System.out.println(errors);
//        }
        
    }
  

2) Chamando o validador no form de edição do contato

I) No springlesson07.view.contato.frameEditaContato.java vamos modificar a função
private void salvarContato(), observe que a solução de validação manual está comentada e a explicação coloquei em etapas .
   
 

    private void salvarContato() {

        Contato C = new Contato();
        //Solução 1 : validação manual 
//        String preMessage = "Verifique os seguintes erros:\n";
//        String message = "";
//
//        if ("".equals(jTFNome.getText())) {
//            message += "- Nome deve ser preenchido \n";
//        }
//        System.out.println(message.length()+"");
//        if (message.length() == 0) {


        //Solucao 2 - com o Spring 
        // etapa 1 - Carga no objeto
        if ("".equals(jTFId.getText())) {
            C.setId(0);
        } else {

            C.setId(Integer.parseInt(jTFId.getText()));
            C.setNome(jTFNome.getText());
            C.setTelefone(jTFTelefone.getText());
        }
        //etapa 2 chama nosso validador de Pessoa
        ContatoValidator CV = new ContatoValidator();
        
        //etapa 3 criamos um objeto result passando o nosso Contato após a carga
        BeanPropertyBindingResult result = new BeanPropertyBindingResult(C, "Contato");
       
        //etapa 4 chamamos o validador do spring , que pode ou não conter error
        ValidationUtils.invokeValidator(CV, C, result);
       
        //etapa 5 - se não tiver erros entra no bloco que salva  
        if (result.getErrorCount() == 0) {
            this.contato = C;
            try {
                this.getDao().salvar(this.getContato());
                System.out.println("Salvando Contato:" + C.getNome());
                this.getFlC().atualizaGrade();
                this.dispose();
            } catch (java.lang.NullPointerException npe) {
                System.out.println("Erro ao Salvar o  Contato.");
                Logger.getLogger(frameEditaContato.class.getName()).log(Level.SEVERE, null, npe);
            }
        } else {
            //etapa 6 - se tiver error começa a mostrar no console
            System.out.println("Total de erros : " + result.getErrorCount());
            System.out.println("-------------------------------------------------");
           
            //etapa 7 - String p/ conter os erros e mostrar num jOptionPane p/ o usuário
            String errosGui="";
           
            //etapa 8 - criamos uma lista p/ conter os erros
            List<ObjectError> allObjectErrors = result.getAllErrors();
           
            //etapa 9 varremos os erros 
            for (ObjectError objectError : allObjectErrors) {

                if (objectError instanceof FieldError) {

                    FieldError fieldError = (FieldError) objectError;
                    System.out.println("O campo é: " + fieldError.getField());
                }

                System.out.println("Codigo " + Arrays.asList(objectError.getCodes()).toString());
                System.out.println("Codigo de erro: " + objectError.getCode());
                System.out.println("Mensagem Default: " + objectError.getDefaultMessage());
                System.out.println();
                errosGui+="- " + objectError.getDefaultMessage()+ "\n";
            }
            // fim da etapa 9
            System.out.println("-------------------------------------------------");
          
            // etapa 10 - Informando o usuário
            JOptionPane.showMessageDialog(null, "Erros ao salvar.Mais detalhes, veja o console.\n" + errosGui );
        }
//        } else {
//            JOptionPane.showMessageDialog(null, preMessage + message);
//        }
    }
// ao final sua classe deve conter esses imports :
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
import springlesson07.dao.ContatoDAO;
import springlesson07.model.Contato;
import springlesson07.validators.ContatoValidator; 



 II) Teste o programa : Tente criar um novo Contato e salvar sem preencher nada:
o resultado deve ser esse
Figura 03 - Teste com validação do Spring

Observe o output no console do NB : 

run:
Sep 23, 2012 2:34:25 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@669980d5: startup date [Sun Sep 23 14:34:24 BRT 2012]; root of context hierarchy
Sep 23, 2012 2:34:25 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [beans-definitions.xml]
Sep 23, 2012 2:34:25 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5470be88: defining beans [dataSource,jdbcTemplate,ContatoDao]; root of factory hierarchy
Sep 23, 2012 2:34:25 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
Sep 23, 2012 2:34:26 PM org.springframework.jdbc.datasource.SingleConnectionDataSource initConnection
INFO: Established shared JDBC Connection: com.mysql.jdbc.JDBC4Connection@63779885
Total de erros : 2
-------------------------------------------------
O campo é: nome
Codigo [nome.empty.Contato.nome, nome.empty.nome, nome.empty.java.lang.String, nome.empty]
Codigo de erro: nome.empty
Mensagem Default: Nome é obrigatório

O campo é: telefone
Codigo [telefone.empty.Contato.telefone, telefone.empty.telefone, telefone.empty.java.lang.String, telefone.empty]
Codigo de erro: telefone.empty
Mensagem Default: Telefone é obrigatório

-------------------------------------------------

3) Adicionando o tema :

Podemos utilizar os pacotes que o Java já tem para dar uma interface mais elegante ao nosso programa :
I) Para isso , vamos criar uma classe Tema:
Figura 04 - Nova Classe p/ utilizar um Tema


essa classe fica assim :


package springlesson07.utils;

import com.sun.java.swing.plaf.gtk.GTKLookAndFeel;
import com.sun.java.swing.plaf.motif.MotifLookAndFeel;
import com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel;
import com.sun.java.swing.plaf.windows.WindowsLookAndFeel;
import java.awt.Component;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.metal.MetalLookAndFeel;

public class Tema {

    int tema;

    public Tema() {
    }

    public int getTema() {
        return tema;
    }

    public void setTema(int tema) {
        this.tema = tema;
    }

//    public final void setTheme(int numTema, JRootPane rootPane) { // se fosse com MDI
    public final void setTheme(int numTema, Component rootPane) {
        setTema(numTema);

        try {

            System.out.println("Antigo: " + UIManager.getLookAndFeel());
            switch (numTema) {
                case 1:
                    UIManager.setLookAndFeel(new NimbusLookAndFeel());
                    break;
                case 2:
                    UIManager.setLookAndFeel(new MotifLookAndFeel());
                    break;
                case 3:
                    UIManager.setLookAndFeel(new WindowsLookAndFeel());
                    break;

                case 4:
                    UIManager.setLookAndFeel(new MetalLookAndFeel());
                    break;
                case 5:
                    UIManager.setLookAndFeel(new GTKLookAndFeel());
                    break;
                default:

                    break;
            }
            System.out.println("Novo: " + UIManager.getLookAndFeel());
            //   // Update the ComponentUIs for all Components. This
            //   // needs to be invoked for all windows.
            SwingUtilities.updateComponentTreeUI(rootPane);


        } catch (UnsupportedLookAndFeelException ulfe) {
            //            ulfe.printStackTrace();
            System.out.println("nao suportado");
        } catch (java.lang.NoClassDefFoundError ncdf) {
            //            ncdf.printStackTrace();
            System.out.println("nao disponivel");
        }

    }
}


Se estivessemos usando uma aplicação MDI com a classe jDesktop, ao aplicar o tema no pai as filhas já herdam, mas como não estamos fazendo assim : precisamos chamar o tema em cada janela :
no package view

II) no arquivo frameListaContatos vamos criar uma função setTema e chamá-la no contrutor:
    public final class frameListaContatos extends javax.swing.JFrame {

    private ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-definitions.xml");
    private ContatoDAO dao = (ContatoDAO) ctx.getBean("contatoDao");
    private ContatoTableModel tableModel = new ContatoTableModel(dao.listar());
    private Contato contatoAtivo;
    private frameEditaContato janelaEdicao;
    
    /**
     * Creates new form frameListaContatos
     */
    public frameListaContatos() {
        initComponents();
        carregaTabela();
        setJanelaEdicao(new frameEditaContato(this, getDao()));
        setTema(1);
    }
  
    public  void setTema(int tema) {
          Tema T = new Tema();
          T.setTheme(tema, rootPane);
    }
}



III) no arquivo frameEditaContato vamos criar uma função setTema e chamá-la no contrutor:
public final class frameEditaContato extends javax.swing.JFrame {

    private frameListaContatos flC;
    private ContatoDAO dao;
    private Contato contato;

  
    //novo construtor
    public frameEditaContato(frameListaContatos flC, ContatoDAO daoInformed) {
        initComponents();
        this.flC = flC;
        this.dao = daoInformed;
        this.contato = new Contato(); // que já possui um id=0
        setTema();
    }
    
      public final void setTema() {
        Tema T = new Tema();
        T.setTheme(1, SwingUtilities.getRootPane(this));
    }
}


Observe que as classes ganharam o modificador final


4) Teste o programa e veja se aplicou o tema nas duas janelas


Figura 05 - Tema implementado


5) Implementando Exportação arquivos XML.

I) Vamos adicionar 2 botões a nossa Lista de Contatos springlesson07.view.contato.frameListaContatos.java

Figura 06 - Adicionando os botões p/ importar/exportar XML

Imagens :

importar 

 exportar 

 O spring fornece uma interface p/ trabalhar com o Xstream org.springframework.oxm.xstream.XStreamMarshaller que me levou ao XStream, mas veremos esse pacote numa lição mais a frente (web)

Figura 07 - Documentação do Spring

II ) baixe o Xstream, e após baixar o Binary distribution: , crie uma pasta components dentro do seu projeto e coloque ali os arquivos : xstream-1.4.3.jar e sua dependencia : xmlpull-1.1.3.1.jar

Figura 08 - Obtendo o XStream

III) e adicione as libs no projeto : 
Figura 09 - Adicionando a lib XStream ao projeto


Veja que simples fica (1º vamos implementar a solução, depois vamos organizar o código para facilitar a explicação)
Exportar XML : Solução 1
IV)  No arquivo springlesson07.view.contato.frameListaContatos.java, clique duas vezes no botão exportar XML :  e escreva a função exportarXML(); e deixe o NB criar a função
Figura 10 - Utilizando o NB p/ gerar o código da função exportarXML

 
   private void jBtExportXMLActionPerformed(java.awt.event.ActionEvent evt) {
        exportarXML();
    }
   private void exportarXML() {
    
        XStream xstream = new XStream();
        List<Contato> listaContato = this.getDao().listar();
        xstream.alias("contato", Contato.class);
        String xmlOut = xstream.toXML(listaContato);
        System.out.println("Gerando arquivo XML:");
        System.out.println(xmlOut);
        
    }
Observe o import
import com.thoughtworks.xstream.XStream;


6) Teste o programa e clique no botao Exportar XML, veja o resultado no console :
Figura 11 - Veja a String p/ o Arquivo XML no console do NB

7) Alterando o jeito de exportar o XML

Para não fazer o usuário ter q copiar do console e colar em um novo arquivo, vamos criar esse arquivo:
com o nome contatos_DIA_MES_ANO.xml e ao gerar o arquivo , vamos abri-lo p/ o usuário (função openFile() que criaremos abaixo). Isso dependerá de qual o programa que o seu SO usará para abrir o arquivo. No meu caso abriu com o Xcode.

I) Alterando a função exportarXML e adicionando a função openFile (solução 1)
No arquivo springlesson07.view.contato.frameListaContatos.java

private void exportarXML() {

        XStream xstream = new XStream();
        List<Contato> listaContato = this.getDao().listar();
        xstream.alias("contato", Contato.class);
        String xmlOut = xstream.toXML(listaContato);
        System.out.println("Gerando arquivo XML:");
//        System.out.println(xmlOut);

        // p/ gerar o nome do arquivo
        Calendar calendar = Calendar.getInstance();
        String dataHojeFormatada = calendar.get(calendar.DATE) + "/" + (calendar.get(calendar.MONTH) + 1) + "/" + calendar.get(calendar.YEAR) +" "+ calendar.get(calendar.HOUR_OF_DAY) + ":" + + calendar.get(calendar.MINUTE) + ":" + + calendar.get(calendar.SECOND);
        String dataNomeArquivo = dataHojeFormatada.replace(" ", "_").replace(":", "_").replace("/", "_");
        String nomeArquivo = "contatos_" + dataNomeArquivo + ".xml";
        
        // p/ salvar o arquivo solução 1
        try {
            FileOutputStream fs = new FileOutputStream(nomeArquivo);
            
            xstream.toXML(listaContato, fs);
            fs.close();

            System.out.println("Arquivo gerado:" + nomeArquivo);
            //abre o arquivo p/ o usuário fora do programa
            openFile(new File(nomeArquivo));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(frameListaContatos.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(frameListaContatos.class.getName()).log(Level.SEVERE, null, ex);

        }
    }
        public void openFile(File document) throws IOException {
        Desktop dt = Desktop.getDesktop();
        dt.open(document);
    }

Observe os imports
import com.thoughtworks.xstream.XStream;
import java.awt.Desktop;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import springlesson07.dao.ContatoDAO;
import springlesson07.model.Contato;
import springlesson07.model.ContatoTableModel;
import springlesson07.utils.Tema;

Teste o programa, e o resultado deve ser :
Figura 12 - Exportando nosso 1º XML observe que o root node é list

e no output temos :

run:
Sep 24, 2012 12:33:38 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@bd10a5c: startup date [Mon Sep 24 12:33:38 BRT 2012]; root of context hierarchy
Sep 24, 2012 12:33:38 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [beans-definitions.xml]
Sep 24, 2012 12:33:39 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6401d98a: defining beans [dataSource,jdbcTemplate,ContatoDao]; root of factory hierarchy
Sep 24, 2012 12:33:39 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
Sep 24, 2012 12:33:40 PM org.springframework.jdbc.datasource.SingleConnectionDataSource initConnection
INFO: Established shared JDBC Connection: com.mysql.jdbc.JDBC4Connection@31e46a68
Antigo: [Aqua Look and Feel for Mac OS X - com.apple.laf.AquaLookAndFeel]
Novo: [Nimbus Look and Feel - com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel]
Antigo: [Nimbus Look and Feel - com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel]
Novo: [Nimbus Look and Feel - com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel]
Gerando arquivo XML:
Arquivo gerado:contatos_24_9_2012_12_33_55.xml

Arquivo xml contatos_24_9_2012_12_33_55.xml :

<list>
  <contato>
    <id>3</id>
    <nome>Cliente 1</nome>
    <telefone>2222-3333</telefone>
  </contato>
  <contato>
    <id>4</id>
    <nome>Cliente 2 renomeado de novo</nome>
    <telefone>4444-5555</telefone>
  </contato>
</list>

II) Alterando a função exportarXML  (solução 2)
No arquivo springlesson07.view.contato.frameListaContatos.java

    private void exportarXML() {

        XStream xstream = new XStream();
        List<Contato> listaContato = this.getDao().listar();
        xstream.alias("contato", Contato.class);
        String xmlOut = xstream.toXML(listaContato);
        System.out.println("Gerando arquivo XML:");
//        System.out.println(xmlOut);

        // p/ gerar o nome do arquivo
        Calendar calendar = Calendar.getInstance();
        String dataHojeFormatada = calendar.get(calendar.DATE) + "/" + (calendar.get(calendar.MONTH) + 1) + "/" + calendar.get(calendar.YEAR) +" "+ calendar.get(calendar.HOUR_OF_DAY) + ":" + + calendar.get(calendar.MINUTE) + ":" + + calendar.get(calendar.SECOND);
        String dataNomeArquivo = dataHojeFormatada.replace(" ", "_").replace(":", "_").replace("/", "_");
        String nomeArquivo = "contatos_" + dataNomeArquivo + ".xml";
        
        // p/ salvar o arquivo solução 1
//        try {
//            FileOutputStream fs = new FileOutputStream(nomeArquivo);
//            
//            xstream.toXML(listaContato, fs);
//            fs.close();
//
//            System.out.println("Arquivo gerado:" + nomeArquivo);
//            //abre o arquivo p/ o usuário fora do programa
//            openFile(new File(nomeArquivo));
//        } catch (FileNotFoundException ex) {
//            Logger.getLogger(frameListaContatos.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (IOException ex) {
//            Logger.getLogger(frameListaContatos.class.getName()).log(Level.SEVERE, null, ex);
//
//        }
        
         // p/ salvar o arquivo solução 2
        try {

            Writer writer;
            writer = new FileWriter(nomeArquivo);
            ObjectOutputStream out = xstream.createObjectOutputStream(writer);
//adicionando um header
          writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            for (Contato C : listaContato) {
                out.writeObject(C);
            }
            out.close();
            System.out.println("Arquivo gerado:" + nomeArquivo);
            openFile(new File(nomeArquivo));
        } catch (IOException ex) {
            Logger.getLogger(frameListaContatos.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
        public void openFile(File document) throws IOException {
        Desktop dt = Desktop.getDesktop();
        dt.open(document);
    }
Observe os imports
import com.thoughtworks.xstream.XStream;
import java.awt.Desktop;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Writer;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import springlesson07.dao.ContatoDAO;
import springlesson07.model.Contato;
import springlesson07.model.ContatoTableModel;
import springlesson07.utils.Tema;
Figura 13 - Exportando nosso 2º XML observe que o root node é object-stream  e adicionamos o cabeçalho no XML
e no output temos :
run:
Sep 24, 2012 12:54:50 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@669980d5: startup date [Mon Sep 24 12:54:50 BRT 2012]; root of context hierarchy
......
Gerando arquivo XML:
Arquivo gerado:contatos_24_9_2012_13_15_11.xml
Arquivo xml contatos_24_9_2012_13_15_11.xml :
<?xml version="1.0" encoding="UTF-8"?>
<object-stream>
  <contato>
    <id>3</id>
    <nome>Cliente 1</nome>
    <telefone>2222-3333</telefone>
  </contato>
  <contato>
    <id>4</id>
    <nome>Cliente 2 renomeado de novo</nome>
    <telefone>4444-5555</telefone>
  </contato>
</object-stream>

Funcionamento parecido, gerou o arquivo XML , mas dentro de uma tag <object-stream> e adicionamos um header ao arquivo
Para deixar esse arquivo XML no jeito faremos uma modificação em nosso objeto out  assim:
ObjectOutputStream out = xstream.createObjectOutputStream(writer,"contatos");

Teste o programa, e o resultado deve ser :
Figura 14 - Exportando nosso 3º XML observe que o root node é contatos

Arquivo xml contatos_24_9_2012_13_41_9.xml :
<?xml version="1.0" encoding="UTF-8"?>
<contatos>
  <contato>
    <id>3</id>
    <nome>Cliente 1</nome>
    <telefone>2222-3333</telefone>
  </contato>
  <contato>
    <id>4</id>
    <nome>Cliente 2 renomeado de novo</nome>
    <telefone>4444-5555</telefone>
  </contato>
</contatos>

Agora que já sabemos como gerar  o Arquivo XML corretamente com o header, vamos organizar esse monte de código
III) Crie uma nova Classe de nome XmlUtil
Figura 15 - Criando uma classe XmlUtil para manipular o XML e organizar melhor o código

essa classe fica assim : 

package springlesson07.utils;

import com.thoughtworks.xstream.XStream;
import java.awt.Desktop;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Writer;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import springlesson07.model.Contato;

public class XmlUtil {

    
    private Calendar calendar;
    private String dataHojeFormatada;
    private String dataNomeArquivo;
    private String nomeArquivo;
    private XStream xstream;

    public XmlUtil() {
        // p/ gerar o nome do arquivo no contrutor
        this.calendar = Calendar.getInstance();
        this.dataHojeFormatada = calendar.get(calendar.DATE) + "/" + (calendar.get(calendar.MONTH) + 1) + "/" + calendar.get(calendar.YEAR) + " " + calendar.get(calendar.HOUR_OF_DAY) + ":" + +calendar.get(calendar.MINUTE) + ":" + +calendar.get(calendar.SECOND);
        this.dataNomeArquivo = dataHojeFormatada.replace(" ", "_").replace(":", "_").replace("/", "_");
        this.nomeArquivo = "contatos_" + dataNomeArquivo + ".xml";
        this.xstream = new XStream();
    }

    public String getNomeArquivo() {
        return nomeArquivo;
    }

    public void setNomeArquivo(String nomeArquivo) {
        this.nomeArquivo = nomeArquivo;
    }

    public XStream getXstream() {
        return xstream;
    }

    public void setXstream(XStream xstream) {
        this.xstream = xstream;
    }

    public void exportarArquivoContatos(List<Contato> list) {
        try {
            System.out.println("Gerando arquivo XML:");
            getXstream().alias("contato", Contato.class);
            Writer writer;
            writer = new FileWriter(getNomeArquivo());
            ObjectOutputStream out = getXstream().createObjectOutputStream(writer, "contatos");
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            for (Contato C : list) {
                out.writeObject(C);
            }
            out.close();
            System.out.println("Arquivo gerado:" + getNomeArquivo());
            openFile(new File(getNomeArquivo()));
        } catch (IOException ex) {
            Logger.getLogger(XmlUtil.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void openFile(File document) throws IOException {
        Desktop dt = Desktop.getDesktop();
        dt.open(document);
    }

}

Alterando a função exportarXML  (solução 3)
No arquivo springlesson07.view.contato.frameListaContatos.java

    private void exportarXML() {

        List<Contato> listaContato = this.getDao().listar();
        XmlUtil xmlU = new XmlUtil();
        xmlU.exportarArquivoContatos(listaContato);
        JOptionPane.showMessageDialog(null, "Arquivo gerado\n " + xmlU.getNomeArquivo());

    }

Após alterar, Teste o programa, clique no botão exportar XML
Figura 16 - arquivo gerado pela classe XmlUtil , e retorno de sucesso ao usuário
Se o arquivo exportou corretamente, vamos ver como importar ?


8) Implementando Importação arquivos XML.

Precisamos adicionar o comportamento do botão Importar XML. Logo, precisamos mostrar uma caixa de diálogo p/ o usuário poder escolher o arquivo XML

I) Usuário seleciona o arquivo
No arquivo springlesson07.view.contato.frameListaContatos.java , clique duas vezes no botao importar  XML  e dentro do actionPerformed escreva uma nova função importarXML e deixe o NB gerar o código

Figura 17 - Utilizando o NB p/ gerar o código da função importarXML
P/ abrir a caixa de diálogo simplesmente e mostrar o arquivo que o usuário selecionou o seguinte código atende :


    private void importarXML() {
        JFileChooser chooser = new JFileChooser();
        int retornoJFC = chooser.showOpenDialog(null);
        if (retornoJFC == JFileChooser.APPROVE_OPTION) {
            System.out.println("Arquivo escolhido: \n"
                    + "Pasta: " + chooser.getSelectedFile().getAbsolutePath() + "\n"
                    + "Arquivo: " + chooser.getSelectedFile().getName());
        }
    }

Teste o programa , clique no importar arquivo,selecione um arquivo e veja as informações no console do NB:
Figura 18 - Caixa de diálogo p/ o usuário escolher o arquivo a ser importado


Porém queremos apenas arquivos xml : p/ isso adicionamos a função um filtro (muito simples):

II) Adicionando um filtro ao JFileChooser

import javax.swing.filechooser.FileNameExtensionFilter;
private void importarXML() {
        JFileChooser chooser = new JFileChooser("Selecione um arquivo XML");
        chooser.setFileFilter(new FileNameExtensionFilter("Arquivos XML", "xml"));
        int retornoJFC = chooser.showOpenDialog(null);
        
        if (retornoJFC == JFileChooser.APPROVE_OPTION) {
            System.out.println("Arquivo escolhido: \n"
                    + "Pasta: " + chooser.getSelectedFile().getAbsolutePath() + "\n"
                    + "Arquivo: " + chooser.getSelectedFile().getName());
        }
    }


Teste o programa , e veja filtro funcionando.
Figura 19 - filtro dos tipos de Arquivo por extensão XML

Certo. já temos o nome e caminho do arquivo. Agora faremos o esquema para importar para nosso banco de dados na tabela contato.
Precisamos fazer algumas modificações antes de continuar.
Para fazer a leitura de Arquivos XML utilizando o esquema desse exemplo, precisamos usar o  recurso Annotations .

III) Implementando as anotações
a) no arquivo modelo springlesson07.model.Contato.java

package springlesson07.model;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("contato")
public class Contato {

    private int id;
    private String nome;
    private String telefone;

    public Contato() {
        this.id = 0;
    }//gets e sets
}

b) precisamos criar uma classe Contatos.java , incluido a anotação por que geramos o arquivo dentro de tags <contatos></contatos>
Figura 20 - Adicionando as anotações p/ o XStream

essa classe fica assim:

package springlesson07.model;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import java.util.List;

@XStreamAlias("contatos")
public class Contatos {

    private List<Contato> list;

    public Contatos() {
    }

    public List<Contato> getList() {
        return list;
    }

    public void setList(List<Contato> list) {
        this.list = list;
    }
}

IV) implementando a função importarXML no arquivo springlesson07.utils.XmlUtil.java
Adicione outra dependencia do XStream ao projeto (conforme o passo 5 etapa II): dom4j-1.61.jar 
Figura 21 - adicione a dep do XStream : dom4j-1.61.jar


usaremos outros imports para essa função :
import com.thoughtworks.xstream.io.xml.Dom4JDriver;
import java.io.BufferedReader;
import java.io.FileReader;
e para armazenar o Conteúdo do arquivo XML,  no definimos o atributo xstream para  new XStream(new Dom4JDriver())
V) essa função na classe XmlUtil fica assim: boolean para retornar se importou com sucesso.
 
   public boolean importarArquivoContatos(String F) {
         
        this.xstream = new XStream(new Dom4JDriver());
        try {
            getXstream().alias("contato", Contato.class);
            getXstream().alias("contatos", List.class);
            getXstream().processAnnotations(Contato.class);
            BufferedReader input = new BufferedReader(new FileReader(F));
            List<Contato> contatos = (List) getXstream().fromXML(input);
            input.close();
            for (Contato C : contatos) {
                
                System.out.println("Contato: " + C.getNome() + " - " + C.getTelefone());
                
            }
            input.close();
            
        return true;
        
        } catch (IOException ex) {
            Logger.getLogger(XmlUtil.class.getName()).log(Level.SEVERE, null, ex);
        }
        // arquivos com tags erradas
        catch (com.thoughtworks.xstream.io.StreamException ex) {
            System.err.println("Problemas com o arquivo :" + F);
            Logger.getLogger(XmlUtil.class.getName()).log(Level.SEVERE, null, ex);
        }
        return false;
        
    }
VI) Alterando a função importarXml do arquivo frameListaContatos (criado no início do passo 8)

    private void importarXML() {
        JFileChooser chooser = new JFileChooser("Selecione um arquivo XML");
        chooser.setFileFilter(new FileNameExtensionFilter("Arquivos XML", "xml"));
        int retornoJFC = chooser.showOpenDialog(null);

        if (retornoJFC == JFileChooser.APPROVE_OPTION) {
            System.out.println("Arquivo escolhido: \n"
                    + "Pasta: " + chooser.getSelectedFile().getAbsolutePath() + "\n"
                    + "Arquivo: " + chooser.getSelectedFile().getName());

            XmlUtil xmlU = new XmlUtil();
            String message;
            boolean importResult = xmlU.importarArquivoContatos(chooser.getSelectedFile().getAbsolutePath());
            if (importResult) {
                message = "Importação concluída.";
                // o q fazer com os dados...
            } else {
                message = "Importação com erros.\n"
                        + "Verifique o console.";
            }
            JOptionPane.showMessageDialog(rootPane, message);
        }

    }


VII) Vamos 1º testar com um arquivo XML errado p/ ver esse segundo catch em execução
crie um arquivo de nome contatos_Import_Errado.xml, veja uma tag nomes e outra telefono
<?xml version="1.0" encoding="UTF-8"?>
<contatos>
  <contato>
    <nomes>João 1 XML</nome>
    <telefono>3475-9876</telefone>
  </contato>
  <contato>
    <nome>Cliente 2 XML</nome>
    <telefone>7654-5432</telefone>
  </contato>
</contatos>

Salve esse arquivo e Teste o programa, clique no importar XML e escolha o arquivo  que está com erro

Figura 22 - teste de importação de arquivo com tags erradas

o resultado deve ser :
Figura 23 - resultado do teste de importação de arquivo com tags erradas

VIII) Vamos 1º testar com um arquivo XML correto.
crie um arquivo de nome contatos_Import.xml,  (pode alterar o q que foi gerado pelo programa)
<?xml version="1.0" encoding="UTF-8"?>
<contatos>
  <contato>
    <nome>João 1 XML</nome>
    <telefone>3475-9876</telefone>
  </contato>
  <contato>
    <nome>Cliente 2 XML</nome>
    <telefone>7654-5432</telefone>
  </contato>
  <contato>
    <nome>Cliente 3 XML</nome>
    <telefone>8888-3333</telefone>
  </contato>
  <contato>
    <nome>Cliente 4 XML</nome>
    <telefone>6543-2222</telefone>
  </contato>
</contatos>

Salve esse arquivo e Teste o programa, clique no importar XML e escolha o arquivo  correto
Figura 24 - teste de importação de arquivo com tags corretas, mas os valores ainda estão apenas no console do NB

Observe o Output do NB
Arquivo escolhido: Pasta: /Users/surfer/contatos_Import.xml Arquivo: contatos_Import.xml Contato: João 1 XML - 3475-9876 Contato: Cliente 2 XML - 7654-5432 Contato: Cliente 3 XML - 8888-3333 Contato: Cliente 4 XML - 6543-2222



OK. :) os dados estão aí. 
Mas agora o que fazer?? Em uma situação real, precisaríamos verificar esses dados para não duplicar registros, ou quando identificar nome ou telefone, perguntar se deseja atualizar o existente. Também não estou utilizando o atributo id na importação, vou salvar os dados no banco p/ completar o tutorial, mas fica a sugestão de fazer todas as verificações necessárias para manter a integridade dos dados.
IX) Salvando os dados do XML no banco (acho melhor fazer em outro passo, next)

9) Salvando os dados no Mysql

Obs: Durante os testes vou ter q limpar essa tabela várias vezes (até acertar, rs), portanto nas imagens capturadas, as vezes os registros pode estar diferentes. Atenha-se ao código..

Já temos a função salvar que recebe um objeto Contato em nosso ContatoDAO.
Implementação Quick and Dirty:

I) P/ salvar direto sem verificação, basta criar uma instância do ContatoDAO e chamar o método salvar  dentro no loop for que varre a lista de contatos criado no passo 8 etapa V, mas para mostrar na tela quantos registros foram importados

- Modificamos a classe XmlUtil p/ armazenar a lista de contatos importados do XML com Get/Set, entre outras coisas, e além disso já fizemos um validador de Contatos no início dessa lição. 

Como fica isso ? 
II) na classe springlesson07.utils.XmlUtil.java modificamos para  o seguinte código:

 package springlesson07.utils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.Dom4JDriver;
import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Writer;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.ValidationUtils;
import springlesson07.dao.ContatoDAO;
import springlesson07.model.Contato;
import springlesson07.validators.ContatoValidator;

public final class XmlUtil {

    private Calendar calendar;
    private String dataHojeFormatada;
    private String dataNomeArquivo;
    private String nomeArquivo;
    private XStream xstream;
    // to import 
    private List<Contato> contatosImportados;
    private int totalImportados;
    private ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-definitions.xml");
    private ContatoDAO dao;

    public XmlUtil() {
        // p/ gerar o nome do arquivo no contrutor
        this.calendar = Calendar.getInstance();
        this.dataHojeFormatada = calendar.get(calendar.DATE) + "/" + (calendar.get(calendar.MONTH) + 1) + "/" + calendar.get(calendar.YEAR) + " " + calendar.get(calendar.HOUR_OF_DAY) + ":" + +calendar.get(calendar.MINUTE) + ":" + +calendar.get(calendar.SECOND);
        this.dataNomeArquivo = dataHojeFormatada.replace(" ", "_").replace(":", "_").replace("/", "_");
        this.nomeArquivo = "contatos_" + dataNomeArquivo + ".xml";
        this.xstream = new XStream();
        this.dao = (ContatoDAO) getCtx().getBean("contatoDao");

    }

    public ApplicationContext getCtx() {
        return ctx;
    }

    public String getNomeArquivo() {
        return nomeArquivo;
    }

    public void setNomeArquivo(String nomeArquivo) {
        this.nomeArquivo = nomeArquivo;
    }

    public XStream getXstream() {
        return xstream;
    }

    public void setXstream(XStream xstream) {
        this.xstream = xstream;
    }

    public void setTotalImportados(int totalImportados) {
        this.totalImportados = totalImportados;
    }

    public int getTotalImportados() {
        return totalImportados;
    }

    public List<Contato> getContatosImportados() {
        return contatosImportados;
    }

    public void setContatosImportados(List<Contato> contatosImportados) {
        this.contatosImportados = contatosImportados;
    }
    private int errosImportacao;

    public void setErrosImportacao(int errosImportacao) {
        this.errosImportacao = errosImportacao;
    }

    public int getErrosImportacao() {
        return errosImportacao;
    }

    public ContatoDAO getDao() {
        return dao;
    }

    public void setDao(ContatoDAO dao) {
        this.dao = dao;
    }

    public void exportarArquivoContatos(List<Contato> list) {
        try {
            System.out.println("Gerando arquivo XML:");
            getXstream().alias("contato", Contato.class);
            Writer writer;
            writer = new FileWriter(getNomeArquivo());
            ObjectOutputStream out = getXstream().createObjectOutputStream(writer, "contatos");
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            for (Contato C : list) {
                out.writeObject(C);
            }
            out.close();
            System.out.println("Arquivo gerado:" + getNomeArquivo());
            openFile(new File(getNomeArquivo()));
        } catch (IOException ex) {
            Logger.getLogger(XmlUtil.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void openFile(File document) throws IOException {
        Desktop dt = Desktop.getDesktop();
        dt.open(document);
    }

    public boolean importarArquivoContatos(String F) {

        this.xstream = new XStream(new Dom4JDriver());
        int erros = 0;
        int imports = 0;
        try {
            getXstream().alias("contato", Contato.class);
            getXstream().alias("contatos", List.class);
            getXstream().processAnnotations(Contato.class);
            BufferedReader input = new BufferedReader(new FileReader(F));
            List<Contato> contatos = (List) getXstream().fromXML(input);
            List<Contato> contatosOk = null;
            input.close();

            for (Contato C : contatos) {

                System.out.println("Contato: " + C.getId() + " : " + C.getNome() + " - " + C.getTelefone());
                ContatoValidator CV = new ContatoValidator();
                BeanPropertyBindingResult result = new BeanPropertyBindingResult(C, "Contato");
                ValidationUtils.invokeValidator(CV, C, result);

                if (result.getErrorCount() == 0) {
                    try {
                        this.getDao().salvar(C);
                        imports++;
                        contatosOk.add(C);

                    } catch (java.lang.NullPointerException npe) {
                        // importa os dados mas entra aqui.. p/ examinar com mais calma
//                        System.out.println("Erro ao Salvar o  Contato.");
//                        Logger.getLogger(XmlUtil.class.getName()).log(Level.SEVERE, null, npe);
                    }
                } else {
                    erros++;
                    debugResults(result, erros);
                }
            }
            input.close();
            this.setTotalImportados(imports);
            this.setContatosImportados(contatosOk);// para acessarmos após a importação    

            System.out.println("Total de erros:" + erros);
            setErrosImportacao(erros);

            if (erros == 0) {
                return true;
            } else {
                return false;
            }

        } catch (IOException ex) {
            Logger.getLogger(XmlUtil.class.getName()).log(Level.SEVERE, null, ex);
        } // arquivos com tags erradas
        catch (com.thoughtworks.xstream.io.StreamException ex) {
            System.err.println("Problemas com o arquivo :" + F);
            Logger.getLogger(XmlUtil.class.getName()).log(Level.SEVERE, null, ex);
        }
        return false;

    }

    private void debugResults(BeanPropertyBindingResult result, int erroN) {
        System.out.println("Erro nº : " + erroN);
        System.out.println("Total de erros : " + result.getErrorCount());
        System.out.println("-------------------------------------------------");
        String erros = "";
        List<ObjectError> allObjectErrors = result.getAllErrors();
        for (ObjectError objectError : allObjectErrors) {

            if (objectError instanceof FieldError) {

                FieldError fieldError = (FieldError) objectError;
                System.out.println("O campo é: " + fieldError.getField());
            }

            System.out.println("Codigo " + Arrays.asList(objectError.getCodes()).toString());
            System.out.println("Codigo de erro: " + objectError.getCode());
            System.out.println("Mensagem Default: " + objectError.getDefaultMessage());
            System.out.println();
            erros += "- " + objectError.getDefaultMessage() + "\n";
        }

        System.out.println("-------------------------------------------------");
    }
}

III) e modificamos função importarXML do frameListaContatos

    private void importarXML() {
        JFileChooser chooser = new JFileChooser("Selecione um arquivo XML");
        chooser.setFileFilter(new FileNameExtensionFilter("Arquivos XML", "xml"));
        int retornoJFC = chooser.showOpenDialog(null);

        if (retornoJFC == JFileChooser.APPROVE_OPTION) {
            System.out.println("Arquivo escolhido: \n"
                    + "Pasta: " + chooser.getSelectedFile().getAbsolutePath() + "\n"
                    + "Arquivo: " + chooser.getSelectedFile().getName());

            XmlUtil xmlU = new XmlUtil();
            String message;
            boolean importResult = xmlU.importarArquivoContatos(chooser.getSelectedFile().getAbsolutePath());
            if (importResult) {
                message = "Importação concluída.\n"
                        + "Total de registros importados:" + xmlU.getTotalImportados();
                atualizaGrade();
                
                // o q fazer com os dados...
            } else {
                message = "Importação com " +  xmlU.getErrosImportacao() + "erros.\n"  
                        + "Verifique o console.";
                atualizaGrade();
            }
            JOptionPane.showMessageDialog(rootPane, message);
        }

IV) Teste o programa e tente importar o arquivo XML correto novamente, após importar o arquivo  o programa deve exibir uma mensagem de importação concluída e a quantidade de registros incluídos, e a grade e o label Total devem ser atualizados.
Figura 25 - Importação do arquivo XML correto p/ o Myqsl.
Concluído o processo é informado ao usuário quantos registros foram importados



E assim podemos importar e exportar arquivos XML do e para o Mysql, 
Ficando para  implementar as validações num proximo post.
Sugestão , antes de importar os dados p/ o banco , mostre-os p/ o usuário e você pode destacá-los usando uma técnica bem simples que expliquei nesse Tutorial colorir linhas de um jTable
Pois o objetivo aqui foi atendido.


10) Configurando um Splash Screen

Ou seja uma tela de inicialização para o programa. Basicamente ela aparece antes da JVM carregar as classes necessárias para execução e some quando o  método main é chamado.Existem vários exemplos na internet de como fazer, até no Wiki do NB e no site da Oracle-SplashSrceen
Vou fazer um simples : 

I) Crie uma imagem para o Splash, com o espaço embaixo e coloque essa imagem dentro de um novo package (splash)

Figura 26 - meu SplashScreen
Figura 27 - visão atual do projeto

II) edite o arquivo manifest.mf (ctrl+2 Files) e adicione a entrada p/ o Splash

Manifest-Version: 1.0
X-COMMENT: Main-Class will be added automatically by build
SplashScreen-Image: splash/splash_mcezzare_JSNBTut.png

III) Execute Clean and Build:
Figura 28 - Limpando e preparando o projeto p/ distribuição

Observe o Output
..
ant -f /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07 clean jar
init:
deps-clean:
Updating property file: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/built-clean.properties
Deleting directory /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build
clean:
init:
deps-jar:
Created dir: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build
Updating property file: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/built-jar.properties
Created dir: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/classes
Created dir: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/empty
Created dir: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/generated-sources/ap-source-output
Compiling 12 source files to /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/classes
..
Copying 14 files to /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/classes
compile:
Created dir: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist
Copying 1 file to /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build
Created dir: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/classes/META-INF
Copying 1 file to /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/build/classes/META-INF
Copy libraries to /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist/lib.
Building jar: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist/SpringLesson07.jar
To run this application from the command line without Ant, try:
java -jar "/Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist/SpringLesson07.jar"

IV) Teste o programa conforme informado na ultima linha
Abra um terminal (windão:Start > Run, digite cmd) e execute o comando acertando a pasta onde está seu SpringLesson07.jar:
java -jar "/Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist/SpringLesson07.jar"


Figura 29 - programa sendo iniciado pelo terminal (ou seja fora do NB) e mostrando o splashScreen


11) Rodando o Programa em outras máquinas que tenham JVM instalado

I) localize a pasta dist
Ao executar o Build acima, o NB colocou tudo o que precisa p/ o programa rodar em outro ambiente.
dentro do projeto foi criada uma pasta dist (de distribution ) 
Figura 30 - Aonde está meu projeto p/ distribuição ? e as libs ?? 

II) Aí basta copiar  essa pasta, e distribuir isso na rede onde será usado : (toda vez que usamos o Build, essa pasta é removida e criada novamente)

Figura 31 - Comprima a pasta dist e pode distribuir o programa. Mas ... e as customizações ? ainda tem alguma ??

III) E coloque o zip em algum lugar da rede, descompactando e dando um nome amigável, tipo MeusContatos-1.0, e os usuários podem clicar direto no arquivo.jar que já abrirá o programa.

12) Customizando o Build

Costumo iniciar os programas com scripts. Porquê ?
1) p/ poder solucionar erros mais facilmente
2) p/ gerar um arquivo de log
E não quero ter que toda vez que preparar o projeto p/ distribuição ter que manualmente colocar os arquivos de Script (Windows:.bat, *nix.sh, OSX.command) mais o arquivo LEIAME.txt , então ao usar o Build Project, o NB copie os arquivos para a mesma pasta do programa : 

I) vamos criar uma pasta scripts dentro da pasta src
Figura 32 - criando uma pasta que conterá os arquivos para serem incluídos na pasta do programa, após a compilação (build)

Figura 33 - Pasta scripts : .bat.sh, command, arquivos txt

II) Agora vamos criar os arquivos dentro dessa pasta: 
vou criar 5 arquivos 
LEIAME.txt com as info de execução do programa
.bat p/ quem usa o windão
.sh p/ quem usa linux,OSX,*nix
.sql p/ rodar o banco de dados em caso de nova distribuição e p/ enriquecer o tutorial.
e como esses arquivos são fornecidos com o projeto final no fim da lição,  veja a tela abaixo com todos os arquivos abertos : 
Figura 34 - visão geral da Pasta scripts : .bat.sh, command, arquivos txt, sql 

Figura 35 - Pasta scripts : .bat.sh, command, arquivos txt, dentro do projeto NB


III) Localize pela aba Files o arquivo build.xml
Figura 36 - localize o arquivo build.xml

a) Localize o target -post-compile, e clique duas vezes nele, embaixo dos comentarios vamos adicionar o seguinte :
    <target name="-post-compile">
        <!-- Empty placeholder for easier customization. -->
        <!-- You can override this target in the ../build.xml file. -->
        <mkdir dir="./dist/Db" />
        <copy todir="./dist/Db">
             <fileset dir="./src/scripts/">
                  <include name="**/*.sql" />  
                </fileset>
        </copy>
        
        <copy todir="./dist">
             <fileset dir="./src/scripts/">
                  <include name="**/*.bat" />  
                  <include name="**/*.sh" />  
                  <include name="**/*.txt" />  
                </fileset>
        </copy>
<!--   nao consegui apagar o arquivo README.TXT
<delete quiet="false">
            <fileset dir="./dist">
                <include name="**/README.TXT" />
            </fileset>
        </delete>-->

    </target>

b) Salve. Localize o target -clean mais acima , e Run Target
Figura 37 - Executando a chamdas a ant de dentro do NB


c) Localize o target -post-compile, e 
Figura 38 - testando o target post-compile

d) Olhe a aba Output:
Figura 39 - veja se os arquivos foram copiados

ant -f /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07 -post-compile
Created dir: /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist/Db
Copying 1 file to /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist/Db
Copying 5 files to /Users/surfer/NetBeansProjects/_BLOG/SpringLesson07/dist
BUILD SUCCESSFUL (total time: 0 seconds)

e) Confira se os arquivos foram copiados 
Figura 40 - conferindo se os arquivos foram copiados


f) Execute Clean and Build:
Figura 41 - como São Tomé, execute o build pelo NB e veja  se ainda compila tudo certinho..

e verifique se todos os arquivos estão na pasta dist
Figura 42 - veja se os arquivos foram copiados pelo build do projeto

E está pronta a nossa aplicação exemplo.
Figura 43- visão final do projeto


Download do projeto NB
SpringLesson07-final

Experimente executar o Profile e compare os resultados.

E assim concluímos nossa lição 07.

Resumo : o que foi feito ?
- Implementamos um CRUD completo em Java   utilizando Swing e  Spring
- Implementamos um validador com org.springframework.validation.Validator
- Implementamos um Tema
- Vimos como importar /exportar arquivos XML usando XStream
- Aprendemos a usar Anotações
- Implementamos um SplashScreen
- Aprendemos a distribuir nossa aplicação e customizar o build do projeto no build.xml
- Vimos um pouco de como o NB gera os códigos (que são protegidos no ambiente de código, mas todas as suas propriedades podem ser alterados pelo GUI)
- Vimos mais facilidades do NB


Existem centenas de programas gerenciadores de contatos hoje em dia. O objetivo dessas lições não foi fazer um Software de Agenda. E sim  mostrar algumas técnicas p/ implementar um CRUD em Java, utilizando como ferramenta o NetBeans, aproveitando recursos do framework Spring, manipular arquivos XML e pode ser uma base para quem está começando, ou usa o eclipse e não migrou ainda para o NB :) hehe

Tem algumas coisas que são comuns aos sistemas e espero ter demonstrado nesses posts um jeito simples de resolver algumas delas.

no próximo post. WebApps e Struts..

Dúvidas, Contribuições, Sugestões? Poste..
Grato,
Até a próxima.