Arquitectura MVC : El Controlador

Por fin llegamos a la parte final de este curso. Pequeño pero intenso realizado en Kernel Panic . El pdf del curso tiene una copia local como siempre ;)

La idea básica es que si creamos muchos servlets el web.xml va a ser mas largo que la biblia. Para hacer que la organización sea un poco mas sencilla se crea un único servlet controlador que recibirá todas las peticiones y que se encargará de distribuir el trabajo en funcón de la petición recibda. El controlador,pues, no es mas que un guardia que dirige el tráfico. Y que redirige las tareas a POJOS. Notese la similitud con la palabra pollo asimismo como la similitudo de la acción de “pasar el pollo a alguien”.

POJO

Plain Old Java Objects. Un POJO es un objeto java que hace el trabajo que antes hacían los servlets. Pero como que ahora ya tenemos un servlet que se dedica a dirigir el tráfico, pues podemos tener clases normales y corrientes para hacer los trabajos normales y corrientes.

Crear el controlador y los POJOS

Para este ejemplo crearemos un controlador que llamaremos ServletControlador Este es el único servlet que dejaremos en nuestra aplicación. por lo que el web.xml tendría el mapeo de un único servlet.

<servlet>
   <description>Servlet controlador</description>
   <display-name>ServletControlador</display-name>
   <servlet-name>ServletControlador</servlet-name>
   <servlet-class>net.juantxu.encuesta.ServletControlador</servlet-class>
</servlet>
<servlet-mapping>
   <servlet-name>ServletControlador</servlet-name>
   <url-pattern>*.accion</url-pattern>
</servlet-mapping>
<welcome-file-list>
   <welcome-file>ServletControlador</welcome-file> <!-- queremos que por defecto pase por el controlador -->
</welcome-file-list>

Por lo que ahora tan sólo tendremos un servlet que hará las funciones de controlador de tráfico (guardia urbano) y clases java normales y corrientes haciendo los trabajos normales y corrientes:

Servlet Controlador

package net.juantxu.encuesta;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.juantxu.encuesta.acciones.EncuestaAccion;
import net.juantxu.encuesta.acciones.Estadistica;
import net.juantxu.encuesta.acciones.Inicio;
 public class ServletControlador extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
   static final long serialVersionUID = 1L;
	public ServletControlador() {
		super();
	}   	
	

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	procesarPeticion(request, response);
	}  	
	

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		procesarPeticion(request, response);
	}   	  	    
	private void 	procesarPeticion(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String url = request.getRequestURI();
		String accion = null;
		// no siempre voy a recibir una accion por lo que hay que prevenir errores
		if(url.contains(".accion")){
			accion = url.substring(url.lastIndexOf("/") + 1 , url.indexOf(".accion"));
		}else{
			accion = "inicio";
		}
		if("Estadistica".equals(accion)){
			// Si quiero mostrar las estadisticas:
			Estadistica es = new Estadistica();
			es.mostrarEstadisticas(request, response);
			
		}else if("Guardar".equals(accion)){
			// Si quiero guardar una opinion
			EncuestaAccion ea = new EncuestaAccion();
			ea.recogeDatos(request, response);
		}else{
			// Si no, dirijo al índice
			Inicio i = new Inicio();
			i.muestraIndex(request, response);
		}
		
	}
 }

Pojo

package net.juantxu.encuesta.acciones;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

import javax.naming.NamingException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.juantxu.encuesta.dao.AmbitoDAO;
import net.juantxu.encuesta.dao.EstadisticasDAO;
import net.juantxu.encuesta.dao.OficioDAO;
import net.juantxu.encuesta.dto.Ambito;
import net.juantxu.encuesta.dto.Oficio;
import net.juantxu.encuesta.dto.ValorEstadistico;

