lunes, 24 de septiembre de 2012

SpringMVC + JQuery Datatables

Datatables es uno de los plugins de JQuery por excelencia a la hora de mostrar un listado de valores en una tabla. Soporta ordenación por columnas, paginación, filtrado de datos, etc... Y es muy flexible a la hora de ser alimentado de datos, puede hacerse por Html, Ajax, JavaScript Array, Desdel servidor Ajax+Json, etc....

Desde luego para kien empiece en esto lo mejor q puede hacer antes de nada es leer la documentación tanto de jquery como de datatables para empaparse bien del funcionamiento de la API.

Mezclar dicha API con nuestro proyecto Spring en principio no tiene mayor problema (una vez q se ha estudiado su funcionamiento y lo que se quiere lograr), dicho a lo rápido, en nuestro proyecto añadimos las dependencias relativas a JSON; en el jsp hacemos referencia a los archivos .js y .css q nos hemos descargado o bien apuntamos a su URL si están onLine; y finalmente en nuestra clase controller añadimos la etiqueta @ResponseBody en el método que retornará la colección de datos a través de JSON.

¿Que es JSON?, cuando usamos AJAX javascript llama al servidor y el servidor devuelve un XML con los datos a pintar. Si en lugar de eso, devolvemos un JSONObject, en javascript con evaluate() tienes un objeto y podemos usarlo sin necesidad de parseo.

Dicho esto y sin más preludios pasamos a ver las partes clave de código:

1º  Creamos un proyecto web:
$ mvn archetype:create -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=org.nak.testJQuery -DartifactId=testJQuery

En el pom.xml  me decanté por las librerías de google para json, llamadas gson... Como todo, esto q propongo se puede mejorar, usando las librerías jackson y con algunas anotaciones quizás se pueda hacer algo más elegante ya que construyen el JSONObject de forma autmática, pero por desgracia, por la documentación y blogs que he visto no he terminado de controlar su funcionamiento :), asi que optaré por construir el JSONObject de forma manual por ahora, así luce mi archivo pues:

<dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>3.8.1</version>
   <scope>test</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>3.1.2.RELEASE</version>
   <type>jar</type>
   <!-- <version>${org.springframework.version}</version> -->
   <scope>compile</scope>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>3.1.2.RELEASE</version>
   <!-- <version>${org.springframework.version}</version> -->
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
  </dependency>
  <dependency>
   <groupId>taglibs</groupId>
   <artifactId>standard</artifactId>
   <version>1.1.2</version>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.16</version>
  </dependency>
  <!-- jar propios acceso a datos y llamada ws -->
  <dependency>
   <groupId>testNak</groupId>
   <artifactId>dataTest</artifactId>
   <version>1.3</version>
  </dependency>

  <!-- del otro jar para correr el jetty -->

  <!-- Para poder usar hibernate con spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-orm</artifactId>
   <version>3.1.2.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring</artifactId>
   <version>2.5.6</version>
  </dependency>
  <!-- Hibernate framework -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate</artifactId>
   <version>3.2.7.ga</version>
  </dependency>
  <!-- Para usar anotaciones con hibernate -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-annotations</artifactId>
   <version>3.4.0.GA</version>
  </dependency>
  <!-- mysql conector -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.21</version>
  </dependency>
  <!-- Log4j y Sfl4j -->
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.5.8</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>1.5.8</version>
   <scope>runtime</scope>
  </dependency>

  <dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
   <version>2.2.2</version>
  </dependency>

 </dependencies>


Están las dependencias del proyecto web SpringMVC q estamos haciendo, las q acceden a la capa de datos, el jar q hicimos para el acceso a dichos datos, las dependencias de los log... y la última de todas es la q mencionábamos de gson.


2º De la propia web de DataTables nos podemos bajar el API con los css y javaScripts necesarios para montar el artilugio y los alojamos en una carpeta que cuelge directamente de "webapp", podrían llamarse por ejemplo "scripts" y "themes", aunke en el código q voy a poner tengan otros nombres, fruto de multiples pruebas.

Este sería el aspecto del .jsp donde cargaremos la tabla:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

        <link href="cssxample/demo_page.css" rel="stylesheet" type="text/css" />
        <link href="cssxample/demo_table.css" rel="stylesheet" type="text/css" />
        <link href="cssxample/demo_table_jui.css" rel="stylesheet" type="text/css" />
        <link href="cssxample/base/jquery-ui.css" rel="stylesheet" type="text/css" media="all" />
        <link href="cssxample/smoothness/jquery-ui-1.7.2.custom.css" rel="stylesheet" type="text/css" media="all" />
        <script src="scriptsxample/jquery.js" type="text/javascript"></script>
        <script src="scriptsxample/jquery.dataTables.min.js" type="text/javascript"></script>

 <script type="text/javascript" charset="utf-8">
 $(document).ready(function () {
  $('#myTable').dataTable(
   "bServerSide" : true,
   "sAjaxSource" : 'doajax.do',
    "bProcessing": true,
             "sPaginationType": "full_numbers",
             "bJQueryUI": true,
             "aoColumns" : [ {
              mDataProp : 'nombre'
          }, {
                mDataProp : 'rut'
          },  {
                mDataProp : 'apellidoMaterno'
          },  {
                mDataProp : 'apellidoPaterno'
          } ]

  });
 });

 </script>



<title>Insert title here</title>
</head>
<body>
 
 
 <table id="myTable">
  <thead>
   <tr>
    <th>nombre</th>
    <th>rut</th>
    <th>apellidoPaterno</th>
    <th>apellidoMaterno</th>
   </tr>
  </thead>
  <tbody />
 </table>

</body>
</html>

Todo dentro de la etiketa <head> las llamadas a los javaScripts y hojas de estilo y luego la función donde parametrizamos el dataTable:

bServerSide: Indica al plugin que hay que sacar la información del lado del servidor.
sAjaxSource: Define la URL que debe ser convocada por el plug-in para tomar datos.
bProcessing (opcional): se utiliza para mostrar el
mensaje "Processing mientras la llamada AJAX se está ejecutando.
bJQueryUI: aplicación de estilos estándar jQueryUI.
sPagination: instruir a los plug-in para generar paginación con números en lugar de dos botones de anterior, siguiente, como se genera por omisión.
aoColumns: Dado que en nuestro caso viene el nombre de los campos junto a su valor correspondiente, debemos hacer un mapeo:
 "aaData":[{"nombre":"Perico","rut":1,"apellidoMaterno":"Moya","apellidoPaterno":"Palotes"}
...]

3º La clase controller captará la llamada url "doajax.do" y retornará un String con lo necesario para cargar la tabla:
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.nak.dataTest.SpringContext;
import org.nak.dataTest.model.Persona;
import org.nak.dataTest.service.PersonaManager;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.google.gson.Gson;
import com.google.gson.JsonObject;


@Controller
public class PersonaController { 

 private static Logger logger = Logger.getLogger(PersonaController.class);

 private SpringContext context;

 private PersonaManager personaManager;

 public PersonaController() {
  super();
  context = SpringContext.getInstance();
  personaManager = (PersonaManager) context.getBean("personaManager");

 }

 @RequestMapping(value = "/doajax.do", method = RequestMethod.GET)
 protected @ResponseBody String doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  
  JQueryDataTableParamModel param = DataTablesParamUtility.getParam(request);
  
  String sEcho = param.sEcho;
  int iTotalRecords; // total number of records (unfiltered)
  int iTotalDisplayRecords; //value will be set when code filters companies by keyword
  
