sábado, 22 de agosto de 2009

Spring MVC Implementando Internacionalización

La internacionalización de tu aplicación consiste en hacerla accesible no solo para las personas localizadas en un país(con un determinado lenguaje, como el español para nosotros), sino tambien para aquellas personas en otros países(como Inglaterra, Francia ó Italia, que leen inglés, francés, italiano) La internacionalización consiste básicamente en que el texto que muestra tu aplicación en un determinado lenguaje pueda ser visto en otro lenguaje si cambiar nada de código, es decir si desde Italia accedo a tu aplicación entonces todo el texto tiene que estar en italiano, si desde Inglaterra accedo a tu aplicación entonces todo el texto tiene que estar en inglés. Pero, ¿como implementamos esto? ¿cuál es la mejor manera?. A veces cuando no se tiene un conocimiento profundo de la tecnología realizamos la aplicación sin tener en cuenta este punto osea implementamos todas los texto solo en nuestro lenguaje, para muestra un ejemplo:

- Si tenemos un pedaz de código en una página jsp como el siguiente:
Usuario: <input type="text" name="usuario"/>
Clave: <input type="text" name="clave"/>

-Entonces el texto(ó etiquetas) Usuario y Clave siempre van a aparecer en español para cualquier tipo de usuario(sea inglés, italiano, alemán...)

La solución está en asociar claves a las etiquetas y centralizar esta asociación en un archivo de propiedades, teniendo un archivo por cada lenguaje que desee soportar nuestra aplicación. El código jsp anterior se puede cambiar por lo siguiente:

<spring:message code="etiq.usuario"/><input type="text" name="usuario"/>
<spring:message code="etiq.clave"/><input type="text" name="clave"/>

Y el archivo de propiedades para tres idiomas sería:
1. Para soporte español(por defecto cuando el sufijo no es especificado)
mensajes.properties
etiq.titulo = Spring Framework
etiq.usuario = Usuario
etiq.clave = Clave

2. Para soporte inglés
mensajes_EN.properties
etiq.titulo = Spring Framework
etiq.usuario = User
etiq.clave = Password

3. Para soporte italiano
mensajes_IT.properties
etiq.titulo = Spring Framework
etiq.usuario = Utente
etiq.clave = Chiave

Cuando la aplicación sea invocada esta va a detectar el lenguaje del navegador y va a utilizar el archivo de propiedades apropiado.

Bueno, ahora veamos como Spring soporta todo este contexto de internacionalización. A diferencia de los post anteriores ahora vamos a agregar a la aplicación un contenedor de beans(un ApplicationContext.xml) adicionando al descriptor de despliegue lo siguiente:

<!--
Una variable de contexto que tendrá como valor el archivo de configuración del contenedor de beans.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<!--
Un listener de contexto que usará el archivo anterior para instanciar todos los beans que contiene
-->
<listener><listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class></listener>

Una vez declarado la ruta del contenedor de beans ahora lo definimos:
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!--
Declaración del bean que obtendrá el archivo y que a su vez lo hará disponible al resto de la aplicación(ya no tenemos que colocar los mensajes en duro) ahora podemos centralizar estos mensajes en un archivo mensajes.properties, este archivo debe de estar en la raiz de tu classpath, osea /WEB-INF/classes
-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename"><value>mensajes</value></property>
</bean>
</beans>

Los archivos de propiedades con los textos en cada idioma debemos de colocarlos en la raiz del classpath, es decir en /WEB-INF/classes . Esta carpeta quedaría como:

/WEB-INF/classes/mensajes.properties
/WEB-INF/classes/mensajes_EN.properties
/WEB-INF/classes/mensajes_IT.properties

Una vez configurada la aplicación procedemos a probarla con un archivo jsp:
index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><spring:message code="etiq.titulo"/></title>
</head>
<body>
<form action="/usuario/procesar.do" method="POST">
<spring:message code="etiq.usuario"/><input name="usuario" type="text"/>
<spring:message code="etiq.clave"/><input name="clave" type="text"/>
</form>
</body>
</html>

Para finalizar, este tipo de diseño simplifica el desarrollo y mantenimiento de tu aplicación, ya no tienes que estar buscando en las miles de líneas de código en tu aplicación, todos los mensajes y textos los centralizas en un archivo de mensajes y si quieres cambiar algo, pues solo lo haces allí.


Spring MVC implementando la interface Controller

En los 2 post anteriores hemos visto como podemos implementar un controlador(la C en MVC) mediante anotaciones, para aquellos a quienes no les guste este tipo de implementación pues hay una forma un poco más extensa y un tanto vieja, esta forma es mediante la implementación de la interface Controller, allí las va el código:

1. En el Front Controller(-servlet.xml) de tu aplicación declara el siguiente bean:

<!--
El siguiente bean mapeará las solicitudes /login/procesar.do hacia el controlador pe.com.slcsccy.springmvc.controllers.LoginController, estas solicitudes serán procesadas por el metodo: handleRequest(HttpServletRequest,HttpServletResponse) que deacuerdo al procesamiento ó lógica de negocio retornará hacia la vista apropiada
-->
<bean name="/login/procesar.do" class="pe.com.slcsccy.springmvc.controllers.LoginController">
<property name="vistaOk" value="iniciar"/>
<property name="vistaError" value="error"/>
</bean>

2. Crea una clase que implemente la interface Controller y agregale la lógica de negocia que tu quieras:

package pe.com.slcsccy.springmvc.controllers;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import pe.com.slcsccy.springmvc.dominio.Usuario;

public class LoginController implements Controller {

private String vistaOk;
private String vistaError;

/*
Método definido en la interfaz Controller e implementado aquí para dar soporte a las solicitudes cuyo patrón URL está declarado en el Front Controller(frontController-servlet.xml)
*/
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("VistaOk:"+getVistaOk());
System.out.println("VistaError:"+getVistaError());
String usuarioRequest = request.getParameter("usuario");
if ("ccacique".equals(usuarioRequest)) {
/* Creamos los datos que supuestamente los obtenemos con un Service */
Map datosLogin = new HashMap();
Usuario usuario = new Usuario();
usuario.setNombres("Carlos Alberto");
usuario.setApellidos("Cacique Yupanqui");
usuario.setCodigo("ccacique");
datosLogin.put("usuario", usuario);
/* El constructor de ModelAndView recibe como parametros el nombre de la
vista hacia la cual vamos a responder y el modelo de datos que hemos obtenido.*/
return new ModelAndView(getVistaOk(), datosLogin);
} else {
Map datosError = new HashMap();
datosError.put("mensaje", "Usuario incorrecto");
return new ModelAndView(getVistaError(), datosError);
}
}

public String getVistaOk() {
return vistaOk;
}
public void setVistaOk(String vistaOk) {
this.vistaOk = vistaOk;
}
public String getVistaError() {
return vistaError;
}
public void setVistaError(String vistaError) {
this.vistaError = vistaError;
}
}

3. Define las vistas, para el caso solo 2 páginas jsp; error.jsp e iniciar.jsp

error.jsp:
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Pagina de Error</title>
</head>
<body>PAGINA DE ERROR:
<c:out value="${mensaje}"/>
</body>
</html>

iniciar.jsp
<%@ page contentType="text/html" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>Aplicación J2EE</title></head>
<body>Bienvenido: <c:out value="${usuario.apellidos}"/>,<c:out value="${usuario.nombres}"/>
</body>
</html>


Comparando la forma de implementación de Controllers ya sea mediante anotaciones(@Controller) o mediante la implementación de la interfaz Controller(como vimos en este post), pues la tendencia es ir hacia lo que es anotaciones, con las anotaciones podemos prescindir de declarar los controllers en el FrontController, lo unico que tenemos que hacer es escanear el contexto del contenedor en busca de estereotipos y es el contenedor quien se encargará de detectar e instanciar los controllers adecuados, recuerda que el escaneo lo realizamos con la siguiente declaración:
<context:component-scan base-package="pe.com.slcsccy.springmvc.controllers"/>

Si aún así pues no te animas por las anotaciones pues en los siguientes post vamos a ver algo más de controllers más detallados y especificos para un problema particular.


viernes, 21 de agosto de 2009

Spring MVC Usando @Controller y Formularios

En el post anterior vimos como podemos usar el estereotipo @Controller para mapear las solicitudes con la anotación @RequestMapping a métodos, esta vez vamos a ver como podemos usar formularios, validarlos y hacer uso de una clase estereotipada(denuevo @Controller) que controle y mapee adecuadamente las solicitudes que se hagan con el formulario, para esto la configuración de la aplicación no ha cambiado(no he modificado web.xml ni frontController-servlet.xml del post anterior y que voy a reutilizar).

Veamos el primer archivo cliente:
<a href="${pageContext.request.contextPath}/forms/usuario.do">Registrar Usuario</a>

La línea anterior la podemos colocar en cualquier archivo jsp y lo que hará es solicitar al controlador(UsuarioController.java) que me retorne la página donde voy a registrar al usuario, la clase estereotipada UsuarioController.java es la siguiente:


package pe.com.slcsccy.springmvc.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import pe.com.slcsccy.springmvc.dominio.Usuario;
import pe.com.slcsccy.springmvc.validacion.UsuarioValidador;

@Controller
@RequestMapping("/forms/usuario.do")
@SessionAttributes(types = Usuario.class)
public class UsuarioController {

/**
Este método es el que se ejecutará cuando las solicitudes tengan el patrón URL de la forma /forms/usuario.do y sean del tipo GET, es decir desde un link a href, desde un URL, desde javascript o cuando en el formulario html el atributo method sea GET o no esté especificado.
*/
@RequestMapping(method = RequestMethod.GET)
public String inicializarFormulario(Model model) {
/*
Al formulario le enviamos los datos por default de un usuario, este método actua como el inicializador o constructor del formulario
*/
Usuario usuario = new Usuario();
model.addAttribute(usuario);
return "forms/usuario";
}

/*
Este método será ejecutado solo cuando el patrón URL del formulario sea /forms/usuario.do y además el método del formulario que envía los datos solo sea POST, el detalle de los argumentos de los métodos es el siguiente:
@param @ModelAttribute contiene todos los datos del usuario que ha sido enviado desde el formulario
@param BindingResult, BindinResult es una interface, lo que hace Spring es crear un objeto que implementa esta interface,especificamente Spring implementa org.springframework.validation.BeanPropertyBindingResult este objeto se enlazará el objeto usuario enviado para permitir realizar las validaciones necesarias, extiende de Errors.
@param SessionStatus, objeto que determina si el PROCESAMIENTO de la session a sido completado o no, podemos usar este objeto para indicar a la aplicación que el PROCESAMIENTO de la session ha sido completado y que los atributos en él ya no son necesarios.
*/
@RequestMapping(method = RequestMethod.POST)
public String procesarFormulario(@ModelAttribute Usuario usuario,
BindingResult resultado, SessionStatus estado) {
/* Validamos los datos del usuario */
new UsuarioValidador().validar(usuario, resultado);

/* Si no hay errores limpiamos la session y redireccionamos */
if(!resultado.hasErrors()){
/* Limpiamos los objetos de session para este procesamiento*/
estado.setComplete();
/* redireccionamos al método inicializarFormulario */
return "redirect:usuario.do";
}

/* Si hay errores los mostramos en el formulario */
return "forms/usuario";
}

}


La clase anterior usa un validador que es el siguiente:

UsuarioValidador.java
package pe.com.slcsccy.springmvc.validacion;

import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import pe.com.slcsccy.springmvc.dominio.Usuario;

public class UsuarioValidador {

public void validar(Usuario usuario, Errors errors) {

if (!StringUtils.hasLength(usuario.getCodigo())) {
errors.rejectValue("codigo", "required", "required");
}
if (!StringUtils.hasLength(usuario.getNombres())) {
errors.rejectValue("nombres", "required", "required");
}
if (!StringUtils.hasLength(usuario.getApellidos())) {
errors.rejectValue("apellidos", "required", "required");
}
}
}


Espero haber sido lo suficientemente claro en los comentarios java, si tienes dudas no dudes en comentar.

La clase anterior será llamada cuando el usuario hace click en un enlace como:
<a href="${pageContext.request.contextPath}/forms/usuario.do">Registrar Usuario</a>

La clase será llamada porque el url(href) satisface el patrón declarado en la clase:
@RequestMapping("/forms/usuario.do") y el método a ejecutar será inicializarFormulario, porque dicho método está anotado para que le lleguen todas las solicitudes GET @RequestMapping(method = RequestMethod.GET) debes de recordar que una de las formas de realizar solicitudes GET es mediante links(href) en las páginas html.

Una vez ejecutado el método, este instanciarà un objeto usuario y lo devolverá en el modelo de datos a la siguiente vista:

usuario.jsp
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Registro de Usuarios</title>
</head>
<body>
<form:form modelAttribute="usuario">
Codigo: <form:input path="codigo" /><form:errors path="codigo"/><br/>
Nombres: <form:input path="nombres" /><form:errors path="nombres"/><br/>
Apellidos: <form:input path="apellidos" /><form:errors path="apellidos"/><br/><br/><br/>
<input type="submit" value="Registrar Usuario"/>
</form:form>
</body>
</html>


Podemos ver el código html generado y notar que el atributo method(del formulario) es POST, entonces cuando le demos al botón "Registrar Usuario" este procesamiento será enviado hacia el método procesarFormulario de la clase UsuarioController.

Eso es todo por el momento, si tienes alguna duda puedes resolverla comentando...