----------------------------------------------------------

Pedro del Gallego's Weblog

Main | Next month (sep 2003) »

20030819 martes agosto 19, 2003

Front Controller + gestor de eventos ( 2ª parte )

En los post anteriores hablabamos sobre el patron front controller, ( tambien hablaron Al y Álvaro). Este post es un poco largo y contiene bastante codigo. Para ver un esquema que sigue el flujo de una peticion ver esta secuencia y para ver un diagrama de las clases finales ver aqui. En este post seguire con dos aspectos que deje fuera:

Antes que nada decir que este ejemplo no pretende ser un framework real, ni nada parecido, sino solo un conjunto de clases que muestren de la manera mas sencilla posible algunos conceptos del front controller (por esto no he querido utilizar XML y minimizar la configuracion en archivos properties ya que no son en si mismo parte del patron)

Redireccionamiento


En la implementacion anterior solo podiamos ir a una vista o a otra accion mediante el metodo sendRedirect. En este caso el primer paso era crear una estructura que almacenase el tipo de respuesta que se debia ejecutar.

public class AccionResult { private String nombre="";
private String path="";
private boolean forward=true;
/**
* Constructores
*/
public AccionResult(String nombre, boolean forward){ this.nombre=nombre;
this.forward=forward;
}

public AccionResult(String nombre, String path, boolean forward){ this.nombre=nombre;
this.path=path;
this.forward=forward;
}

..... Metodos Getters y Setters


Cambiamos el metodo accion de la interfaz Accion para que en vez de una cadena ahora nos devuelva una instancia de la clase AccionResult

public AccionResult accion();

Y despues añadir a la clase Controlador el metodo enrutar

public void enrutar(AccionResult aResult, HttpServletRequest request, HttpServletResponse response){
if (aResult.isForward()){ // Lanzo un forward con la request y un response
try { this.getServletContext().getRequestDispatcher(response.encodeURL(aResult.getNombre())).forward(request,response); } catch (ServletException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
}else{ // Redirecciono a la nueva URL
try { response.sendRedirect(aResult.getNombre()); } catch (IOException e) { e.printStackTrace(); }
}
}


Y ahora solo con cambiar la accion req.sendRedirect por this.enrutar(aResult,rep,res) del metodo procesar del controlador podemos elegir el tipo de redireccionamiento. El problema de esta solucion es que los valores del la clase AccionResult son introducidos directamente enel codigo, cuando lo ideal seria poder definirlos en un fichero (xml o properties), por ejemplo algo como [forward value="true" result="exito" direccion="/RegistroExito.jsp"] para desacoplar las JSPs de las Acciones algo mas.

Gestion de eventos


El segundo punto, creo que va a ser mas polemico (ya que aqui si que he encontrado cierta dificultad en cuanto al concepto en si mismo). A priori habia dos casos que se podrian tratar: El esquema basico del funcionamiento del front controller que he diseñado viene dado por esta secuencia. Como vemos existen unas clases que ejecutaran cada evento particular, estas clases implementan la interfaz AccionListener:

public interface AccionListener { public String doBeforeAccion() throws ServletException;
public String doAfterAccion() throws ServletException;
}
Existe una clase abstracta apartir de la que heredan todos los eventos AccionEvent : public abstract class AccionEvent implements AccionListener { public static final int BEFORE_ACCION=0,AFTER_ACCION=1; int tipoEvento;
public AccionEvent(int tipoEvento) { this.tipoEvento=tipoEvento; }
.....Metodos Getters y Setters
}


Y la interfaz accion se amplia con los metodos necesarios para añadir, eliminar y disparar los eventos.

public void dispararEvents(int tipoEvento);
public void addEventListener(AccionListener aListener);
public void removeEvent(AccionListener aListener);

Asi mismo estos metodos son implementados por la clase AbstracAccion, del que heredaran ahora las acciones.
protected List eventListeners = new ArrayList(); public void dispararEvents(int tipoEvento) { Iterator i = eventListeners.iterator(); AccionEvent aListener = null; while (i.hasNext()) { aListener =(AccionEvent) i.next(); try{ if (aListener.getTipoEvento()==tipoEvento) aListener.doBeforeAccion(); else if (aListener.getTipoEvento()==tipoEvento) aListener.doAfterAccion(); }catch(ServletException se){ System.out.println("Error al lanzar un Evento del tipo " + tipoEvento); } } }
.... Los otros dos metdos .....
}

Resumiendo el esquema de las clases queda asi. Ya podeis despedazarme por los errores que he cometido. ¿Algun problema de concepto?. ¿Alguna sugerencia? ( ago 19 2003, 06:31:30 PM CEST ) Permalink Comentarios [3]

20030813 miércoles agosto 13, 2003

Revisado : El patron Front Controller 1ª parte Bueno siguiendo el consejo de Al, he decidido cambiar la implementacion del ActionFactory para que solo se instancie una vez cada accion y luego se reutilice esta instancia el resto de las veces.

public class AccionFactory {
// tabla para guardar las acciones
private static Hashtable mapaAcciones = new Hashtable();

public static Accion getAccion(String accion){
Accion action = (Accion) mapaAcciones.get(accion); if (action==null){
try { action = (Accion)Class.forName(accion).newInstance();
mapaAcciones.put(accion, action);
} catch (InstantiationException e) { System.out.println("Error al instanciar la clase"); e.printStackTrace(); } catch (IllegalAccessException e) { System.out.println("Acceso Ilegal a no se que ¿?"); e.printStackTrace(); } catch (ClassNotFoundException e) { System.out.println("Tas tonto esa clase no existe"); e.printStackTrace(); }
}
return action;
} }


Bueno en principio cada clase que implemente accion se instancia una unica vez y es almacenada en una Hashtable. Pero no daria esto problemas de concurrencia si se solicitan a la vez dos instancias de esa accion? no termino de verlo del todo claro... voy a darle un par de vueltas a ver si veo donde me he perdido. + criticas please...

Bueno a parte de esto he hecho una pequeña modificacion para que el fichero de configuracion quedase tal que asi:

comprar=comprarAccion
elegir=elegirAccion
devolver=devolveAccion
buscar=buscarAccion
inicio=inicioAccion

Que siempre queda algo mas fino. Para extraer la accion de la URI he añadido el metodo

private String getClassName(String nombre){ int barra = nombre.lastIndexOf("/");
int punto = nombre.lastIndexOf(".");
if ((barra<=0)&&(punto<=0)) return nombre;
return nombre.substring(barra+1,punto);
}

PD Al, de esta semana no pasa que no me baje el codigo y la documentacion de Cañamo. No se si sabre implementar algo o sugerir alguna mejora del diseño, pero por lo menos que no se diga que no lo intento (Que despues hablamos de sensacion de comunidad y de implicacion, y yo soy el primero en dar mal ejemplo). Ademas que ya has tirado unas cuantas indirectas y muchos nos hacemos lo locos, ya va siendo hora. ( ago 13 2003, 05:35:22 PM CEST ) Permalink Comentarios [5]

20030812 martes agosto 12, 2003

El patron Front Controller 1ª parte
Como comente en mi ultimo post he estado dando un pequeño curso sobre J2EE (Bueno mas bien sobre Servlet y JSP, pero eso es otra historia). En la aplicacion del ultimo dia presentaron un Servlet que implantaba el patron Front Controller de la forma

public class MiServlet extends HttpServlet{

public void doPost(HttpServletRequest request, HttpServletResponse response) { String action = request.getParameter("action"); if(action.equals("alta")){ // codigo del alta }else if(action.equals("baja")){ // codigo para dar de baja else if (....){ }.... }
}



No hace falta discutir lo malo de esta forma de implentarlo. Como soy bastante quisquilloso decidi implementar uno por mi parte. Para empezar me marque una serie de objetivos:

Empece por definir la interfaz Accion de la forma mas simple posible. La idea de crear una interfaz comun a todas las acciones es que obliga a que todas inserten el codigo que se ejecutara dentro de un metodo.

public interface Accion {

public String accion(); }



Para obtener una clase pense en un patron factory, creo que es una buena eleccion y que la tarea se ajusta a la definicion de este patron. Posiblemente seria necesario declarar el metodo como syncrhonized, pero no estoy del todo seguro. El objetivo de esta clase es que dado el nombre de otra clase nos devuelva una instancia de esta.

public class AccionFactory {

public static Accion getAccion(String accion){ Accion action = null; try { action = (Accion)Class.forName(accion).newInstance(); } catch (InstantiationException e) { System.out.println("Error al instanciar la clase"); e.printStackTrace(); } catch (IllegalAccessException e) { System.out.println("Acceso Ilegal a no se que ¿?"); e.printStackTrace(); } catch (ClassNotFoundException e) { System.out.println("Tas tonto esa clase no existe"); e.printStackTrace(); } return action; } }



Ahora tocaba buscar una forma de almacenar el mapeo, como ya explique me decidi por un fichero de propiedades por su simplicidad, el nombre del fichero esta directamente implementado en el codigo, aunque seria muy sencillo pasarselo como parametro de inicializacion al servlet y despues recuperarlo alli.

public class MapeoAccionesDAO {

private Properties mapa; public MapeoAccionesDAO(String configuracion){ mapa = new Properties(); try { InputStream iStream = new FileInputStream(configuracion);
mapa.load(iStream);
} catch (FileNotFoundException e) { System.out.println("Error fichero no encontrado");
e.printStackTrace();
} catch (IOException e) { System.out.println("Error IOException no se porque da este error");
e.printStackTrace();
}
} public String getNombreAccion(String requesURI){ return mapa.getProperty(requesURI); }
}



Ya por ultimo (para alivio de los que habeis leido tantas lineas de codigo) solo faltaba implementar el servlet que actua como controlador. Tambien opte por hacerlo de la forma mas sencilla posible, eliminando por ejemplo la posibilidad de decidir si lo que quiero es ralizar un forward o un sendRedirect (esto lo hare un poco mas adelante).

public class Controlador extends HttpServlet {

/*
* Usaremos un objeto que nos mapee las URI con las
* clases accion asociadas a esa URI
*/
private MapeoAccionesDAO acciones;
public void init(ServletConfig config) throws ServletException {
super.init(config);
/*
* Inicializamos el mapeo usando un patron DAO
*/
ServletContext sc = config.getServletContext();
String configuracion = sc.getRealPath("configuracion.properties");
acciones = new MapeoAccionesDAO(configuracion);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.procesar(request,response); }
protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.procesar(request,response); }
private void procesar( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ Accion action = null;
/*
* Extraemos la URI de la solicitud
* y se la pasamos al properties como clave
*/
String accion = acciones.getNombreAccion(request.getRequestURI());
System.out.println("Voy a realizar la accion : " + accion);
/*
* Instanciamos la clase que nos devuelve el properties
* y usamos este como zona de mapeo
* y todo esto usando un patron Factory
*/
action = AccionFactory.getAccion(accion);
String nuevaURL = action.accion();
response.sendRedirect(nuevaURL);
}
}


un ejemplo del mapeo seria el siguiente fichero :

comprar.do=comprarAccion
/mittienda/elegir.do=org.mitienda.elegirAccion
/mittienda/devolver.do=org.mitienda.devolveAccion
/mittienda/buscar.do=org.mitienda.buscarAccion
/mittienda/inicio.do=inicioAccion

Pues esto es lo que he hecho, no se si sigue a rajatabla el patron front controller o si he metido la pata, asi que espero opiniones, sugerencias y sobre todo críticas (que es de donde + se aprende). Quedan muchos aspectos por hacer, pero solo queria tener una idea de como funcionaba.

PD: es mi primer post con lineas de codigo :-) ( ago 12 2003, 11:48:41 PM CEST ) Permalink Comentarios [5]