  iTotalRecords = personaManager.obtenerPersonas().size();
  List personas = new LinkedList();
  
  for(Persona p : personaManager.obtenerPersonas()){
   if (p.getNombre().toUpperCase().contains(param.sSearch.toUpperCase()) ||
    p.getApellidoPaterno().toUpperCase().contains(param.sSearch.toUpperCase()) ||
    p.getApellidoMaterno().toUpperCase().contains(param.sSearch.toUpperCase())){
     personas.add(p);
   }
  }
  iTotalDisplayRecords = personas.size();  // number of companies that match search criterion should be returned
  final int sortColumnIndex = param.iSortColumnIndex;
  final int sortDirection = param.sSortDirection.equals("asc") ? -1 : 1;
  
  Collections.sort(personas, new Comparator(){
   public int compare(Persona o1, Persona o2) {
    switch(sortColumnIndex){
    case 0:
     return o1.getNombre().compareTo(o2.getNombre()) * sortDirection;
    case 2:
     return o1.getApellidoPaterno().compareTo(o2.getApellidoPaterno()) * sortDirection;
    case 3:
     return o1.getApellidoMaterno().compareTo(o2.getApellidoMaterno()) * sortDirection;
    }
    return 0;
   }
  });
  
  if(personas.size()< param.iDisplayStart + param.iDisplayLength) {
   personas = personas.subList(param.iDisplayStart, personas.size());
  } else {
   personas = personas.subList(param.iDisplayStart, param.iDisplayStart + param.iDisplayLength);
  }
  
  
   JsonObject jsonResponse = new JsonObject();  
   jsonResponse.addProperty("sEcho", sEcho);
   jsonResponse.addProperty("iTotalRecords", iTotalRecords);
   jsonResponse.addProperty("iTotalDisplayRecords", iTotalDisplayRecords);   
   Gson gson = new Gson();
   jsonResponse.add("aaData", gson.toJsonTree(personas));
   
   System.out.println("Retorno:: "+jsonResponse.toString());
   return jsonResponse.toString();
 }

}

JQueryDataTableParamModel es una especie de bean que contiene las diferentes propiedades del plug-in:
public class JQueryDataTableParamModel {

 /// 
    /// Request sequence number sent by DataTable, same value must be returned in response
    ///        
    public String sEcho;

    /// 
    /// Text used for filtering
    /// 
    public String sSearch;

    /// 
    /// Number of records that should be shown in table
    /// 
    public int iDisplayLength;

    /// 
    /// First record that should be shown(used for paging)
    /// 
    public int iDisplayStart;

    /// 
    /// Number of columns in table
    /// 
    public int iColumns; 

    /// 
    /// Number of columns that are used in sorting
    /// 
    public int iSortingCols;
    
    /// 
    /// Index of the column that is used for sorting
    /// 
    public int iSortColumnIndex;
    
    /// 
    /// Sorting direction "asc" or "desc"
    /// 
    public String sSortDirection;

    /// 
    /// Comma separated list of column names
    /// 
    public String sColumns;

}

DataTablesParamUtility carga los valores que se han pedido en la request:
import javax.servlet.http.HttpServletRequest;

public class DataTablesParamUtility {

 public static JQueryDataTableParamModel getParam(HttpServletRequest request)
 {
  if(request.getParameter("sEcho")!=null && request.getParameter("sEcho")!= "")
  {
   JQueryDataTableParamModel param = new JQueryDataTableParamModel();
   param.sEcho = request.getParameter("sEcho");
   param.sSearch = request.getParameter("sSearch");
   param.sColumns = request.getParameter("sColumns");
   param.iDisplayStart = Integer.parseInt( request.getParameter("iDisplayStart") );
   param.iDisplayLength = Integer.parseInt( request.getParameter("iDisplayLength") );
   param.iColumns = Integer.parseInt( request.getParameter("iColumns") );
   param.iSortingCols = Integer.parseInt( request.getParameter("iSortingCols") );
   param.iSortColumnIndex = Integer.parseInt(request.getParameter("iSortCol_0"));
   param.sSortDirection = request.getParameter("sSortDir_0");
   return param;
  }else
   return null;
 }
}

Mientras que PersonaManager es el manejador que hicimos en el HibernateDAO y q hemos importado desdel pom en este proyecto, encargado en este caso de devolvernos una colección en forma de lista tipada.

4º Finalmente la configuracción del web.xml y el mapeador de Spring no presentan diferencias de lo que sería cualquier otro proyecto hecho con Spring Web MVC:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 id="WebApp_ID" version="2.5">
 <display-name>Archetype Created Web Application</display-name>

 <!-- utilizamos el servlet de spring MVC -->
 <servlet>
  <servlet-name>NakSpringMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <!-- declaracion de beans utilizados por spring MVC y asociacion entre vistas 
  y controladores -->
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/NakSpringMVC-servlet.xml</param-value>
 </context-param>

 <!-- Creates the Spring Container shared by all Servlets and Filters -->
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>

 <servlet-mapping>
  <servlet-name>NakSpringMVC</servlet-name>
  <url-pattern>*.do</url-pattern>
 </servlet-mapping>

 <welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
</web-app>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang"
 xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:util="http://www.springframework.org/schema/util"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
  http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

 
 <context:component-scan base-package="org.nak.testJQuery.controller" />
 
 <mvc:annotation-driven />
 
 <bean id="jspViewResolver"
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass"
   value="org.springframework.web.servlet.view.JstlView" />
  <property name="prefix" value="/WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />
 </bean>

</beans>


Este debe ser el resultado final:











Y aki adjunto el código fuente, veréis en el más jsp, librerías y controllers a parte de lo que os he comentado arriba, la razón de ello es que probé varias fórmulas para conseguir esto, todas son válidas y os pueden servir de apoyo.


lunes, 10 de septiembre de 2012

Spring 3 Web MVC Maven Project con internacionalización y Cambios de Tema CSS


Vamos a crear la capa web para un proyecto en el que vamos a reutilizar  2 jars creados anteriormente: uno consume un webService, que nos devuelve una colección de personas recogidas de una tabla; y el otro es un Hibernate Spring DAO que maneja el CRUD de esas mismas personas, asi q este proyecto solo ha de preocuparse de la parte web implementando los jars correspondientes...

Y sin más preámbulos empezamos, arquetipo maven para aplicaciones web:
$ mvn archetype:create -DgroupId=com.nak.testWeb -DartifactId=testWeb -DarchetypeArtifactId=maven-archetype-webapp

Editamos el pom.xml hasta dejarlo como sigue:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.nak.testWeb</groupId>
 <artifactId>testWeb</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>testWeb Maven Webapp</name>
 <url>http://maven.apache.org</url>
 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>3.8.1</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-core</artifactId>
   <version>3.1.2.RELEASE</version>
   <type>jar</type> 
   <!-- <version>${org.springframework.version}</version> -->
   <scope>compile</scope>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>3.1.2.RELEASE</version> 
   <!-- <version>${org.springframework.version}</version> -->
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
  </dependency>
  <dependency>
   <groupId>taglibs</groupId>
   <artifactId>standard</artifactId>
   <version>1.1.2</version>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.16</version>
  </dependency>
  <!-- jar propios acceso a datos y llamada ws -->
  <dependency>
   <groupId>testNak</groupId>
   <artifactId>dataTest</artifactId>
   <version>1.3</version>
  </dependency>
  <dependency>
   <groupId>testNak</groupId>
   <artifactId>ws-proxy</artifactId>
   <version>1.0</version>
  </dependency>
  
  <!-- del otro jar para correr el jetty -->
  
    <!-- Para poder usar hibernate con spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-orm</artifactId>
   <version>3.1.2.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring</artifactId>
   <version>2.5.6</version>
  </dependency>
  <!-- Hibernate framework -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate</artifactId>
   <version>3.2.7.ga</version>
  </dependency>
  <!-- Para usar anotaciones con hibernate -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-annotations</artifactId>
   <version>3.4.0.GA</version>
  </dependency>
  <!-- mysql conector -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.21</version>
  </dependency>
  <!-- Log4j y Sfl4j -->
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.5.8</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>1.5.8</version>
   <scope>runtime</scope>
  </dependency>
  <!--  -->
 </dependencies>
 <build>
  <finalName>testWeb</finalName>
  <plugins>
   <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.0.0.M3</version>
    <configuration>
     <webAppConfig>
      <contextPath>/${project.artifactId}</contextPath>
     </webAppConfig>
     <scanIntervalSeconds>5</scanIntervalSeconds>
     <stopPort>9966</stopPort>
     <stopKey>foo</stopKey>
     <connectors>
      <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
       <port>9080</port>
      </connector>
     </connectors>
    </configuration>
   </plugin>
   <!-- plugin de selenium -->
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>selenium-maven-plugin</artifactId>
    <executions>
     <execution>
      <phase>process-test-classes</phase>
      <goals>
       <goal>start-server</goal>
      </goals>
      <configuration>
       <background>true</background>
      </configuration>
     </execution>
    </executions>
   </plugin>
  </plugins>
 </build>
</project>


Varias cosas que decir sobre este pom:

  • De primeras todas las dependencias relativas a las librerías necesarias para un proyecto web de estas caracterírticas.
  • Luego viene la llamada de de los 2 jars que programamos nosotr@s, el testNak de hibernate annotations del cual he subido 3 versiones diferentes, de ahí el .3;
    y ws-rpoxy que fué el proyecto que hicimos para consumir el servicio web.
  • Posteriormente he incluido las dependencias de las librerías que necesita el proyecto dataTest para funcionar, en condiciones nosmales no estarían aquí si no en la carpeta "../lib/ext" del servidor web correspondiente (por ejemplo un tomcat), están añadidas en este pom.xml porque las necesita el plugin de jetty para levantar la web y  hacer pruebas en nuestro navegador.
  • El plugin Jetty nos permitirá correr nuestra aplicación sin necesidad de q instalemos servidores web en nuestra mákina de desarrollo.
  • También el pluging de Selenium para posteriores testeos de la web con firefox, no es imprescindible para este caso.
     
Listo el pom.xml, compilamos:
$ mvn -U compile

Importamos nuestro proyecto en nuestro IDE, mi caso eclipse:
 Import -> Existing Maven Project -> Etc...


Empezamos a configurar la web, en el archivo web.xml definimos quien distribuye las páginas y como mapeamos:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 id="WebApp_ID" version="2.5">
 <display-name>Archetype Created Web Application</display-name>

 <!-- utilizamos el servlet de spring MVC -->
 <servlet>
  <servlet-name>NakSpringMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <!-- declaracion de beans utilizados por spring MVC y asociacion entre vistas y controladores -->
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/NakSpringMVC-servlet.xml</param-value>
 </context-param>

 <servlet-mapping>
  <servlet-name>NakSpringMVC</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
 
 <welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
</web-app>


By mapping DispatcherServlet to /, I’m saying that it’s the default servlet and that
it’ll be responsible for handling all requests, including requests for static content.

Por alusiones lo siguiente será ver el contenido de "/testWeb/src/main/webapp/WEB-INF/NakSpringMVC-servlet.xml":
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang"
 xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:util="http://www.springframework.org/schema/util"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
  http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

 <context:annotation-config />
 <context:component-scan base-package="controller" />

 <!-- the mvc resources tag does the themes magic -->
 <mvc:resources mapping="/themes/**" location="/themes/" />
 <!-- also add the following beans to get rid of some exceptions -->
 <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

 <bean id="jspViewResolver"
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass"
   value="org.springframework.web.servlet.view.JstlView" />
  <property name="prefix" value="/WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />
 </bean>

 <bean id="messageSource"
  class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  <property name="basename" value="classpath:messages" />
  <property name="defaultEncoding" value="UTF-8" />
 </bean>

 <bean id="localeChangeInterceptor"
  class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
  <property name="paramName" value="lang" />
 </bean>

 <bean id="localeResolver"
  class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
  <property name="defaultLocale" value="en" />
 </bean>

 <bean id="themeSource"
  class="org.springframework.ui.context.support.ResourceBundleThemeSource">
  <property name="basenamePrefix" value="theme-" />
 </bean>

 <!-- Theme Change Interceptor and Resolver definition -->
 <bean id="themeChangeInterceptor"
  class="org.springframework.web.servlet.theme.ThemeChangeInterceptor">
  <property name="paramName" value="theme" />
 </bean>
 <bean id="themeResolver"
  class="org.springframework.web.servlet.theme.CookieThemeResolver">

 </bean>

 <bean id="handlerMapping"
  class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
  <property name="interceptors">
   <list>
    <ref bean="localeChangeInterceptor" />
    <ref bean="themeChangeInterceptor" />
   </list>
  </property>
 </bean>

</beans>


Notice que al estar jugando con anotaciones también simplificamos contenido en este xml, aun así varias cosas de que hablar:

  • Las 2 primeras etiquetas hacen referencia a que vamos a usar anotaciones y el lugar donde se alojan las clases de control.
  • Luego 2 etiquetas donde se describen donde se alojan los .css de cada thema y un manejador de excepciones.
  • El bean de JspViewResolver indicando donde están alojadas las paǵinas y cual es la extensión que ha de añadirse. 
  • Finalmente las etiquetas relativas al manejo de archivos de internacionalización y temas css, indicando sus beans interceptores de cambio, temas y locale por default y definiendo manejadores que capten cambios en ellos.

Los archivos.properties de internacionalización y temas (así como el log4j) van ubicados en "/src/main/resources" , mientras que los diferentes css irán en una carpeta llamada "themes" dentro de "webapp" , muestro un pekeño gráfico donde se ve su estructura:

El contenido por ejemplo de "/testWeb/src/main/resources/theme-black.properties" sería:
css=themes/black.css


Este sería el de "/testWeb/src/main/webapp/themes/black.css":
body {
    background-color: #888;
    color: white;
}


Y el de "/testWeb/src/main/resources/messages_es.properties":
label.firstname=Nombre Pila
label.lastname=Apellido Paterno
label.thirtname=Apellido Materno
label.email=Email
label.rut=ManId
label.telephone=Telefono
label.addcontact=Agregar
label.listado=Ver Listado DAO
label.listadows=Ver Listado WS

label.menu=Menu
label.title=Persona Manager Web
label.footer=© http://naknatxo.blogspot.com.es/search/label/Indice


Llendo ya a la navegación web, el index.xml podría empezar por ejemplo de la siguiente manera:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
 <body onLoad="location.href='personas'">
</body>
</html>


The Spring controller class recogerá nuestras peticiones de URL, en esta clase creamos los objetos necesarios para cada caso y recogeremos los paramétros necesarios para resolver los formularios:
package controller;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;

