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.

1 comentario:

Rocío Supercoder dijo...

Muy buen post... aquí te dejo un ejemplo sencillo de MVC por si a alguien le intersa el tema: Configuración proyecto web MVC con Eclipse y Spring 3.2 ... Saludos!