/**
 * Servlet implementation class for Servlet: Inicio
 *
 */
 public class Inicio {
  	  	    
	public void muestraIndex(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// como que el listado de oficios lo vamos a necesitar siempre
		// he creado  un método al que puedo llamar siempre getListaOficios().
		request.setAttribute("oficios", getListaOficios() ); // llamo al método en el momento de establecer el atributo
		// lo mismo para los ambitos
		request.setAttribute("ambitos", getListaAmbitos() ); // llamo al método en el momento de establecer el atributo
		// lo mismo parra las estadisticas
		request.setAttribute("estadisticasPorAmbito", getEstadisticasPorAmbitos() );
		// Pasamos el  control al JSP
		RequestDispatcher dispatch = request
				.getRequestDispatcher("inicio.jsp");
		dispatch.forward(request, response);
	}
	private List<Oficio> getListaOficios(){
		List<Oficio> oficios = null;
		// Abrimos un try - catch ya que OficiosDAO lanza excepciones
		try {
			OficioDAO of= new OficioDAO();
			oficios = of.getOficios();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (NamingException e) {
			e.printStackTrace();
		}
		return oficios;
	}
	private List<Ambito> getListaAmbitos(){
		List<Ambito> ambitos = null;
		// Abrimos un try - catch ya que OficiosDAO lanza excepciones
		try {
			AmbitoDAO am= new AmbitoDAO();
			ambitos = am.getAmbitos();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (NamingException e) {
			e.printStackTrace();
		}
		return ambitos;
	}
	private List<ValorEstadistico> getEstadisticasPorAmbitos(){
		List<ValorEstadistico> estadisticas = null;
		// Abrimos un try - catch ya que OficiosDAO lanza excepciones
		try {
			EstadisticasDAO eDAO= new EstadisticasDAO();
			estadisticas = eDAO.porAmbitos();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (NamingException e) {
			e.printStackTrace();
		}
		return estadisticas;
	}
}

Y todos los demás igual. (luego te pongo el tar.gz)

Separación real del modelo y el controlador

De momento no tenemos una separación real entre las clases DAO y las acciones del controlador por dos motivos.

  • Las clases DAO lanzan excepciones que dependen de la tecnología que utilizan Las acciones dependen de la fuente de datos JDBC ya que esperan excepciones del tipo SQLException
  • Las acciones trabajan directamente con las clases DAO. Si quisieramos añadir un nuevo conjunto de clases DAO afectaría a las acciones.

Trato de las excepciones en el DAO

Envoltorio para las Exceciones DAO

Hasta ahora nuestro controlador incluye código para procesar errores que lanza el DAO. Para obtener una autentica separación en capas el controlador de debería de ser consciente del tipo de la fuente de datos. Es decir, los errores que lanza el DAO no deberían estar relacionados con una tecnología de conexión con datos concreta. Si mas adelate se cambiase la base de datos por una fuente de datos XML ya no tendríamos excepciones SQL. En este caso la situación ideal sería la de simplemente cambiar el DAO sin cambiar el controlador.

Para conseguir esta separación debemos crear un envoltorio o wrapper, como dicen en bárbaro, para nuestras excepciones. Para eso crearemos una clase DAOException que será un envoltorio para java.langExpecption

package net.juantxu.encuesta.dao;

public class DAOException extends Exception{

   private static final long serialVersionUID = 1L;

   public DAOException() {
   }
   public DAOException(String e) {
        super(e);
   }
   public DAOException(Throwable e) {
        super(e);
        e.printStackTrace();
   }
   public DAOException(String s, Throwable e) {
        super(s, e);
        e.printStackTrace();
   }
}

Clases DAO lanzan Excepciones DAO

Ahora que tenemos una excepción generica que envuelve a todas nuestras excepciones debemos hacer que todos los métodos lancen nuestra excepción.

Pongo una clase para mostrar como queda, pero se deberán retocar todos los dao.

package net.juantxu.encuesta.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import net.juantxu.encuesta.dto.Ambito;


public class AmbitoDAO {
	private Connection getConnexio() throws DAOException {
		Connection con;
		try{
			Context ic = new InitialContext();
			DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/Encuesta");
			con = ds.getConnection();
		}catch(NamingException e){
			throw new DAOException("error al recuperar la conexión'java:comp/env/jdbc/Encuesta'java:comp/env/jdbc/Encuesta '" , e);
		}catch(SQLException e){
			throw new DAOException("error al abrir la conexion a la base de datos", e);
		}
			return con;
	}
	public List<Ambito>   getAmbitos() throws DAOException {
		List<Ambito> lista;
		Connection con = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			con = getConnexio();
			ps  = con.prepareStatement("select * from  ambito" );
			rs = ps.executeQuery();
			lista = meteEnLista(rs);
		} catch (SQLException e) {
			throw new DAOException(e);
		} finally {
			try{
				if (rs != null)
					rs.close();
				if (ps != null)
					ps.close();
				if (con != null)
					con.close();
			}catch(SQLException e){
				throw new DAOException("error al cerrar la conexion a la base de datos", e);
			}
		}
	
		return lista;
	}
	private List<Ambito>   meteEnLista(ResultSet rs) throws DAOException {
		ArrayList<Ambito>   listado = new ArrayList<Ambito>  ();
		try{
			while (rs.next()) {
				Ambito a = new Ambito();
				a.setId((Integer)rs.getInt("id"));
				a.setNombre(rs.getString("nombre"));
				listado.add(a);
				
			}
		} catch (SQLException e) {
			throw new DAOException(e);
		}
			return listado;
		
	}
}

Tratamiento del Error

Pero, nuestra aplicación se usará a traves de la web por lo que los errores deben ser tratados en referencia a la web. Y esto se configura…. ¡En el WEB.XML! Ahi podemos definir diferentes tipos de errores. En nuestro caso vamos a tratar el error 404 y el error 500 Que son los errores que lanza el servidor cuando se solicita una página que no existe y el error que lanza nuestra aplicación cuando hay algún tipo de error de código.

	<error-page>
		<error-code>404</error-code>
		<location>/error/sembrao.html</location>
	</error-page>
      <error-page>
		<error-code>500</error-code>
		<location>/error/error.jsp</location>
	</error-page>

Para el error 404 no nay que hacer nada mas que crear la página /error/sembrao.html indicando que ahi no hay nada y que se salga del sembrao.

<!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>Error</title>
</head>
<body>
<div align="center">
<h1>SAL DEL SEMBRAO</h1>

<img src="img/sembrao.png">
<h2> QUE AHI NO HAY NADA TODAVÍA</h2>
Anda, tira pa'<a href="/Encuesta">fuera</a>
</div>
</body>
</html>

Para el error 500 debemos, primero, modificar el servlet controlador para que pase el DAOException que recibe al servidor de aplicaciones ( Tomcat en nuestro caso). Que se encargará de interpretar el error 500 y mostrará la página /error/error.jsp Donde nosotros mostraremos un agradable mensaje de error al usuario y una explicación oculta en un comentario.

ServletControlador

...
		try {
			if ("Estadistica".equals(accion)) {
				// Si quiero mostrar las estadisticas:
				Estadistica es = new Estadistica();
				es.mostrarEstadisticas(request, response);

			} else if ("Guardar".equals(accion)) {
				// Si quiero guardar una opinion
				EncuestaAccion ea = new EncuestaAccion();
				ea.recogeDatos(request, response);
				Inicio in = new Inicio();
				in.muestraIndex(request, response);
			} else {
				// Si no, dirijo al índice
				Inicio i = new Inicio();
				i.muestraIndex(request, response);
			}
		} catch (DAOException e) {
			throw new ServletException(e); // aqui pasamos la excepcion al servlet //
		}
...

/error/error.jsp

 <%@ page isErrorPage="true" %>
 <?xml version="1.0" encoding="UTF-8" ?>
 <!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>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <title>ERROR</title>
 </head>
 <body>
 <h2>Ha ocurrit un error en l'aplicacion.</h2>
 <!--
       <%= exception.toString() %> 
	   <% exception.printStackTrace(); %>		
-->
Volver al <a href="/Encuesta">inicio</a>
</body>
</html>

De este modo devolveremos una página html como esta:

 <?xml version="1.0" encoding="UTF-8" ?>
 <!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>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <title>ERROR</title>
 </head>
 <body>

 <h2>Ha ocurrit un error en l'aplicacion.</h2>
 <!--
       javax.servlet.ServletException: net.juantxu.encuesta.dao.DAOException: com.mysql.jdbc.exceptions.MySQLNonTransientConnectionException: Server connection failure during transaction. Due to underlying exception: 'java.net.ConnectException: Connection refused'.

** BEGIN NESTED EXCEPTION ** 

java.net.ConnectException
MESSAGE: Connection refused

STACKTRACE:

java.net.ConnectException: Connection refused
	at java.net.PlainSocketImpl.socketConnect(Native Method)
	at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
	at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
	at java.net.Socket.connect(Socket.java:519)
	at java.net.Socket.connect(Socket.java:469)
	at java.net.Socket.<init>(Socket.java:366)
	at java.net.Socket.<init>(Socket.java:208)
	at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:256)
	at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:276)
	at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2178)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2525)
	at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1737)
	at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1888)
	at org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:93)
	at net.juantxu.encuesta.dao.OficioDAO.getOficios(OficioDAO.java:41)
	at net.juantxu.encuesta.acciones.Inicio.getListaOficios(Inicio.java:41)
	at net.juantxu.encuesta.acciones.Inicio.muestraIndex(Inicio.java:28)
	at net.juantxu.encuesta.ServletControlador.procesarPeticion(ServletControlador.java:59)
	at net.juantxu.encuesta.ServletControlador.doGet(ServletControlador.java:24)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:690)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:263)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:584)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
	at java.lang.Thread.run(Thread.java:595)