import org.apache.log4j.Logger;
import org.nak.dataTest.SpringContext;
import org.nak.dataTest.model.Persona;
import org.nak.dataTest.service.PersonaManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
//import org.springframework.web.servlet.mvc.Controller;
//import org.springframework.web.bind.annotation.

import com.nak.wsdltest.ws.UnWebService;
import com.nak.wsdltest.ws.UnWebServiceService;

@Controller
public class PersonaController {  //implements Controller

 private static Logger logger = Logger.getLogger(PersonaController.class);
 
 private SpringContext context;
 
 private PersonaManager personaManager;
 
 public PersonaController() {
  super();
  context = SpringContext.getInstance();
  personaManager = (PersonaManager) context.getBean("personaManager");

 } 

 @RequestMapping("/personas")
 public String listPersonas(Map map) {

  
  logger.debug("Carga ");
  map.put("personaForm", new Persona());
  map.put("personaList", personaManager.obtenerPersonas());

  return "personas"; 
  
  
 }
 
 @RequestMapping("/personasWS")
 public String listPersonasWS(Map map) {

  System.out.println("personasWS");
  UnWebServiceService unWebServiceService;
  UnWebService unWebService;
  List personas = null;
  
  try {
   unWebServiceService = new UnWebServiceService(
           new URL("http://localhost:8080/UnWebService?wsdl"),    // URL real del web service.
           new QName("http://ws.wsdlTest.nak.com/",           // copiado del código generado por wsimport  
                 "UnWebServiceService"));
     unWebService = unWebServiceService.getUnWebServicePort();
     personas = unWebService.listarPenya();
     
  } catch (MalformedURLException e) {
   e.printStackTrace();
  }
  
   
  
  logger.debug("Carga WS");
  
  map.put("personaList", personas);

  return "personasWS";  
 }
 
 public ModelAndView handleRequest(HttpServletRequest arg0,
   HttpServletResponse arg1) throws Exception {

  return new ModelAndView("personas", "personaList", personaManager.obtenerPersonas());
 }
 
 @RequestMapping("/delete/{rut}")
    public String deleteContact(@PathVariable("rut")
    Integer personaId) {
 
  personaManager.borrarPersona(personaId);
 
  return "redirect:/personas";
    }

 @RequestMapping("/adition")
 public String goForm(Map map)  {
  map.put("personaForm", new Persona());
  logger.debug("Addition");
  return "addPersona";
 } 
 
 
  @RequestMapping(value = "/add", method = RequestMethod.POST)
 public String addContact(@ModelAttribute("personaForm") Persona personaForm, BindingResult result) {
  
  List personas = new ArrayList();
  personas.add(personaForm);
  personaManager.insertarPersonas(personas);
  
  return "redirect:/personas";
 } 
 
}


Con @Controller decimos q es una clase de control de spring, @RequestMapping recepcionará las url de los "href" de los jsp y @ModelAttribute Coge el objeto bean de un formulario.

Y los jsp, "/testWeb/src/main/webapp/WEB-INF/jsp/personas.jsp":
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

<link rel="stylesheet" href="<spring:theme code="css"/>" type="text/css" />
<title>Spring 3 MVC - Persona Manager</title>

<style>
body {
 font-family: sans-serif, Arial;
}
</style>
</head>
<body>
 <h2>
  <spring:message code="label.title" />
 </h2>

 <h3>Contacts</h3>

 <br>

 <table>
  <tr>
     
   <td align="left">
    <span style="float: left"> 
     <a href="?theme=default">def</a> | 
     <a href="?theme=black">blk</a> | 
     <a href="?theme=blue">blu</a>
    </span>
   </td>
   
   
   <td align="right">
    <span style="float: right"> 
     <a href="?lang=en">en</a> | <a href="?lang=es">es</a>
    </span>
   </td>
  </tr> 
  <tr><td>
   <c:if test="${!empty personaList}">
    <table class="data" border="1">
     <tr>
      <th><spring:message code="label.rut" /></th>
      <th><spring:message code="label.firstname" /></th>
      <th><spring:message code="label.lastname" /></th>
      <th><spring:message code="label.thirtname" /></th>
      <th> </th>
     </tr>
     <c:forEach items="${personaList}" var="persona">
      <tr>
       <td>${persona.rut}</td>
       <td>${persona.nombre}</td>
       <td>${persona.apellidoPaterno}</td>
       <td>${persona.apellidoMaterno}</td>
       <td><a href="delete/${persona.rut}">delete</a></td>

      </tr>
     </c:forEach>
    </table>
   </c:if>
  </td></tr>
 </table>

 <a href="adition"><spring:message code="label.addcontact" /></a>
 <a href="personasWS"><spring:message code="label.listadows" /></a>
 <br>
 <br>
 <spring:message code="label.footer" />
</body>
</html>

 


"/testWeb/src/main/webapp/WEB-INF/jsp/addPersona.jsp":
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>

<html>
<head>
<title>Insert title here</title>
</head>
<body>
 
<form:form method="POST" action="add.html" commandName="personaForm">
  <form:errors path="*"></form:errors>
  <table>
   <tr>
    <td><spring:message code="label.rut"/></td>
    <td><form:input path="rut" /></td>
    
   </tr>
   <tr>
    <td><spring:message code="label.firstname"/></td>
    <td><form:input path="nombre" /></td>
    
   </tr>
   <tr>
    <td><spring:message code="label.lastname"/></td>
    <td><form:input path="apellidoPaterno" /></td>
   
   </tr>
   <tr>
    <td><spring:message code="label.thirtname"/></td>
    <td><form:input path="apellidoMaterno" /></td>
   </tr>
   <tr>
    <td colspan="3"><input type="submit" title="Añadir" value="dale"/></td>
   </tr>
  </table>
 </form:form>

</body>
</html>


Este jsp lee los datos consumiendo nuestro web service, "/testWeb/src/main/webapp/WEB-INF/jsp/personasWS.jsp":
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>

<link rel="stylesheet" href="<spring:theme code="css"/>" type="text/css" />

<title>Spring 3 MVC - Persona Manager</title>
</head>
<body>
<c:if test="${!empty personaList}">
    <table class="data" border="1">
     <tr>
      <th><spring:message code="label.rut" /></th>
      <th><spring:message code="label.firstname" /></th>
      <th><spring:message code="label.lastname" /></th>
      <th><spring:message code="label.thirtname" /></th> 
      <th> </th>
     </tr>
     <c:forEach items="${personaList}" var="persona">
      <tr>
       <td>${persona.rut}</td>
       <td>${persona.nombre}</td>
       <td>${persona.apellidoPaterno}</td>
       <td>${persona.apellidoMaterno}</td>
       <td><a href="delete/${persona.rut}">delete</a></td>

      </tr>
     </c:forEach>
    </table>
   </c:if>
 
   <a href="adition"><spring:message code="label.addcontact" /></a>
   <a href="personas"><spring:message code="label.listado" /></a>
   <br>
 <spring:message code="label.footer" />
    
 <br>
   
</body>
</html>

Ciertamente la tabla del jsp es exactamente la misma que en "personas.jsp", la diferencia la marca la clase spring controller "PersonaController.java" en los métodos que recogen los datos.

Hecho todo esto compilamos:
$ mvn -U clean compile

Y ponmeos en marcha el jetty:
$ mvn jetty:run


Si abrimos el navegador y ponemos "http://localhost:9080/testWeb/index.jsp", este debe ser el resultado:

Inicialmente se abriría "personas.jsp" por el forward que hicimos en el index, en el podremos cambiar los css (def|blk|bku), el locale (en|es), borrar (por ejemplo el 78), agregar o ver el jsp q lee del WS, si pinchamos por ejemplo en agregar:



Y el jsp que recoge los datos tras consumir un WS:


Y eso es todo, aki dejo el código, en el encontrareis mas clases y código xml comentado de las que no he hablado, la razón de ello es porque empece programando la capa de una manera y al final me decanté por la que os he contado más enfocada a las anotaciones, no obstante decidí no borrar nada de ello porque puede ser interesante observarlo para entender mejor la evolución de este framework.

sábado, 8 de septiembre de 2012

Model Project con Maven Spring Hibernate Annotation

Vamos a crear la capa de acceso a datos de un proyecto que posteriormente será usado por otro proyecto web q sea capaz de implementar todas las funcionalidades CRUD del mismo.
Para ello vamos a empezar por la parte de base de datos, en principio podríamos generar todo el código java y luego por medio de un mandato se crearían los esquemas y tablas correspondientes, peeeero para esta primera parte iremos paso a paso y dejaremos esa fase para post más avanzados, sobre todo porque todavía tengo q estudiar yo esa característica, si alguien quiere comentar y hablar de ello pues estupendo, vamos a ello!, abrimos la terminal y accedemos a la base de datos:

$ mysql -u root -p

Aprovechamos la coyuntura para ver un par de temas sencillos en BBDD MySQL, a continuación pongo una serie de comandos con los siguientes objetivos: crear una base de datos específica para el proyecto, entrar en ella, crear la tabla base, crear un usuario que será quien nacceda a esa base de datos ya que no quiero que el programa vaya con root por razones obvias (si yo soy un DBA y he de crear un esquema o una base de datos para un programador, le daré un usuario específico para atacar esa base de datos, no la password de root):


Create Database Test;

Use Test;

Create Table (
 rut    int, 
 nombre           varchar(100),
 apellido_paterno  varchar(100),
 apellido_materno  varchar(100)
);

GRANT ALL PRIVILEGES ON Test.* TO 'usuario'@'localhost' IDENTIFIED BY 'user' WITH GRANT OPTION;


Con esto ya tenemos el modelo de datos preparado, procedemos a crear el arquetipo:

$ mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=org.nak.dataTest -DartifactId=dataTest

Entramos en la raiz del proyecto "cd dataTest" y pasamos a editar el pom.xml para decirle cuales son todas las dependencias q vamos a necesitar: spring, hibernate, mysql, log4j y sfl4j. Debajo de la etiketa "properties":
<dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.10</version>
   <scope>test</scope>
  </dependency>
  <!-- Para poder usar hibernate con spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-orm</artifactId>
   <version>3.1.2.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring</artifactId>
   <version>2.5.6</version>
  </dependency>
  <!-- Hibernate framework -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate</artifactId>
   <version>3.2.7.ga</version>
  </dependency>
  <!-- Para usar anotaciones con hibernate -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-annotations</artifactId>
   <version>3.4.0.GA</version>
  </dependency>

  <!-- mysql conector -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.21</version>
  </dependency>
  <!-- Log4j y Sfl4j -->
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.5.8</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>1.5.8</version>
   <scope>runtime</scope>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.16</version>
  </dependency>
 </dependencies>

Para saber la ubicación, artefacto y versión de una dependencia del repositorio de maven vamos a http://mvnrepository.com/. Compilamos:
 $ mvn -U clean compile

Los xml descriptores de Spring, Hibernate y log4j van a ir en una carpeta llamada resources, el arquetipo no nos ha creado dicha carpeta, asi q la haremos nostr@s:
$ mkdir src/main/resources

Ahora llega el momento de ir a nuestro IDE, there's a goal de maven que sirve para preparar los descriptores del proyecto y q sea abiero por dicho IDE "mvn eclipse:clean eclipse:eclipse", pero en versiones superiores al Indigo nos encontramos que cuando importamos el proyecto nos sorprende con un error de este tipo:

An internal error occurred during: "Importing Maven projects".
Unsupported IClasspathEntry kind=4


Mmmm, la solución de este error es tan simple como ahorrarse este último goal de maven antes de importar el proyecto, vamos que no es necesario, máxime de la forma en la que estamos trabajando, en la que el eclipse hace la función poco más que de un bloc de notas, ya que los test, compilaciones y enpaquetado final los haremos desde la konsola. Asi pues tras compilar ya importamos directamente el proyecto, akí dejo el link de la página de maven al respecto para kien aun no tenga el plugin del IDE a punto. Este es el aspecto final de la estructura de clases y pakete q veremos en el Project Explorer:




Pasamos pues a detallar cada uno de los componentes, empezamos por el bean "Persona.java", en principio todas sus anotaciones son facilmente entendibles:

package org.nak.dataTest.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "personas")
public class Persona implements Serializable {

 @Id
 @Column(name = "rut")
 private int rut = 0;
 @Column(name = "nombre")
 private String nombre = null;
 @Column(name = "apellido_paterno")
 private String apellidoPaterno = null;
 @Column(name = "apellido_materno")
 private String apellidoMaterno = null;

 public Persona(int rut, String nombre, String apellidoPaterno,
   String apellidoMaterno) {
  this.rut = rut;
  this.apellidoMaterno = apellidoMaterno;
  this.apellidoPaterno = apellidoPaterno;
  this.nombre = nombre;
 }

 public Persona() {
  super();
 }

 public int getRut() {
  return rut;
 }

 public void setRut(int rut) {
  this.rut = rut;
 }

 public String getNombre() {
  return nombre;
 }

 public void setNombre(String nombre) {
  this.nombre = nombre;
 }

 public String getApellidoPaterno() {
  return apellidoPaterno;
 }

 public void setApellidoPaterno(String apellidoPaterno) {
  this.apellidoPaterno = apellidoPaterno;
 }

 public String getApellidoMaterno() {
  return apellidoMaterno;
 }

 public void setApellidoMaterno(String apellidoMaterno) {
  this.apellidoMaterno = apellidoMaterno;
 }

 @Override
 public String toString() {
  return this.nombre + " " + this.apellidoPaterno + " "
    + this.apellidoMaterno + " (" + this.rut + ")";
 }
 
 @Override
 public int hashCode() {
  return this.rut;
 }
 
 @Override
 public boolean equals(Object obj) {
  boolean result = false;
  if (obj != null && getClass() == obj.getClass()) {
   final Persona other = (Persona) obj;
   if (this.rut == other.rut) {
    result = true;
   }
  }
  return result;
 }

}


La parte DAO, primero la interface donde definimos los métodos CRUD q vamos a usar y posteriormente la clases que implementará está interface y hará la operaciones propias de hibernate:

package org.nak.dataTest.dao;

import java.util.List;

import org.nak.dataTest.model.Persona;

public interface PersonaDAO {

 public void saveOrUpdate(Persona persona);

 public void delete(Persona persona);

 public void find(int rut);

 public List findAll();
 
 public void remove(Integer rut);

}

package org.nak.dataTest.hibernate;

import java.util.List;

import org.hibernate.SessionFactory;
import org.nak.dataTest.dao.PersonaDAO;
import org.nak.dataTest.model.Persona;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Service;

@Service(value="personaDAO")
public class HibernatePersonaDao  extends HibernateDaoSupport implements PersonaDAO{

 @Autowired
 public HibernatePersonaDao(@Qualifier("sessionFactory") SessionFactory sessionFactory) {
  this.setSessionFactory(sessionFactory);
 }
 
 public void saveOrUpdate(Persona persona) {
  this.getHibernateTemplate().saveOrUpdate(persona);
 }

 public void delete(Persona persona) {
  this.getHibernateTemplate().delete(persona);
 }

 public void find(int rut) {
  this.getHibernateTemplate().load(Persona.class, rut);
 }

 public List findAll() {
  return this.getHibernateTemplate().find("from Persona");
 }

 public void remove(Integer rut) {
  Persona persona = (Persona)this.getHibernateTemplate().load(Persona.class, rut); 
  if (persona != null){
   this.getHibernateTemplate().delete(persona);
  }
 }

}

Well, hasta aki el que piense que estoy copiando vilmente las indicaciones de esta página tiene toda la razón, pero hay ciertos matices que difieren (especialmente en los xml de configuración) y esa es la razón por la que cuento el proyecto a mi manera, en cualquier caso cito dicha web de forma literal para explicar algunas de las anotaciones anteriormente vistas:
" @Service le dice a Spring que queremos darle un nombre a esta clase para que sea instanciada automáticamente por el framework cada vez que usemos el identificador "personaDAO" ya seaen una inyección de dependencia o en una llamada explicita acontextoSpring.getbean("personaDAO"). Para quienes tengan experiencia previa con Spring elresumen es que estamos reemplazando lo siguiente:
<bean name="personaDAO" class="cl.ariel.ejemplo.dao.HibernatePersonaDao">
<property name="sessionFactory" ref="sessionFactory" /></bean>

En el archivo applicationContext.xml por una anotación. Por otro lado, @Autowired nos permiteindicarle a Spring que el constructor que estamos declarando requiere de una inyección dedependencias, en este caso sessionFactory. Como sabe Spring donde inyectar el objeto y queponer ahí? Pues bien, la anotación @Qualifier precede al parámetro del constructor y con eso indicamos donde debe ser inyectada la dependencia, mientras que el valor de la anotación indicacomo se llama el objeto que Spring debe inyecta.
"
Sigamos, dentro de lo que sería un proyecto MVC, podríamos decir q tenemos lista la capa del Model (evidentemente una muy básica), pasamos a preparar la Capa controladora, quien maneja la lógica de nuestro negocio, la capa de Vista queda emplazada a otro proyecto diferente al que importaremos como librería el .jar resultante aki, pasamos entonces a definir la interface y clase controladora:
package org.nak.dataTest.service;

import java.util.List;

import org.nak.dataTest.model.Persona;

public interface PersonaManager {

 public void insertarPersonas(List personas);
 public List obtenerPersonas();
 public void borrarPersonas(List personas);
 public void borrarPersona(Integer rut);
 
}

package org.nak.dataTest.service;

import java.util.List;

import org.nak.dataTest.dao.PersonaDAO;
import org.nak.dataTest.model.Persona;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service(value="personaManager")
public class PersonaManagerImpl implements PersonaManager{

 @Resource(name="personaDAO")
 private PersonaDAO personaDAO = null;
 
 public PersonaDAO getPersonaDAO() {
  return personaDAO;
 }

 public void setPersonaDAO(PersonaDAO personaDAO) {
  this.personaDAO = personaDAO;
 }

 @Transactional(propagation = Propagation.REQUIRED)
 public void insertarPersonas(List personas) {
  
  if (personas != null){
   for (Persona persona:personas){
    this.personaDAO.saveOrUpdate(persona);
   }
  }
  
 }

 public List obtenerPersonas() {
  return this.personaDAO.findAll();
 }

 @Transactional(propagation = Propagation.REQUIRED)
 public void borrarPersonas(List personas) {
  if (personas != null){
   for (Persona persona:personas){
    this.personaDAO.delete(persona);
   }
  }
  
 }

 public void borrarPersona(Integer rut) {
  this.personaDAO.remove(rut);
 }

}

Y como soy un perla, vuelvo a recurrir a Ariel para hablar de las anotaciones nuevas :) :
 " @Resource le dice a Spring que debe buscar el recurso"personaDAO" e inyectarlo a mi objeto; es en la practica un equivalente de @Autowired y@Qualifier pero en una sola línea. La otra anotación que encontramos es @Transactional y es laque nos permite definir un método o una clase que agrupa varias operaciones que deben ser commiteadas o rollbackeadas en conjunto
"

Archivos de Configuración:

 Dentro de la carpeta "resources" van los archivos de configuración: el applicationContext.xml de Spring (tamibén llamado spring-context); hibernate.cfg.xml, que en este caso uso para mapear las entidades en el session factory; log4j y database .properties para configurar la salida del log y los parámetros de conexión a la base de datos respectivamente, aunke finalmente como podréis var a continuación, opté por hardcodear esto último en el dataSource.
Paso pues a mostrar primero el aspecto completo de mi "applicationContext.xml":
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

 <!-- Declaramos el uso de anotaciones para Spring -->
 <context:annotation-config />
 <!-- Indicamos que clases deben ser leidas por Spring para buscar recursos -->
 <context:component-scan base-package="org.nak.dataTest" />

 <bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="username" value="usuario" />
  <property name="password" value="user" />
  <property name="url" value="jdbc:mysql://localhost:3306/test" />
  <property name="driverClassName" value="com.mysql.jdbc.Driver" /> 
 </bean>

 <bean id="sessionFactory"
  class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="configLocation" value="hibernate.cfg.xml" />
  <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
  <!-- /WEB-INF/ <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/> -->
  <property name="hibernateProperties">
   <props>
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
    <prop key="show_sql">true</prop>
   </props>
  </property>
 </bean> 
 
    <!-- Soporte para transacciones via anotaciones --> 
    <tx:annotation-driven />
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 
   
</beans>

 Tanto el xml como los comentarios añadidos en el mismo hablan por si solos, es destacable la cantidad de código ahorrado gracias a las anotaciones.

 En "hibernate.cfg.xml" simplemente mapeamos las entidades:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" 
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 <hibernate-configuration>
  <session-factory>
   
    <!-- ENTIDADES -->  
    <mapping class="org.nak.dataTest.model.Persona"/>
    
        </session-factory>
 </hibernate-configuration>


 La clase SpringContext nos sirve para acceder a los beans del applicationContext desdel código, creo que debe haber alguna forma mejor de hacer esta parte, de tal manera que se pueda invocar el xml por anotación, lo dejo en el aire hasta que tenga tiempo de mirarlo, invito a q alguien diga como hacerlo, por el momento accederemos usando la clase:
package org.nak.dataTest;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringContext {
 
 private static ApplicationContext context;
 private static SpringContext instance;
 private final static String CONFIG_FILE = "applicationContext.xml";
 
 public static SpringContext getInstance() {
  if (null == instance) {
   instance = new SpringContext();
   context = new ClassPathXmlApplicationContext(CONFIG_FILE);
  }
  return instance;
 }
 
 public Object getBean(String bean) {
  return context.getBean(bean);
 }

}

Y ya estaría, hacemos una clase de testeo para probar q todo funciona:

package org.nak.dataTest;

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.nak.dataTest.dao.PersonaDAO;
import org.nak.dataTest.model.Persona;
import org.nak.dataTest.service.PersonaManager;
import org.junit.Test;

public class PersonaManagerTest {

 private static Logger logger = Logger.getLogger(PersonaManagerTest.class);
 
 @Test
 public void testInsertaPersona() throws Exception {
  logger.debug("Creando contexto spring");
  // Obtenemos el contexto Spring
  SpringContext context = SpringContext.getInstance();

  // Creo mis objetos
  Persona persona1 = new Persona(63676, "evaristo", "lpr", "jnvvhjk");
  logger.debug("persona1 = " + persona1);
  Persona persona2 = new Persona(452, "nak", "ñiak", "punk");
  logger.debug("persona2 = " + persona2);
  List personas = new ArrayList();
  personas.add(persona1);
  personas.add(persona2);
  logger.debug("personas = " + personas);
   
  // Obtengo el Manager
  PersonaManager personaManager = (PersonaManager) context.getBean("personaManager");
  // Inserto
  personaManager.insertarPersonas(personas);
  // Borro
  //personaManager.borraPersonas(personas);
  // Borro ppr Id
  //personaManager.borrarPersona(12651196);
  // Obtengo la lista le personas
  List listaPersonas = personaManager.obtenerPersonas();
  logger.debug("personas retornadas = " + listaPersonas);
  // Verifico que la persona existe
  assertTrue(listaPersonas.contains(persona1) && listaPersonas.contains(persona2));
 }
}

Si todo ha ido bien ya podemos empaquetar el proyecto y preparar el .jar en nuestro repositorio de maven para ser usado desde otros proyectos:
$ mvn -U clean package
$ mvn install:install-file -Dfile=target/dataTest-1.0-SNAPSHOT.jar -DgroupId=testNak -DartifactId=dataTest -Dversion=1.0 -Dpackaging=jar

Aki van los fuentes.

jueves, 6 de septiembre de 2012

Crear y Consumir Web Service usando Java con JAX-WS + Maven


Para esta primera entrada de java vamos a crear un Servicio Web que ataca a una base de datos y devuelve un recodset con la información de una serie de contactos almacenados en una tabla.

Para kien no sepa aun q es un Servicio Web, en pocas palabras, es una página web preparada para q sea consumida por un programa en lugar de un humano. Por ejemplo, imaginemos q keremos hacer un juego web con una liguilla de baloncesto, una especie de SuperManager donde cada jugador se hace un equipo; pues bien, la página nba.com nos poroveerá de webServices públicos donde nosotros podremos extraer las estadísticas de cada jugador y dar a cada equipo los puntos correspondientes en nuestro juego (logicamente no podríamos hacer un DAO con una select a su base de datos). Podremos concretar diciendo que un servicio web sirve para comunicar y hacer transacciones entre 2 distintas aplicaciones. De tal manera q nosotr@s primero haremos un webService como si fuesemos el programador/a de nba.com y luego lo consumiremos como un o una particular (o empresa) que quiere acceder a dichos datos.

He elegido estás tecnologías porque ultimamente uso maven para la creación y gestión de todos mis proyectos ya que hace todo con una gran limpieza y da una total independencia de los IDE; por otro lado usar JAX-WS sin ser ningún experto en SOA, simplemente porque cuando me puse a documentarme para iniciar este proyecto me dió las mejores sensaciones, el hecho de definir el endpoint en una clase Main para luego funcionar ejecutando el .jar me pareció muy interesante, eso y la simplicidad de con la que vamos a hacer todo, ya vereis :D

Creación del Servicio Web

  1. Creamos el proyecto, artefacto básico, no necesitamos más:
    $ mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.nak.wsdlTest -DartifactId=wsdlTest

  2.  Vamos a la raiz del proyecto y añadimos las dependencias necesarias en el pom.xml : junit para testeos, jaxws para crear el ws y mysql para conectar con la base de datos:
    <dependencies>
      <dependency>
       <groupid>junit</groupid>
       <artifactid>junit</artifactid>
       <version>4.10</version>
       <scope>test</scope>
      </dependency>
      <dependency>
       <groupid>com.sun.xml.ws</groupid>
       <artifactid>jaxws-rt</artifactid>
       <scope>compile</scope>
       <version>2.2.7</version>
      </dependency>
      <!-- mysql conector -->
      <dependency>
       <groupid>mysql</groupid>
       <artifactid>mysql-connector-java</artifactid>
       <version>5.1.21</version>
      </dependency>
     </dependencies>
    

  3. "mvn -U clean compile" viene bien para refrescar los cambios y que maven baje al repositorio las librerías pertinentes.
  4.  Vamos a abrir el proyecto con nuestro IDE, yo en mi caso eclipse, os recuerdo la web de maven a propósito de la integración de dicha herramienta, descarga, configuración, etc...
  5.  A continuación me dispongo a crear 4 clases: una será el POJO con las propiedades de la entidad; otra será un DAO para el acceso a datos; otra será la clase maestra del web service y el main del que hablé para definir el end point. Asi pues hacemos el bean (nombre, constructor, setters y getters):
    public class Persona implements Serializable {
     
     private int rut = 0;
     private String nombre = null;
     private String apellidoPaterno = null;
     private String apellidoMaterno = null;
     
     public Persona() {
      super();
     }
    
     public Persona(int rut, String nombre, String apellidoPaterno,
       String apellidoMaterno) {
      super();
      this.rut = rut;
      this.nombre = nombre;
      this.apellidoPaterno = apellidoPaterno;
      this.apellidoMaterno = apellidoMaterno;
     }
    
     public int getRut() {
      return rut;
     }
    
     public void setRut(int rut) {
      this.rut = rut;
     }
    
     public String getNombre() {
      return nombre;
     }
    
     public void setNombre(String nombre) {
      this.nombre = nombre;
     }
    
     public String getApellidoPaterno() {
      return apellidoPaterno;
     }
    
     public void setApellidoPaterno(String apellidoPaterno) {
      this.apellidoPaterno = apellidoPaterno;
     }
    
     public String getApellidoMaterno() {
      return apellidoMaterno;
     }
    
     public void setApellidoMaterno(String apellidoMaterno) {
      this.apellidoMaterno = apellidoMaterno;
     }
    
     
    }
    
    
  6.  Well, nada nuevo bajo el sol, vamos con el DAO, como veis no me estoy metiendo en jaleos de Hibernate, anotaciones y demás para hacer algo rápido y sencillo. Un DAO de toda la vida:
    package com.nak.wsdlTest.dao;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.List;
    
    import com.nak.wsdlTest.model.Persona;
    
    public class PersonaDAO {
    
     private Connection connect = null;
     private PreparedStatement preparedStatement = null;
     private ResultSet res = null;
     
     public PersonaDAO() {
      super();
     }
     
     public void conectar() throws ClassNotFoundException, SQLException{
      // This will load the MySQL driver, each DB has its own driver
           Class.forName("com.mysql.jdbc.Driver");
           // Setup the connection with the DB
           connect = DriverManager
               .getConnection("jdbc:mysql://localhost:3306/test?"
                   + "user=usuario&password=user");
           
     }
     
     public List getPersonas() throws SQLException{
      Persona per = null;
      List personas = new ArrayList();
      String sql = "select * from personas";
      preparedStatement = connect.prepareStatement(sql);
      res = preparedStatement.executeQuery();
     
      while(res.next()){
       per = new Persona(res.getInt("rut"), res.getString("nombre"), 
              res.getString("apellido_paterno"), res.getString("apellido_materno"));
       personas.add(per);
      }
      
      return personas;
     }
     
     public void cerrarConexiones() throws SQLException{
      if (res != null) res.close();
      if (preparedStatement != null) preparedStatement.close();
      if (connect != null) connect.close();
     }
    }
    

  7. Hasta acoe ningún problema, ahora viene lo bueno, creamos la clase maestra q define clases y métodos (podemos tomar como apoyo el ejemplo de una de las web del crack chuidiang):
    package com.nak.wsdlTest.ws;
    
    import java.sql.SQLException;
    import java.util.List;
    import javax.jws.WebMethod;
    import javax.jws.WebService;
    import com.nak.wsdlTest.dao.PersonaDAO;
    import com.nak.wsdlTest.model.Persona;
    
    @WebService
    public class UnWebService {
    
     @WebMethod
     public List listarPenya(){
      List personas = null;
      PersonaDAO dao = new PersonaDAO();
      try {
       dao.conectar();
       personas = dao.getPersonas();
       
      } catch (Exception e) {
       e.printStackTrace();
      } finally{
       try {
        dao.cerrarConexiones();   
                                } catch (SQLException e) {
        e.printStackTrace();
       }
      }
      
      return personas;
     }
    }
    

  8.  En la clase main definimos el endpoint:
    package com.nak.wsdlTest.ws;
    import javax.xml.ws.Endpoint;
    
    public class Main {
    
     /**
      * @param args
      */
     public static void main(String[] args) {
      
      Endpoint.publish("http://localhost:8080/UnWebService", new UnWebService());
    
     }
    }
    

  9. Con esto tenemos tendríamos todo listo para levantar nuestro wsdl en el servidor, la utilidad wsgen nos permitiría descargarnos los xsd, etc... Pero no nos va a ser necesario. Lo que vamos a hacer es epaquetar nuestro proyecto y probarlo en un navegador (podríamos correr el main ya desde el propio eclipse y ver http://localhost:8080/UnWebService?wsdl si quisieramos probar), asi pues vamos a añadir unas líneas después de las dependencias a nuestro pom.xml para definir la compilación y cual es el MainClass de nuestro proyecto:
    <build>
      <plugins>
       <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.4</version>
        <configuration>
         <archive>
          <manifest>
           <addClasspath>true</addClasspath>
           <mainClass>com.nak.wsdlTest.ws.Main</mainClass>
          </manifest>
         </archive>
        </configuration>
       </plugin>
    
      </plugins>
     </build>
    

  10. Ya tenemos el webService listo para ser desplegado, salimos del eclipse y volvemos a nuestra kerida terminal :) para empaketar el proyecto, ya sabeis, desde la raiz del mismo (donde está el pom.xml):
    $ mvn -U clean package

  11. Notice que nuestro ws ataca a una base de datos mysql y que necesitamos añadir el .jar conector a nuestro jre si no keremos q nos de de hostias cuando vayamos a consumirlo, pongo rutas completas de mi caso para q sea más comprensible:
    $ cp /home/natxo/.m2/repository/mysql/mysql-connector-java/5.1.21/mysql-connector-java-5.1.21.jar /usr/lib/jvm/jdk1.6.0_33/jre/lib/ext/
  12. Ejecutamos el .jar:
    $ java -jar target/wsdlTest-1.0-SNAPSHOT.jar
  13. Vemos como nuestro servicio corre, si ponemos http://localhost:8080/UnWebService?wsdl en nuestro navegador, debe tener un look like this:

  14. Listo, estos son los fuentes por si alguien los quiere consultar. 

Consumir el Servicio Web

Ahora viene la parte contraria, crear un proyecto que consuma un servicio web (en este caso el q acabamos de hacer).

  1. Creamos el artefacto, uno normal y corriente:
    $ mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.nak.wsTest -DartifactId=ws-proxy
  2.  La parte importante aquí es el pom, lo editamos con el vim para agregar las dependencias y plugins q vamos a usar, pongo la parte crítica y acontinuación explico:
    <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
     <build>
      <plugins>
    
       <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxws-maven-plugin</artifactId>
        <version>1.12</version>
        <executions>
         <execution>
          <goals>
           <goal>wsimport</goal>
          </goals>
         </execution>
        </executions>
        <configuration>
         <wsdlUrls>
          <!-- WS en entorno de test -->
          <wsdlUrl>http://localhost:8080/UnWebService?wsdl</wsdlUrl>
         </wsdlUrls>
         <sourceDestDir>${basedir}/src/main/java</sourceDestDir>
         <xadditionalHeaders>true</xadditionalHeaders>
        </configuration>
       </plugin>
       <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.5.1</version>
        <configuration>
         <source>1.6</source>
         <target>1.6</target>
        </configuration>
       </plugin>
      </plugins>
     </build>
     <dependencies>
      <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.10</version>
       <scope>test</scope>
      </dependency>
      <dependency>
       <groupId>com.sun.xml.ws</groupId>
       <artifactId>jaxws-rt</artifactId>
       <scope>compile</scope>
       <version>2.2.7</version>
      </dependency>
     </dependencies>
    

      Em pezando por el final, las dependencias son obvias, jaxws porque son las librerías que necesitamos para los servicios web y junit para los testeos; los testeos son muy importantes, en la parte anterior también hice aunque no hablé de ellos, en esta parte si veremos una pekeña muestra. Por otro lado tenemos los pugins, el plugin "jaxws-maven-plugin" será quien conecte con el WS y ejecutando el goal "wsimport", definiremos la URL del mismo y donde queremos que nos aloje las clases de acceso a los métodos del WS.
  3. Oki, los siguientes comandos maven leeran el pom, conectarán con el ws y generarán el código pertinente:
    mvn -U clean compile
        o
    mvn jaxws:wsimport

  4.  Ahora solo hay q crear una clase de testeo para comprobar que todo va ok:
    package com.nak.wsTest;
    
    import static org.junit.Assert.assertTrue;
    
    import java.net.URL;
    import java.util.List;
    
    import javax.xml.namespace.QName;
    
    import org.junit.Test;
    
    import com.nak.wsdltest.ws.Persona;
    import com.nak.wsdltest.ws.UnWebService;
    import com.nak.wsdltest.ws.UnWebServiceService;
    
    public class TestWS {
    
     @Test
     public void verTime() throws Exception {
      
      UnWebServiceService unWebServiceService = new UnWebServiceService(
                 new URL("http://localhost:8080/UnWebService?wsdl"),    // URL real del web service.
                 new QName("http://ws.wsdlTest.nak.com/",           // copiado del código generado por wsimport  
                       "UnWebServiceService"));
      
       UnWebService unWebService = unWebServiceService.getUnWebServicePort();
       
       List pers = unWebService.listarPenya();
       
       for(Persona p:pers){
      
        System.out.println(p.getNombre()+" "+p.getApellidoPaterno()+" "+p.getApellidoMaterno());
        
       }
       assertTrue(pers.size()>1);
      
     }
    }
    

  5. Solo falta volver a la terminar y ejecutar el goal de maven para testear:
    $ mvn -U clean test
Y ya está!! Si a ido todo ok, espero que si :) lo empaquetamos en un .jar "mvn -U clean package" para poder importarlo posteriormente en otros proyectos donde queramos listar a estos "pájaros" jeje.

Una vez tenemos el .jar creado en la carpeta ./target del proyecto lo vamos a subir a nuestro repositorio de maven:
$ cd target
$ mvn install:install-file -Dfile=ws-proxy-1.0-SNAPSHOT.jar -DgroupId=testNak -DartifactId=ws-proxy -Dversion=1.0 -Dpackaging=jar

Así pues, para invocarlo desde cualkier otro proyecto en el pom.xm:

<dependency> 
<groupId>testNak</groupId> 
<artifactId>ws-proxy</artifactId> 
<version>1.0</version> 
</dependency>

Y a correr!!