sábado, 5 de septiembre de 2009

Spring MVC SimpleFormController

Veamos una clase que nos permitirá controlar las acciones que realicemos con los formularios de una manera más simple: SimpleFormController

El caso de uso a desarrollar es uno bien simple de registro de usuarios, los requerimientos son los siguientes:

1. Si el actor o persona que va a registrar al usuario no está autenticada en el sistema entonces redirigirla hacia una página para que se autentique.

Solución a 1. Extensión de la clase HandlerInterceptorAdapter para implementar nuestro propio interceptor de solicitudes hacia un controlador específico, en este caso el controlador es un SimpleFormController, el componente que verificará la existencia del usuario en el contexto de sesion es el Interceptor.

2. Uso de etiquetas de formulario de spring, la libreria a usar será:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

Ahora veamos el código:

Lo primero que tenemos que hacer es implementar nuestro objeto Command, en este caso el objeto command es un javabean simple:

Usuario.java
package pe.com.slcsccy.springmvc.dominio;
import java.util.Date;

public class Usuario {

private String codigo;
private String nombres;
private String apellidos;
private String perfil;
private String multisesion;
private Boolean habilitado;
private String[] aplicaciones;
private String nacionalidad;
private Date fechaRegistro;
private Date fechaCese;

//Continuan setters and getters....
}

Ahora vamos a crear nuestro controlador:

UsuarioFormController.java
package pe.com.slcsccy.springmvc.controllers;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import pe.com.slcsccy.springmvc.dominio.Usuario;
import pe.com.slcsccy.springmvc.facade.Facade;

public class UsuarioFormController extends SimpleFormController {
private Facade facade;

@Override
protected Object formBackingObject(HttpServletRequest request) throws Exception {
Usuario usuario = null;
String coUsuario = request.getParameter("coUsuario");
if (coUsuario != null && !coUsuario.equals("")) {
usuario = facade.getUsuarioService().buscar(coUsuario);
} else {
usuario = new Usuario();
}
return usuario;
}


/**
Este método es llamado antes de que el formulario sea procesado, aquí
es donde colocamos el modelo de datos que el formulario necesita utilizar,
por ejemplo si vas a registrar un usuario necesitas asignarle un perfil de una
lista, la lista la obtenemos del DAO por aquí
*/
@Override
public Map referenceData(HttpServletRequest request) {
Map modelo = new HashMap();
ArrayList perfiles = facade.getPerfilService().listar();
ArrayList aplicaciones = facade.getAplicacionService().listar();
ArrayList nacionalidades = facade.getNacionalidadService().listar();
modelo.put("aplicaciones", aplicaciones);
modelo.put("perfiles", perfiles);
modelo.put("nacionalidades", nacionalidades);
return modelo;
}


/**
* Este método permite registrar editores personalizados para ciertos campos
* de nuestro objeto command, en este caso el objeto command es un usuario
* el cual tiene dos campos de fechas pero de tipo Date, lo que hacemos aquí
* es asociar los valores del request a los tipos Date con una fecha
* personalizada, recuerda que el request.getParameter("campo") te devuelve
* siempre un valor null o un String por lo que la transformación a Date
* lo realiza el editor personalizado declarado aquí(Binding)
*/
@Override
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder dataBinder) throws ServletException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}

/**
* hacemos un postprocesamiento de los datos que el usuario a enviado,
* en este caso solo le asignamos a la variable de multisesion un valor N.
* este campo de multisesion está relacionado a un checkbox y en el
* objeto command el campo es detipo String, si el usuario no ha marcado
* el checkbox entonces su valor es null, pero en la base
* el valor solo debe ser ó N ó S. Aqui cambiamos el valor null por un más
* significativo.
*/
@Override
protected void onBindAndValidate(HttpServletRequest request, Object command, BindException errors)
throws Exception {
Usuario usuario = (Usuario)command;
usuario.setMultisesion(usuario.getMultisesion()==null?"N":"S");
}

/**
* Método que se encargará del registro del usuario en la base de datos
* después de que las validaciones fueron exitosas(no hubo errores)
*/
@Override
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors) throws Exception {
Usuario usuario = (Usuario) command;
int coError = facade.getUsuarioService().registrar(usuario);
if (coError == 0) {
return new ModelAndView("forms/vistaOk");
} else {
return new ModelAndView("forms/vistaError");
}
}

/**
* @return the facade
*/
public Facade getFacade() {
return facade;
}

/**
* @param facade the facade to set
*/
public void setFacade(Facade facade) {
this.facade = facade;
}
}


Ahora declaramos la clase que validará los datos que ingrese el usuario:
UsuarioValidator.java
package pe.com.slcsccy.springmvc.validators;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import pe.com.slcsccy.springmvc.dominio.Usuario;

public class UsuarioValidator implements Validator {

public boolean supports(Class clase) {
return Usuario.class.isAssignableFrom(clase);
}

/**
* Método principal que será llamado antes de que pase por el controlador
* si la lista de errores contiene al menos un elemento, regresará al
* formulario
*/
public void validate(Object object, Errors errores) {
Usuario usuario = (Usuario) object;
if(usuario.getAplicaciones().length==0)
errores.rejectValue("aplicaciones", "errores.aplicacionesReq");
ValidationUtils.rejectIfEmpty(errores, "codigo", "errores.codigoReq","Mensaje 1");
ValidationUtils.rejectIfEmpty(errores, "apellidos", "errores.apellidosReq","Mensaje 2");
ValidationUtils.rejectIfEmpty(errores, "nombres", "errores.nombresReq","Mensaje 3");
}
}

Los ultimos componentes a crear serán los interceptores:
LoginInterceptor.java
package pe.com.slcsccy.springmvc.controllers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LoginInterceptor extends HandlerInterceptorAdapter {


/*
* Este método debe de asegurar que al controller solo le lleguen solicitudes
* que cumplan ciertos datos del negocio, como que el usuario este autenticado por ejemplo,
* recordar que el objeto handler en el argumento es el controller hacia al cual
* se dirige la solicitud.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
/*Solo retornamos true...*/
return true;
/*Si no queremos ir hacia la vista apropiada entonces:*/
/*if(true){
throw new ModelAndViewDefiningException(new ModelAndView("exception/noAutenticado"));
}else return true;*/
}

/*
* Este meétodo puede hacer un postprocesamiento de la solicitud, por ejemplo
* puede cambiar la vista hacia la cual el controller respondió
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav)
throws Exception {
}
}

Otro interceptor para que veas como funcionan en forma encadenada o en chain.
LoginAduanasInterceptor.java
package pe.com.slcsccy.springmvc.controllers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LoginAduanasInterceptor extends HandlerInterceptorAdapter {


@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("Interceptor de Adunas ejecutandose en el preHandle()");
return true;
//Si no queremos ir hacia la vista apropiada entonces
/*if(true){
throw new ModelAndViewDefiningException(new ModelAndView("exception/noAutenticado"));
}else return true;*/
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav)
throws Exception {
System.out.println("Interceptor de Aduanas ejecutandose en el postHandle()");
}
}


Me estaba olvidando lo más importante, el formulario jsp, allí está:
usuarioForm.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>Gestión de Usuarios</title>
</head>
<body>
<form:form commandName="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/>
Fecha Registro: <form:input path="fechaRegistro" /><form:errors path="fechaRegistro"/><br/>
Fecha Cese: <form:input path="fechaCese" /><form:errors path="fechaCese"/><br/>
Perfiles:<form:select path="perfil">
<form:option value="">SELECCIONE</form:option>
<form:options items="${perfiles}" itemValue="coPerfil"itemLabel="dePerfil"/>
</form:select><form:errors path="perfil"/><br/><br/><br/><br/>

<!-- Ahora vamos a usar un checbox que diga si el usuario está habilitado o no-->
Habilitado:<form:checkbox path="habilitado"/><br><br/>
MultiSesion:<form:checkbox path="multisesion" value="S"/><br><br/>
<!-- Ahora mostramos un conjunto de checkboxes para que seleccione la aplicación a la que tiene acceso-->
Aplicaciones:
<form:checkboxes path="aplicaciones" items="${aplicaciones}" itemValue="coAplicacion" itemLabel="deAplicacion"/>
<form:errors path="aplicaciones"/><br/>
<br/><br/>

Nacionalidad:
<form:radiobuttons path="nacionalidad" items="${nacionalidades}" itemValue="coNacionalidad" itemLabel="deNacionalidad"/>
<form:errors path="nacionalidad"/><br/>
<br/><br/>
<input type="submit" value="Registrar Usuario"/>
</form:form>
</body>
</html>


Y por ultimo la integración de todos los componentes con beans de spring que debes de colocar en el front controller <controller>-servlet.xml

frontController-servlet.xml
...
<!-- Definición de interceptores de los controladores -->
<bean id="loginInterceptor" class="pe.com.slcsccy.springmvc.controllers.LoginInterceptor"/>
<bean id="loginAduanasInterceptor" class="pe.com.slcsccy.springmvc.controllers.LoginAduanasInterceptor"/>

<!-- Con este mapeador asociamos keys a urls y a ciertos controladores, mapping caleta, -->

<!--
Declaramos un mapeador de urls seguro, es decir que pasara por dos
interceptores antes de que llegue al UsuarioFormController
-->
<bean id="handlerMappingSeguro" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<l-- recuerda que los inteceptores son llamados en el orden en que son declarados-->
<ref bean="loginInterceptor"/>
<ref bean="loginAduanasInterceptor"/>
</list>
</property>
<property name="urlMap">
<map>
<entry key="/aduanas/usuarioForm.do" value-ref="usuarioFormController"/>
</map>
</property>
</bean>

<!-- Declaramos el validador del formulario -->
<bean name="usuarioValidator" class="pe.com.slcsccy.springmvc.validators.UsuarioValidator"/>

<!--
El bean de formulario que integrará los commands(pojos), jsps(formularios) y
validadores y alguna referencia a otro bean como un facade de servicios por ejemplo.
-->
<bean id="usuarioFormController" class="pe.com.slcsccy.springmvc.controllers.UsuarioFormController">
<property name="facade" ref="facade"/>
<property name="sessionForm" value="true"/>
<property name="commandName" value="usuario"/>
<property name="commandClass" value="pe.com.slcsccy.springmvc.dominio.Usuario"/>
<property name="formView" value="forms/usuarioForm"/>
<property name="successView" value="forms/usuarioRegistrado"/>
<property name="validator" ref="usuarioValidator"/>
</bean>
...


Eso es todo por el momento, animate a hacer tu propia aplicación con todos los componentes, en el próximo post hablaré sobre como podemos crear componentes que permitan descargar hacia Excel y Pdf mediante poi e itext integrado con spring.