** END NESTED EXCEPTION **


Attempted reconnect 3 times. Giving up. 
	   		
-->

Volver al <a href="/Encuesta">inicio</a>
</body>
</html>

Interficies para separar capas

Para separar el DAO del controlador podemos usar el patrón de diseño : FACTORY METHOD. En esta página puedes encontrar un modelo de ejemplo.

FACTORY METHOD ...

… en un plis plas

Pues como puedes apreciar en el dibujo…. La idea es que las acciones, en vez de usar clases DAO utilizen interficies DAO. Para obtener la instancia concreta del DAO se llama al el DAOFactory para que nos produzca el DAO deseado. Os acordais de lo que es el polimorfismo y las interficies ? Pues se trata de eso. Una vez que tenemos definidas las interficies se pueden implementar como queramos. De ese modo el controlador llama a el DAOFactory y le dice que le de un ObjetoDAO . El DAOFactory es el que tiene que saber que la implementación ObjetoDAOMysql es la implementación de la interfaz ObjetoDAO solicitada por el controlador. De ese modo, si en un futuro queremos cambiar la implementación ObjetoDAOMysql por la de ObjetoDAOXML pues sólo hay que decirselo al DAOFactory que ahora entregue el nuevo ObjetoDAOXML que implementa la interfaz ObjetoDAO.

Por lo que tenemos que crear las interficies y rehacer los DAO. Eso es tan facil como renombrar el dao OpinionDAO a OpinionDAOMysql. Una vez renombrado… ¡ Recuerda que yo estoy trabajando con Eclipse! En otros entornos de trabajo supongo que será parecido. Como nos vamos sobre la clase, le damos al boton derecho =⇒ Refactor =⇒ Extraer Interficie.

Seleccionas los miembros a incluir y le das el antiguo nombre del dao. Por lo que ahora tienes la interficie OpinionDAO y la implementación OpinionDAOMysql. Eso sirve para todo.

OpinionDAO.java

package net.juantxu.encuesta.dao;
import java.util.List;
import net.juantxu.encuesta.dto.Opinion;
import net.juantxu.encuesta.dto.Satisfaccion;

public interface OpinionDAO {
	public abstract void guardarOpinion(Opinion o) throws DAOException;
	public abstract List<Satisfaccion> getSatisfacciones() throws DAOException;
}

OpinionDAOMysql.java

package net.juantxu.encuesta.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import net.juantxu.encuesta.dto.Opinion;
import net.juantxu.encuesta.dto.Satisfaccion;

public class OpinionDAOMysql implements OpinionDAO {

	private Connection getConnexio() throws DAOException {
		Connection con;
  ...
}

Y por fin el DAOFacotry que no es mas que una clase a la que se le dice, para cada DAO posible, que complementación llamar

DAOFactory.java

package net.juantxu.encuesta.dao;

public class DAOFactory {

	private String AmbitoDAOClass="net.juantxu.encuesta.dao.AmbitoDAOMysql";
	private String OpinionDAOClass = "net.juantxu.encuesta.dao.OpinionDAOMysql";
	private String OficioDAOClass = "net.juantxu.encuesta.dao.OficioDAOMysql";
	private String EstadisticasDAOClass = "net.juantxu.encuesta.dao.EstadisticasDAOMysql";
	public AmbitoDAO getAmbitoDAO() throws DAOException{
		AmbitoDAO adao;
		try{
			adao = (AmbitoDAO) Class.forName(AmbitoDAOClass).newInstance();
		}catch(InstantiationException e){
			throw new DAOException(e);
		}catch(IllegalAccessException e){
			throw new DAOException(e);
		}catch(ClassNotFoundException e){
			throw new DAOException(e);
		}
		return adao;
	}
	public OpinionDAO getOpinionDAO() throws DAOException{
		OpinionDAO odao;
		try{
			odao = (OpinionDAO) Class.forName(OpinionDAOClass).newInstance();
		}catch(InstantiationException e){
			throw new DAOException(e);
		}catch(IllegalAccessException e){
			throw new DAOException(e);
		}catch(ClassNotFoundException e){
			throw new DAOException(e);
		}
		return odao;
	}
	public OficioDAO getOficioDAO() throws DAOException{
		OficioDAO odao;
		try{
			odao = (OficioDAO) Class.forName(OficioDAOClass).newInstance();
		}catch(InstantiationException e){
			throw new DAOException(e);
		}catch(IllegalAccessException e){
			throw new DAOException(e);
		}catch(ClassNotFoundException e){
			throw new DAOException(e);
		}
		return odao;
	}
	public EstadisticasDAO getEstadisticasDAO() throws DAOException{
		EstadisticasDAO edao;
		try{
			edao = (EstadisticasDAO) Class.forName(EstadisticasDAOClass).newInstance();
		}catch(InstantiationException e){
			throw new DAOException(e);
		}catch(IllegalAccessException e){
			throw new DAOException(e);
		}catch(ClassNotFoundException e){
			throw new DAOException(e);
		}
		return edao;
	}
}

Aqui te dejo un pelotazo de la aplicación para que puedas trastear.

Y aquí puedes ver la aplicación funcionando : Encuesta2

 
jee/5.txt · Última modificación: 2008/05/13 10:00 (editor externo)
 
Excepto donde se indique lo contrario, el contenido de esta wiki se autoriza bajo la siguiente licencia:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki