Acciones JSF basadas en clases internas de Java

0
15224

Acciones JSF basadas en clases internas de Java.

  1. Resumen
  2. Introducción
  3. Acciones simples basadas en clases internas
  4. Introducción de captura de excepciones en nuestro ActionMethod
  5. Reescritura del Bean de Login usando la AccionBase con control de excepciones
  6. Añadiendo flexibilidad y acceso a datos al ActionMethod
  7. Conclusión

1. Resumen

En este tutorial aprenderemos a crear nuestros backbeans para Java Server Faces implementando las acciones de los beans como clases internas de Java. Esto nos facilitará tanto la escritura de las mismas como la claridad con la que quedan reflejadas las funciones que se realizan en cada acción.

2. Introducción

Cuando desarrollamos aplicaciones JSF gran parte de la lógica de interfaz y negocio se desarrolla dentro de las acciones de los BackBeans, las clases Java asociadas a la página JSF que está sirviendo la petición actual. Por ejemplo, cuando el usuario pulsa el botón de «Inicio» en la página de inicio de sesión, el controlador de JSF invocará a la acción asociada al botón de inicio de esta página. Dicha acción será normalmente un método del backbean de la página de login.

Un ejemplo de código para la página y el backbean podría ser:


<%@ page contentType="text/html; charset=Cp1252" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=Cp1252"/>
        <title>Login
    </head>
    <body>
        <f:view>
            <h:form>
                UserID: 
                <br/>Password: 
                <br/>
            </h:form>
        </f:view>
    </body>
</html>

Y su backbean:


// SimpleLoginBean.java
package samples;

import java.io.Serializable;

public class SimpleLoginBean implements Serializable {
	private static final long serialVersionUID = 1L;
	
	public final static String OK = "OK";
	public final static String ERROR = "ERROR";
	
	private String username;
	private String password;
	
	private boolean logged;
	
	public SimpleLoginBean() {
		
	}

	// ------- Actions ---------------------
	
	/** very simple login action */
	public String login() {
		
		if (!"user".equals(username))
			return ERROR;
		if (!"sesamo".equals(password))
			return ERROR;
	
		logged = true;
		return OK;
	}
	
	// ------- Getters and setters ---------
	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public boolean isLogged() {
		return logged;
	}

	public void setLogged(boolean logged) {
		this.logged = logged;
	}
	
	
}

En este ejemplo la comprobación de usuario y contraseña es muy simple. Lo normal es que en una acción se mezclen varios tipos defunciones:

  1. Verificación de las precondiciones para la ejecución de la acción.
  2. Validación con lógica de negocio de los datos de entrada. No siempre las validaciones de los datos de entrada son simples (comprobaciones de formato, de tamaños, etc.) En aplicaciones complejas la validación de los datos puede involucrar realizar llamadas a diferentes partes de la lógica de negocio.
  3. Ejecución de lógicas de negocio relativas a la acción.
  4. Comprobaciones de resultados de la lógica de negocio.
  5. Actualización de los datos de la interfaz de usuario y del estado de la sesión de aplicación.
  6. Control de las excepciones y errores, con inclusión de mensajes de error o de confirmación en la página del usuario.
  7. Selección del flujo de salida para esta página.

Todo ello se adereza con las clásicas funciones auxiliares, como pueden ser:

  • Inserción de trazas de información, depuración y error.
  • Comprobaciones de seguridad (credenciales de usuario y otras).
  • Registro de acciones de usuario en tablas de auditoría, etc.

Si aplicamos esto a una acción JSF, el código de la acción realmente se complica. Un ejemplo podría ser
el siguiente: imaginemos una pantalla de una aplicación de gestión de un sistema de aprovisionamiento de servidores para un proveedor de Internet.

package samples;

public class LoadServerBackBean {

	/** stores the server type selected by customer */
	private String serverKind;

	final static String OK = "OK";
	final static String ERROR = "ERROR";

	// ----------- Actions ---------------

	/** creates a new server for a customer */
	public String loadServerActiion() {
		log("loadServerAction Action begin");

		DatabaseSession dbsession = null;
		UserBean user;

		try {
			user = UserBean.getUserBean();
			dbsession = DataBaseManager.getDatabaseSession();

			// validate if user can load a new server
			if (!validateUserForLoadServer(user, dbsession)) {
				insertResponseErrorMessage("loadServer.badUserValidation");
				log("loadServerActiion - User validation error");
				return "ERROR";
			}

			// validates server data
			if (!validateServerData()) {
				insertResponseErrorMessage("loadServer.badUserValidation");
				log("loadServerActiion - data server validation error");
				return "ERROR";
			}

			// Creates the new Server and preload servides for that server
			// (DNS, IP, email, etc)
			if (!createNewServer()) {
				insertResponseErrorMessage("loadServer.badUserValidation");
				log("loadServerActiion - Error creating a new Server");
				return "ERROR";
			}

			// updates Servers information for that user
			if (!upadateUserServers()) {
				insertResponseErrorMessage("loadServer.badUserValidation");
				log("loadServerActiion - Error creating a new Server");
				return "ERROR";
			}

			insertResponseErrorMessage("loadServer.badUserValidation");

			dbsession.commitTransaction();

		} catch (DatabaseException e) {
			logError("DatabaseException in loadServerActiion", e);
			insertResponseErrorMessage("loadServer.databaseError");
			return ERROR;
		} finally {
			if (dbsession.isOpen())
				dbsession.rollbackTransaction();
		}

		insertResponseErrorMessage("loadServer.loadSuccessfull");
		log("loadServerAction Action begin");
		return OK;
	}
...

Si nos fijamos en el código, gran parte del mismo son líneas que vamos a repetir de manera habitual cuando creemos las acciones de nuestra aplicación, para acciones similares a ésta. Sería más interesante poder reescribir este código de manera que fuese más sencillo y fácil de escribir y mantener. A estas alturas seguro que a todos ya se os han ocurrido varias formas de hacerlo…

3. Acciones simples basadas en clases internas

Bien, voy a proponer una forma sencilla de crear nuestras acciones utilizando las clases internas de Java. Las clases internas de Java nos dan algunas ventajas que nos van a venir bien a la hora de crear métodos para las acciones: Una clase interna siempre se crea dentro del contexto de una instancia de la clase contenedora, por lo que tiene acceso a los atributos privados de la instancia de la clase contenedora.

  • Podemos crear una instancia de nuestra clase interna dentro de un método de la clase contenedora.
  • Si la clase interna no tiene atributos el coste en demora para crear dicha instancia es pequeño.
  • Normalmente vamos a trabajar sobre los métodos de la clase interna y los datos estarán en la instancia de la clase contenedora, por lo que el uso de memoria será pequeño.
  • Además las clases internas se pueden tomar como clase base para nuevas clases internas en las clases derivadas de la clase contenedora.

Pongamos un ejemplo sencillo para ilustrar esta técnica:

	// ActionBaseBean.java
package samples;

public class SimpleActionBaseBean {


	public abstract class ActionMethod {
      String result;
      
      public String execute() {
        log("Execute begin");
        try {
          executeAction();
          log("Action OK");
          result = "OK";
        
        } catch (Exception e) {
          log("Exception in action" + e.getMessage());
          result =  "ERROR";
        }
      
        log("Execute end");
        return result;
      }
      protected abstract String executeAction();
  }
  
  // ------------- ACtion utility methods --------------
  
  private void log(String msg) {
    //TODO: do logging
  }

}

Veamos cómo podría quedar una acción de un bean de login implementada con esta clase:

// LoginBean.java
package samples;

import java.io.Serializable;

public class LoginBean extends SimpleActionBaseBean implements Serializable {

	private String username;
	private String password;

	public class ActionLogin extends ActionMethod {

		protected String executeAction() {

			if (!username.equals("admin"))
				return "ERROR";
			else if (!password.equals("sesamo"))
				return "ERROR";

			return "OK";
		}
	}

	// public action method for this bean
	public String login() {
		return new ActionLogin().execute();
	}

}

Como vemos la acción del bean de login sólo tiene código necesario para la lógica de negocio, careciendo de código adicional para la gestión de las excepciones o para la emisión de trazas.

Cuando se ejecuta el método de login de este bean dentro del contexto de una petición JSF, se crea una instancia de la clase interna ActionLogin dentro del método login() del bean, y esta es la responsable de gestionar tanto las excepciones como el resultado devuelto por la acción para los casos en los que hay problemas. Además la acción de login se está apoyando en los métodos definidos dentro de la clase base, por lo que los métodos de utilidad comunes quedan agrupados en la clase base.

En nuestro ejemplo hemos definido el método executeAction() como abstracto. Esto obligará a implementarlo cada vez que creemos una nueva acción dentro del bean

4. Introducción de captura de excepciones en nuestro ActionMethod

Vamos a dar un paso adicional en nuestra definición del ActionMethod. Para ello le vamos a añadir un cierto control sobre las excepciones que se generan. Ahora el método executeAction() que hay que sobrescribir en nuestras acciones puede lanzar excepciones de tipo ActionException.

// ############# ActionBase.java
package samples;

/**
 * Base class for actions
 * 
 * 
 */
public class ActionBase {
	
	// @ActionResult
	public final static String ACTION_OK = "OK";
	// @ActionResult
	public final static String ACTION_ERROR = "ERROR";

	public abstract class SimpleActionMethod {

		public String execute() {
			String result = ACTION_OK;
			log(">> Action method begin.");

			// add more generic stuff here

			try {

				result = executeAction();

			} catch (ActionException e) {
				log("Error ActionMethod.execute() action exception: ", e);
				result = ACTION_ERROR;
			} finally {
				log(">> Action method end.");
				// add finally stuff here
			}

			log(">> Action method end.");
			return result;
		}

		protected abstract String executeAction() throws ActionException;

	}
...

Como vemos ahora durante la ejecución de nuestra acción se pueden lanzar excepciones que provocarán la navegación hacia la página de error definida en nuestro flujo de navegación. Es muy habitual que al escribir las acciones comencemos comprobando las precondiciones y realizando las validaciones oportunas para cada acción. En nuestro ejemplo de LoadServerBackbean teníamos perlas de código de tipo:


// validate if user can load a new server
	if (!validateUserForLoadServer(user, dbsession)) {
		insertResponseErrorMessage("loadServer.badUserValidation");
		log("loadServerActiion - User validation error");
		return "ERROR";
	}

En nuestro bean de login vamos a tener también probablemente validaciones de tipo similar, por ejemplo validaciones de tamaños de nombres de usuario o contraseña y otras precondiciones. Ya sé que JSF proporciona mecanismos de validaciones sencillos, pero introducir las validaciones dentro de la acción le da robustez y nos dará más flexibilidad.

Vamos a añadir a nuestro ActionBase algunos métodos auxiliares del tipo que muestro en este ejemplo:


// ----------- Typical action utility methods -----

	public void validateEmptyField(String field, String fieldKey)
			throws ActionException {
		if (field == null || field.isEmpty()) {
			insertErrorMessageField("validation.emtyField", fieldKey);
			throw new ActionException("validation.emtyField " + fieldKey);
		}
	}

	public void validateFieldMinSize(String field, String fieldKey, int size)
			throws ActionException {
		if (field == null || field.length()  size) {
			insertErrorMessageField("validation.maxSizeField", fieldKey);
			throw new ActionException("validation.fieldMaxSize " + fieldKey);
		}
	}

	// ------------ Utility Methods -----------------

	/** Returns the real class name for the actual instance */
	public String getClassName() {
		return this.getClass().getName();
	}

	protected void log(String message) {
		// TODO: do logginh of the message
		System.out.println(message);
	}

	protected void log(String message, Exception e) {
		// TODO: do logginh of the message
		System.out.println(message + e.getMessage());
	}

	protected void insertMessage(String messageKey) {
		// TODO: inserts a message by messageKey in the request
		System.out.println("Insert message " + messageKey);
	}

	protected void insertErrorMessage(String messageKey) {
		// TODO: inserts a message by messageKey in the request
		System.out.println("Error message " + messageKey);
	}

Nota: dejo el código de inserción de mensajes en el contexto JSF para otro artículo, aunque es sencillo.

Como vemos, ahora se pueden escribir validaciones muy simples llamando a métodos como validateEmptyField(String field, String fieldKey). Extos métodos simplemente comprueban las precondiciones de la acción y si no se cumplen, disparan una excepción, por lo que se provocará la navegación a la página de error.

Hemos creado:

  • Una clase interna SimpleActionMethod que nos serviraá como base para implemntar nuestros métodos de acción como clases internas del bean.
  • Una serie de métodos auxiliares que permitirán reescribir las funciones más comunes de la lógica de interfaz o de negocio, como pueden ser las validaciones, las trazas, la inserción de mensajes en la respuesta JSF.

5. Reescritura del Bean de Login usando la AccionBase con control de excepciones

Ahora vamos a crear un bean de ejemplo que utiliza esta clase base:

// ########## SambpleAction.java
package samples;

public class SampleLoginBean extends ActionBase {
	
	private String username;
	private String password;
  
	
	//----------  Action method loginAction ---------------
	
	private class LoginAction extends ActionMethod {
		
		@Override
		protected String executeAction() throws ActionException {
			
			validateFieldMinSize(username, "login.username", 5);
			validateFieldMinSize(password, "login.password", 8);
			validateFieldMaxSize(username, "login.username", 40);
			validateFieldMaxSize(password, "login.password", 29);
			
			//do the real save action
			// if login != ok thorw new ActionEXception(....
			
			insertMessage("user.loggedin");

			return ACTION_OK;		
		}
	}
	
	/**
	 * Executes the login action in the lotin bean.
	 */     
	public String loginAction() {
		return new SampleLoginBean().execute();
	}

Examinemos el código:

  • Tenemos una clase interna y privada LoginAction que implementa la lógica de login para este bean.
  • Tenemos un método de acción público loginAction() que ejecuta la acción.

Las ventajas de esta forma de reescribir la lógica de interfaz y de negocio son evidentes:

  • Nuestra clase LoginAction está libre de código accesorio. Su escritura es simple, muy fácil de leer y de mantener.
  • Hemos usado un patrón de tipo «Salto de vallas» para nuestra acción, es decir, la acción se va ejecutando linealmente, evitando bifurcaciones, y si todas las etapas (obstáculos) de la acción se cumplen se retorna el «OK». Por cierto, aquel que utilice pruebas unitarias para probar sus clases entenderá las ventajas de usar este patrón, ya que la cobertura completa de los test es más fácil de lograr, debido a la ausencia de bifurcaciones.
  • Cualquier condición no cumplida o problema que aparezca durante la ejecución se resolverá lanzando una excepción de tipo ActionException, que será capturada por la clase SimpleActionMethod, controlando la correcta finalización de la acción.

6. Añadiendo flexibilidad y acceso a datos al ActionMethod

A continuación voy a poner como ejemplo una clase de ejemplo un poco más compleja, que añade nuevas características para hacerla extensible. Dejo al lector el estudio de la misma y el pensar cómo podría reescribir nuestro problema inicial (el aprovisionamiento de un serviodr) utilizando estas clases como punto de partida. Introducimos el siguiente código dentro de nuestra clase ActionBase:

...

	/**
	 * Base internal class for Action methods
	 */
	public class ActionMethod {

		public String execute() {
			String result = ACTION_OK;
			log(">> Action method begin.");

			initExecute();

			
			try {

				beforeExecute();

				result = executeAction();

				// TODO: different Exception catchs for different kind of
				// exceptions
				// catch (DBException e) {
				// insertMessage("application.defaultMessageDatabaseExceition
				// result = ERROR;
				//

				afterExecute();

			} catch (ActionException e) {
				log("Error ActionMethod.execute() action exception: ", e);
				result = ACTION_ERROR;
			} finally {
				finallyExecute();
			}

			finishExecute();

			log(">> Action method end.");
			return result;
		}

		/**
		 * Mothod to be overwriten in derived classes. This methods
		 * must implements the real execute action.
		 */
		protected String executeAction() throws ActionException {
			insertMessage("application.defaultMessageExceition");
			throw new ActionException("Error: new action must override "
					+ "ActionMethod.executeAction method for class "
					+ getClassName());
		}

		// Ectenxion methods that can be overridden

		protected void initExecute()  {
		}

		protected void beforeExecute() {
		}

		protected void afterExecute() {
		}

		protected void finishExecute() {
		}

		protected void finallyExecute() {
		}

	}

Para rizar el rizo, vamos a crear un ActionMethod interno que se encargue de la conexión a base de datos, para crear acciones que realizan consultas a base de datos de forma sencilla: más o menos podría quedar así:

	// ---------------- ActionDatabaseMethod --------------------------
	public class ActionDatabaseMethod extends ActionMethod {
	
		protected Session session;
		protected Transaction transaction;

		public ActionDatabaseMethod() {
			super();

			/** Begins the Database session and transaction */
		}

		protected void beforeAction() {
			super.beforeExecute();
			session = SessionFactory.getSession();
			transaction = session.getTransaction();
		}

		/** Commit transaction */
		protected void afterExecute() {
			transaction.commit();
			super.afterExecute();
		}

		// Do databaase rollback if neccesary */
		protected void finallyExecute() {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			super.finallyExecute();
		}

		/**
		 * Execute a database action inside a specific try/catch. This method
		 * wrap the action execution to isolate specific exception handling
		 * 
		 * @throws ActionException
		 */
		final public String executeAction() throws ActionException {

			String result;
			try {
				result = executeDatabaseeAction();
			} catch (DatabaseException e) {
				throw new ActionException("Database Exception "
						+ e.getMessage(), e);

			}
			return result;
		}

		/**
		 * Mothod to be overwriten in derived classes. This methods
		 * mustimplements the real execute action.
		 * 
		 * @throws DatabaseException
		 * @throws ActionException
		 */
		protected String executeDatabaseeAction() throws DatabaseException,
				ActionException {
			insertMessage("application.defaultMessageExceition");
			throw new ActionException("Error: new action must override "
					+ "ActionDatabaseMethod.executeDatabaseAction method for class "
					+ getClassName());
		}

	}

Esto es una aproximación a un código real. Seguro que a todos se nos ocurre cómo mejorar este código o adaptarlo a nuestros proyectos actuales.

Dejo para el lector la tarea de adaptar los beans de ejemplo utilzando nuestro ActionBase mejorado.

7. Conclusión

En este artículo hemos aprendido una forma sencilla de escribir nuestras acciones JSF de manera que toda la funcionalidad accesoria de la acción quede encapsulada en una clase interna Java. Con ello conseguimos:

  • Una gestión uniforme de la ejecución de las acciones JSF.
  • Mayor claridad en el código de las acciones JSF, que simplifica su creación y mantenimiento.

Nos veremos pronto en esta página con otros tutoriales.

Cristóbal González Almirón
Consultor de desarrollo de proyectos informáticos. Su experiencia profesional se ha desarrollado en empresas como Compaq, HP, Mapfre, Endesa, Repsol, Universidad Autónoma de Madrid, en las áreas de Desarrollo de Software (Orientado a Objetos), tecnologías de Internet, Técnica de Sistemas de alta disponibilidad y formación a usuarios.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad