lunes, 1 de febrero de 2010

Creación de Aspectos con Spring y Anotaciones Parte 2

Ahora vamos a ver lo siguiente: como podemos ordenar la ejecución de los aspectos cuando estos aspectos se disparan ante el mismo advisor o método. En el post anterior definí un método imprimir de la interface ImpresorService, entonces, ¿qué es lo que pasa si tenemos más de un aspecto declarado ante la ejecución del método imprimir? ¿cuál aspecto se ejecuta primero?¿cómo podemos declarar que un aspecto se ejecute antes que otro?.

Veamos el siguiente requerimiento:
1. Agregar un proceso de auditoria que capture todas las ejecuciones del método imprimir

El requerimiento anterior lo podemos solucionar agregando un aspecto más y definir e implementar tanto la clase como el método de auditoria con la anotación @Order

Solución:
Declaración de componentes de spring:
<context:annotation-config/>
<context:component-scan base-package="pe.com.slcsccy.service"/>
<bean id="impresorAspect" class="pe.com.slcsccy.aop.ImpresorAspect"/>
<bean id="auditoriaAspect" class="pe.com.slcsccy.aop.AuditoriaAspect"/>
<aop:aspectj-autoproxy/>

Implementación de componentes java:
AuditoriaAspect.java:
package pe.com.slcsccy.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import pe.com.slcsccy.bean.Libro;
@Order(1)
@Aspect
public class AuditoriaAspect {
@Order(1)
@Before("execution(public * pe.com.slcsccy.service..*(..)) && "+
"args(libroin) && target(instancia)")
public void auditar(Libro libroin, Object instancia) {
System.out.println("Aplicando Aspecto de auditoria auditar(..) @Before! con instancia:"+instancia.getClass().getCanonicalName());
}
}

ImpresorAspect.java
package pe.com.slcsccy.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import pe.com.slcsccy.bean.Libro;
@Order(2)
@Aspect
public class ImpresorAspect {
@Order(2)
@Before("execution(public * pe.com.slcsccy.service..*(..)) && "+
"args(libroin) && target(instancia)")
public void asignarAutor(Libro libroin, Object instancia) {
if(libroin!=null)libroin.setAutor("Carlos Cacique");
System.out.println("Aplicando Aspecto asignarAutor(..) @Before! con instancia:"+instancia.getClass().getCanonicalName());
}
}

Algunas acotaciones: La anotación @Order debe ser colocada tanto a nivel de clase como a nivel de método, si lo colocamos solo a nivel de método, entonces el orden de ejecución va a estar indicado por el orden en la carga de los beans:
<bean id="impresorAspect" class="pe.com.slcsccy.aop.ImpresorAspect"/>
<bean id="auditoriaAspect" class="pe.com.slcsccy.aop.AuditoriaAspect"/>

domingo, 31 de enero de 2010

Creación de Aspectos con Spring y Anotaciones Parte 1

Hola denuevo, hace mucho tiempo que no posteo y esta vez quiero seguir con los aspectos en Spring, en este post supongo que ya conocen que es un aspecto, para resumirles y hablando como pragramador es un método java que se ejecuta antes, después o antes y después de la ejecución de otro método java, esto nos permite agregar lógica a nuestra aplicación conociendo únicamente la especificación de los métodos a los cuales se les quiere aplicar un aspecto.
La aplicación de ejemplo que he desarrollado es simple: un servicio impresor de libros que tiene un método imprimir con un parámetro Libro a este método imprimir es al cual le aplico una serie de aspectos para agregar las propiedades del libro(autor, isbn y título), los anotaciones que he usado para aplicar los aspectos son: @Aspect, @Before, @After, @AfterReturning, @AfterThrowing, @Around

La clase principal es la siguiente:

package pe.com.slcsccy.aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import pe.com.slcsccy.bean.Libro;
import pe.com.slcsccy.service.ImpresorService;

@Aspect
public class ImpresorAspect {

/**
* Este método se ejecuta SIEMPRE antes de cualquier ejecución
* de cualquier método de cualquier clase del paquete pe.com.slcsccy.service y
* que tenga un primer parámetro con nombre libroin
* Obtengo el objeto que viene como parametro y le coloco el nombre del autor,
* además capturo la instancia que disparó el aspecto.
* */
@Before("execution(public * pe.com.slcsccy.service..*(..)) && "+
"args(libroin) && target(instancia)")
public void asignarAutor(Libro libroin, Object instancia) {
if(libroin!=null)libroin.setAutor("Carlos Cacique");
System.out.println("Aplicando Aspecto asignarAutor(..) @Before! con instancia:"+instancia.getClass().getCanonicalName());
}
/**
* Este método se ejecuta SIEMPRE antes de la ejecución de cualquier
* método imprimir que tenga un primer parámetro con nombre libroin de
* cualquier clase que implementa la interface
* pe.com.slcsccy.service.ImpresorService
* */
@Before(value="execution(public * pe.com.slcsccy.service.ImpresorService.imprimir(..)) && "+
"args(libroin) && target(instancia)")
public void asignarISBN(Libro libroin, ImpresorService instancia) {
if(libroin!=null)libroin.setIsbn("41547848");
System.out.println("Aplicando Aspecto asignarISBN(..) @Before! con instancia:"+instancia.getClass().getCanonicalName());
}
/**
* Este método se ejecuta SOLO después de la ejecución EXITOSA de cualquier
* método imprimir que tenga un primer parámetro con nombre libroin
* de cualquier clase que implementa la interface
* pe.com.slcsccy.service.ImpresorService
* */
@AfterReturning(value="execution(public * pe.com.slcsccy.service.ImpresorService.imprimir(..)) && "+
"args(libroin) && target(instancia)",returning="libroOut")
public void asignarTitulo(Libro libroin, Libro libroOut, ImpresorService instancia) {
libroOut.setTitulo("Ejemplo de aspectos con spring");
System.out.println("Aplicando Aspecto asignarTitulo(..) @AfterReturning! con instancia:"+instancia.getClass().getCanonicalName());
System.out.println("Libro Out:"+libroOut);
}
/**
* Este método se ejecuta SIEMPRE después de cualquier
* método imprimir que tenga un primer parámetro con nombre libroin
* de cualquier clase que implementa la interface
* pe.com.slcsccy.service.ImpresorService
* */
@After(value="execution(public * pe.com.slcsccy.service.ImpresorService.imprimir(..)) && "+
"args(libroin) && target(instancia)")
public void finalizarNormal(Libro libroin, ImpresorService instancia) {
System.out.println("Aplicando Aspecto finalizarNormal(..) @After! con instancia:"+instancia.getClass().getCanonicalName());
System.out.println("Libro:"+libroin);
}
/**
* Este método se ejecuta SIEMPRE que ocurre una excepción después de ejecutar
* cualquier método imprimir que tenga un primer parámetro con nombre libroin
* de cualquier clase que implementa la interface
* pe.com.slcsccy.service.ImpresorService
* */
@AfterThrowing(value="execution(public * pe.com.slcsccy.service.ImpresorService.imprimir(..)) && " +
"args(libroin) && target(instancia)",
throwing="excepcion")
public void finalizarConExcepcion(Libro libroin, ImpresorService instancia, ImpresorException excepcion) {
System.out.println("Aplicando Aspecto finalizarConExcepcion(..) @AfterThrowing!");
System.out.println("Mensaje de la Excepcion en el aspecto @AfterThrowing:"+excepcion.getMessage());
}
/**
* Este método se ejecuta dos fases, la primera antes de la ejecución del método y la segunda * cuando el método a terminado satisfactoriamente.
* @throws Throwable
* */
@Around(value="execution(public * pe.com.slcsccy.service.ImpresorService.imprimir(..)) && " +
"args(libroin) && target(instancia)")
public void logging(ProceedingJoinPoint ejecutor,Libro libroin, ImpresorService instancia) throws Throwable {
System.out.println("Iniciando loggin(..) @Around!");
Object retorno = ejecutor.proceed();
System.out.println("Finalizando loggin(..) @Around!, con retorno:"+retorno.getClass().getCanonicalName());
}
}

La aplicación completa en eclipse la puedes descargar desde AQUI.

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.