martes, 27 de enero de 2015

Un proyecto en NetBeans de principio a fin (VIII). Integración transaccional de la lógica de negocio

¡Ey! Ya creíais que había terminado hasta el gorro de este tutorial y que no volveríais a ver un tostón de este calibre ¿Eh?. Lo cierto es que el trabajo y otros avatares (como que se me fue al garete el proyecto de NetBeans con el que estaba llevando este tutorial paso a paso...) han hecho que pase tanto tiempo desde la última entrada (creo que exactamente un año) que a punto he estado de darlo por perdido, olvidado, defenestrado y enterrado... Sin embargo aquí estamos de nuevo, al menos durante un capítulo más... ¡Qué no desfallezca el ánimo! (no hay otra forma de afrontar esto)...

En fin que me enredo, el objetivo de esta unidad es explicar las bondades del mapeo objeto-relacional, u ORM por sus siglas en inglés (Object-Relational Mapping), proporcionado por las tecnologías EJB y JPA para recolectar datos de una petición web y escribirlos en una base de datos en el back-end. Básicamente, ORM es una técnica de programación que permite convertir los datos entre el sistema de tipos usado por un lenguaje de POO y una base de datos relacional, utilizando un motor de persistencia.


En el contexto de la aplicación AffableBean nos centraremos en el procesamiento del pedido de un cliente cuando recibamos datos del formulario de pago. Vamos a crear un EJB OrderManager para procesar datos del formulario de pago junto con el objeto del carro de la compra. Este EJB realizará una transacción que implicará varias operaciones de escritura en la base de datos. Si alguna de las operaciones falla, la transacción hará un rollback.

ECHANDO UN VISTAZO A LA TRANSACCIÓN... 

 A fin de poder procesar datos del formulario de pago y los ítems contenidos en el carro de la compra, nuestro EJB, OrderManager, usa los datos proporcionados e implementa las siguientes acciones de escritura en la base de datos:
  • Añade un nuevo registro de cliente (Customer
  • Añade un nuevo registro de pedido (CustomerOrder
  • Añade nuevos registros para los productos solicitados (OrderedProduct), de acuerdo con el contenido del carrito.

Para hacer esto vamos a implementar un método (placeOrder) que realizará las tres acciones de escritura mediante la llamada secuencial a tres métodos privados auxiliares (addCustomer, addOrder y addOrderedItem). Para aprovechar el Servicio de Transacciones Gestionadas por Contenedor de EJB, requerimos dos anotaciones:
  • @TransactionManagement(TransactionManagementType.CONTAINER): Que se usa para especificar que algunas transacciones de la clase son gestionadas por contenedor. 
  • @TransactionAttribute(TransactionAttributeType.REQUIRED): Que se usa en el método que invoca la transacción, para especificar que, en caso de no existir, debe crearse una nueva transacción. 

Para simplificar un poco las cosas vamos a atacar este ejercicio dividiéndolo en tareas más digeribles:
  • Examinar la implementación del proyecto para esta unidad del tutorial
  • Crear el EJB OrderManager
  • Gestionar los parámetros de la petición
  • Implementar el método placeOrder y los métodos auxiliares
  • Utilizar las EntityManager de JPA
  • Sincronizar el contexto de persistencia con la base de datos
  • Configurar la transacción mediante programación

EXAMINANDO LA IMPLEMENTACIÓN DEL PROYECTO PARA ESTA UNIDAD

Para esta unidad del tutorial podemos descargar la implementación del proyecto desde AQUÍ. Viniendo del anterior capítulo, no vamos a notar grandes diferencias, salvo un par de excepciones: la página "confirmation.jsp" está completamente implementada y la hoja de estilos "affablebean.css" contiene algunas reglas específicas para esa página.

Con estas modificaciones podemos ejecutar el proyecto y probar a conciencia la aplicación, especialmente los pasos relacionados con todo el proceso del flujo de negocio. Cuando al final de la simulación del proceso de compra lleguemos a la página de checkout y pulsemos en el botón de submit, veremos una bonita página de confirmación que no mostrará ningún dato relacionado con el pedido. En realidad, en el estado actual, la aplicación no hará nada de nada con los datos procedentes del formulario de checkout... Pero para cuando termine esta lección, la aplicación será capaz de recoger los datos de un cliente y usarlos para procesar un pedido. Mostraremos un resumen del pedido realizado en la página de confirmación, borraremos el carro de la compra y finalizaremos la sesión del usuario.  

CREACIÓN DEL EJB ORDERMANAGER

Ahora, como ya hemos hecho otras veces, usaremos el asistente para crear un nuevo archivo: En la categoría Enterprise JavaBeans seleccionamos Session Bean. Lo llamaremos "OrderManager" y lo ubicaremos en el package "session". En lo demás aceptaremos las configuraciones por defecto (tipo de sesión stateless y no crearemos ninguna interfaz en el asistente). Al aceptar, la nueva clase OrderManager se genera y se abre en el editor.  

GESTIONANDO LOS PARÁMETROS DE LA PETICIÓN

Vamos a abrir el servlet del controlador, ControllerServlet, y vamos a localizar, dentro del método doPost, el lugar donde implementaremos la petición de compra (/purchase). Buscamos la anotación "TODO: Implement purchase action" y la sustituimos por el siguiente código:
 
// si se invoca la acción de compra
} else if (userPath.equals("/purchase")) {

    if (cart != null) {

        // extract user data from request
        String name = request.getParameter("name");
        String email = request.getParameter("email");
        String phone = request.getParameter("phone");
        String address = request.getParameter("address");
        String cityRegion = request.getParameter("cityRegion");
        String ccNumber = request.getParameter("creditcard");
    }

    userPath = "/confirmation";
}

IMPLEMENTANDO EL MÉTODO PLACEHOLDER Y LOS MÉTODOS AUXILIARES

Lo primero que haremos será añadir una referencia al EJB OrderManager en el ControllerServlet, en la parte superior de la clase, a continuación de los EJBs que ya existen:

@EJB 
private OrderManager orderManager; 

Y añadimos los imports correspondientes para el session.orderManager (pulsando Ctrl+Shift+I) A continuación vamos a pasar los parámetros extraídos de la petición -también el objeto cart- al método OrderManager.placeOrder añadiendo el siguiente código: 
 
// si se invoca la acción de compra
} else if (userPath.equals("/purchase")) {

    if (cart != null) {

        // extract user data from request
        String name = request.getParameter("name");
        String email = request.getParameter("email");
        String phone = request.getParameter("phone");
        String address = request.getParameter("address");
        String cityRegion = request.getParameter("cityRegion");
        String ccNumber = request.getParameter("creditcard");

 int orderId = orderManager.placeOrder(name,email,phone,address,cityRegion,ccNumber,cart);
    }

Obviamente el framework nos "canta" un error, ya que en realidad la clase orderManager todavía no tiene ningún método llamado "placeOrder"... No hay problema, pulsando en la bombillita que nos aparecerá a la izquierda, el IDE nos creará el método, con todos sus parámetros en la clase apropiada. Algo como esto: 
 
public class OrderManager {

    public int placeOrder(String name, String email, String phone, String address, String cityRegion, String ccNumber, ShoppingCart cart) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
[...]
Es más, también se nos añade a la clase el import correspondiente para cart.ShoppingCart. El método placeOrder de momento sólo nos lanza un aviso de que la funcionalidad todavía no se ha implementado, pero aún así hay que reconocer que esta facilidad para generar código mola bastante...

Vamos a sustituir el código autogenerado en la función por lo siguiente: 
 
public int placeOrder(String name, String email, String phone, String address, String cityRegion, String ccNumber, ShoppingCart cart) {

    Customer customer = addCustomer(name, email, phone, address, cityRegion, ccNumber);
    CustomerOrder order = addOrder(customer, cart);
    addOrderedItems(order, cart);
    return order.getId();
}

Y luego vamos a dejar que las bombillitas hagan de nuevo el trabajo por nosotros; que añadan los imports, que generen el esqueleto de los nuevos métodos de la clase y que haga todas sus cosillas (luego, si eso, ya rellenaremos nosotros los métodos...). 

Pues eso, que vamos a comenzar a implementar los nuevos métodos auxiliares. De momento sólo vamos a añadir código para aplicar los parámetros de entrada de cada método y así crear los nuevos objetos de entidad. 

addCustomer crea un nuevo objeto Customer y devuelve el resultado: 
 
private Customer addCustomer(String name, String email, String phone, String address, String cityRegion, String ccNumber) {

    Customer customer = new Customer();
    customer.setName(name);
    customer.setEmail(email);
    customer.setPhone(phone);
    customer.setAddress(address);
    customer.setCityRegion(cityRegion);
    customer.setCcNumber(ccNumber);

    return customer;
}

addOrder crea un nuevo objeto CustomerOrder y lo devuelve. Se utiliza la clase java.util.Random para generar un número de confirmación aleatorio. 
 
private CustomerOrder addOrder(Customer customer, ShoppingCart cart) {

    // set up customer order
    CustomerOrder order = new CustomerOrder();
    order.setCustomer(customer);
    order.setAmount(BigDecimal.valueOf(cart.getTotal()));

    // create confirmation number
    Random random = new Random();
    int i = random.nextInt(999999999);
    order.setConfirmationNumber(i);

    return order;
}

addOrderedItems realiza una iteración en ShoppingCart y crea los correspondientes OrderedProduct's. Para crear un OrderedProduct se utiliza la clase de entidad OrderedProductPK, cuya instancia se puede pasar al constructor de OrderedProduct (como se ve más abajo). 
 
private void addOrderedItems(CustomerOrder order, ShoppingCart cart) {

    List<ShoppingCartItem> items = cart.getItems();

    // iterate through shopping cart and create OrderedProducts
    for (ShoppingCartItem scItem : items) {

        int productId = scItem.getProduct().getId();

        // set up primary key object
        OrderedProductPK orderedProductPK = new OrderedProductPK();
        orderedProductPK.setCustomerOrderId(order.getId());
        orderedProductPK.setProductId(productId);

        // create ordered item using PK object
        OrderedProduct orderedItem = new OrderedProduct(orderedProductPK);

        // set quantity
        orderedItem.setQuantity(scItem.getQuantity());
    }
}

Finalmente añadimos los imports correspondientes (buenos los añade el IDE) y listo. 

UTILIZANDO LOS ENTITYMANAGER DE JPA

Como ya se dijo en la entrada VI de esta serie, sobre Clases de Entidad y Beans de Sesión, la API EntityManager está incluida en JPA y es responsable de implementar las operaciones de persistencia sobre la base de datos. En nuestro proyecto AffableBean todos los EJB usan EntityManager. Lo podemos comprobar abriendo en el editor cualquiera de los beans fachada de sesión. Podemos ver que todas las clases usan la anotación @PersistenceContext para para expresar su dependencia de una EntityManager gestionada por contenedor y su contexto de persistencia asociado (AffableBeanPU, tal como se especifica en el archivo "persistence.xml").

Para poder escribir en base de datos, el EJB OrderManager debe hacer algo parecido. Teniendo una instancia de EntityManager podremos modificar los métodos auxiliares (addCustomer, addOrder, addOrderedItems) de forma que los objetos de entidad que creen se escriban en base de datos.

Así, en OrderManager vamos a utilizar la anotación @PersistenceContext para expresar la dependencia de una EntityManager gestionada por contenedor y su contexto AffableBeanPU. También declararemos una instancia de EntityManager
 
@Stateless
public class OrderManager {

    @PersistenceContext(unitName = "AffableBeanPU")
    private EntityManager em;

    ...
}

Como siempre, añadimos los imports con Ctrl+Shift+I. Luego haremos las siguientes modificaciones en los métodos auxiliares: 

addCustomer 
 
private Customer addCustomer(String name, String email, String phone, String address, String cityRegion, String ccNumber) {

    Customer customer = new Customer();
    customer.setName(name);
    customer.setEmail(email);
    customer.setPhone(phone);
    customer.setAddress(address);
    customer.setCityRegion(cityRegion);
    customer.setCcNumber(ccNumber);

    em.persist(customer);
    return customer;
}

addOrder 
 
private CustomerOrder addOrder(Customer customer, ShoppingCart cart) {

    // set up customer order
    CustomerOrder order = new CustomerOrder();
    order.setCustomer(customer);
    order.setAmount(BigDecimal.valueOf(cart.getTotal()));

    // create confirmation number
    Random random = new Random();
    int i = random.nextInt(999999999);
    order.setConfirmationNumber(i);

    em.persist(order);
    return order;
}

addOrderedItems 
 
private void addOrderedItems(CustomerOrder order, ShoppingCart cart) {

    List<ShoppingCartItem> items = cart.getItems();

    // iterate through shopping cart and create OrderedProducts
    for (ShoppingCartItem scItem : items) {

        int productId = scItem.getProduct().getId();

        // set up primary key object
        OrderedProductPK orderedProductPK = new OrderedProductPK();
        orderedProductPK.setCustomerOrderId(order.getId());
        orderedProductPK.setProductId(productId);

        // create ordered item using PK object
        OrderedProduct orderedItem = new OrderedProduct(orderedProductPK);

        // set quantity
        orderedItem.setQuantity(String.valueOf(scItem.getQuantity()));

        em.persist(orderedItem);
    }
}

El método de persistencia de EntityManager no escribe inmediatamente el objeto en base de datos. Lo que hace dicho método es colocar el objeto en un contexto de persistencia. Esto significa que EntityManager tiene la responsabilidad de asegurar que el objeto de la entidad esté sincronizado con la base de datos. El contexto de persistencia es algo así como un estado intermedio usado por el EntityManager para pasar entidades entre el mundo de los objetos y el mundo relacional (de ahí viene lo de "mapeo objeto-relacional").

El contexto de persistencia (definido por la anotación @PersistenceContext) puede llevar varios elementos opcionales; nosotros estamos usando unitName donde determinamos el nombre de la unidad de persistencia definida en "persistence.xml". Otro elemento interesante es type, que permite definir el alcance del contexto de persistencia. La documentación dice más o menos que type especifica si se va a utilizar un contexto de persistencia en el ámbito de la transacción o un contexto de persistencia extendido. Se refiere al ámbito de la transacción cuando el contexto se crea al iniciarse la transacción y se elimina cuando la transacción termina. Un contexto de persistencia extendido se aplica sólo a los beans de sesión con estado y abarca varias transacciones. Por defecto, el valor de type es javax.persistence.PersistenceContextType.TRANSACTION, es decir, el ámbito de la transacción. Puesto que no lo especificamos, los objetos de EntityManager se comportan de acuerdo al valor por defecto, es decir que su alcance es la transacción.

SINCRONIZANDO EL CONTEXTO DE PERSISTENCIA CON LA BASE DE DATOS

A estas alturas deberíamos asumir que, con transacción o sin ella, nuestro OrderManager es capaz de escribir satisfactoriamente objetos de entidad en la base de datos. Vamos a probarlo, a ver cómo se procesan los pedidos de un cliente...

Vamos a ejecutar la aplicación y a avanzar decididamente a través del proceso de negocio hasta llegar a la página de checkout. Lo único recomendable es no meter la pata al introducir los datos, aunque sea lo justito para que en el proceso de escritura no nos casque algún error de SQL (al menos hasta que veamos más adelante la cuestión de la validación de datos...).

Una vez que llegamos a la página de pago, con nuestro carrito lleno de cosas cardiosaludables, y hemos rellenado el formulario, pulsamos con total resolución el botón de submit y... ¡¡¡PERO QUÉ COJ*!!!¿Un error "HTTP Status 500"?...

Bien, tranquilos, que no cunda el pánico. Si examinamos el log del servidor probablemente nos vamos a encontrar con algún que otro NullPointerException. Resulta que el método order.getId que estamos llamando en el método addOrderedItems de bean de sesión OrderManager.java nos está cascando un NULL como una casa, más que nada porque el método getId está intentando obtener el ID de un pedido que actualmente está en proceso de ser creado. Dado que el identificador del pedido es una clave primaria autonumérica, la base de datos genera el valor sólo cuando el registro es añadido... ¡Toma castaña!

Una forma de salvar este percance es sincronizar el contexto de persistencia con la base de datos, y esto se hace mediante el método flush del EntityManager. Basta añadir lo siguiente en el método addOrderedItems
 
private void addOrderedItems(CustomerOrder order, ShoppingCart cart) {

    em.flush();

    List<ShoppingCartItem> items = cart.getItems();

    ...
}

Ahora deberíamos poder ejecutar el proyecto y navegar por el proceso de negocio hasta la página de checkout. Esta vez, al hacer submit, debe mostrarse la página de confirmación. Podemos comprobar que los datos del pedido están correctamente grabados en la base de datos.

CONFIGURANDO LA TRANSACCIÓN MEDIANTE PROGRAMACIÓN

La finalidad principal de una transacción es asegurar que todas las operaciones sean correctamente realizadas, y de no ser así evitar que cualquier operación individual se realice. A continuación veremos cómo las operaciones de escritura que tienen lugar en el placeOrder son tratadas como una transacción simple.

Lo primero que haremos será añadir las anotaciones correspondientes en el EJB OrderManager relacionadas con transacciones: 
 
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class OrderManager {

    @PersistenceContext(unitName = "AffableBeanPU")
    private EntityManager em;

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public int placeOrder(String name, String email, String phone, String address, String cityRegion, String ccNumber, ShoppingCart cart) {
 ...

La anotación @TransactionManagement sirve para especificar que las transacciones que ocurren en el EJB OrderManager son gestionadas por contenedor. La anotación @TransactionAttribute sobre placeOrder especifica que cualquier operación que ocurra dentro del método debe ser tratada como una transacción... En realidad estas anotaciones no son necesarias ya que, según la especificación de EJB, las transacciones gestionadas por contenedor están activadas por defecto para los beans de sesión. Es más, según la documentación de Javadoc, CONTAINER y REQUIRED son los TransactionManagementType y TransactionAttributeType por defecto. Sin embargo mejora la legibilidad del código.

También vamos a añadir un bloque try-catch dentro del método placeOrder para asegurarnos que que devuelva 0 cuando el pedido no pueda ser procesado: 
 
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public int placeOrder(String name, String email, String phone, String address, String cityRegion, String ccNumber, ShoppingCart cart) {

    try {
        Customer customer = addCustomer(name, email, phone, address, cityRegion, ccNumber);
        CustomerOrder order = addOrder(customer, cart);
        addOrderedItems(order, cart);
        return order.getId();
    } catch (Exception e) {
        return 0;
    }
}

Desafortunadamente colocar las llamadas a los tres métodos en la clausula try implica que, si una de ellas falla en tiempo de ejecución, el motor saltará directamente a la clausula catch, pasándose por el forro cualquier operación de rollback que pudiera devolver la base de datos a un estado estable. Para solucionar este problema prepararemos explícitamente la transacción para hace rollback en la clausula catch
 
@PersistenceContext(unitName = "AffableBeanPU")
private EntityManager em;
@Resource
private SessionContext context;

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public int placeOrder(String name, String email, String phone, String address, String cityRegion, String ccNumber, ShoppingCart cart) {

    try {
        Customer customer = addCustomer(name, email, phone, address, cityRegion, ccNumber);
        CustomerOrder order = addOrder(customer, cart);
        addOrderedItems(order, cart);
        return order.getId();
    } catch (Exception e) {
        context.setRollbackOnly();
        return 0;
    }
}

La anotación @Resource se usa para tomar una instancia del contexto de sesión actual y utilizar su método setRollbackOnly para poder hacer rollback en la transacción.

Llegados a este punto podemos decir que hemos integrado satisfactoriamente la transacción en el proyecto AffableBean. Como esto ya se está alargando más de la cuenta (mucho más), vamos a ir a la fuente original de este curso para descargarnos una de ese implementaciones del proyecto tan chulas en las que todo funciona guay, y que añade algunas cosas más para poder continuar. Pincha AQUÍ.

Nos encontraremos con que tenemos implementado un nuevo método, getOrderDetails, en el EJB OrderManager, que reúne los detalles del pedido realizado. Si la transacción finaliza correctamente, el Servlet del Controlador coloca los detalles del pedido en el ámbito de la petición, destruye el objeto del carrito, termina la sesión y envía la petición a la vista de confirmación. Si la transacción falla, el ControllerServlet lanza un error y envía la respuesta a la vista del pago, permitiendo al usuario reenviar su pedido.  

VALIDANDO Y CONVIERTIENDO DATOS INTRODUCIDOS POR EL USUARIO

En la última versión que hemos descargado del proyecto, también tenemos implementadas validaciones del lado del cliente y del servidor para el formulario de pago. La validación de formularios es el proceso por el cual se comprueba que un formulario ha sido correctamente cumplimentado antes de procesarlo. La validación de un formulario persigue un doble objetivo: Por un lado ayuda al usuario a introducir los datos de forma válida, por otro lado ayuda a frustrar intentos maliciosos de enviar datos que puedan afectar negativamente al proceso o al almacenamiento.

Esencialmente existen dos métodos para validar formularios: del lado del servidor (en nuestro caso, usando Java) y del lado del cliente (mediante JavaScript). La validación del lado del cliente es útil para facilitar información al usuario, sin necesidad de emprender un viaje de ida y vuelta al servidor. Sin embargo, dado que los browsers permiten desactivar JavaScript, se hace necesaria una validación en el lado del servidor para asegurar que los datos del formulario son válidos. En este caso, cuando los datos del formulario llegan al servidor, estos se extraen de la petición y se comprueban antes de ser procesados/almacenados. Si se detecta un error de validación, el servidor envía una respuesta de vuelta al cliente. 

Validación del lado del cliente

Para hacer la validación del lado del cliente vamos a usar la librería jQuery. En la última descarga de la aplicación tenemos una nueva carpeta "js" donde podemos encontrar la librería de jQuery así como el script para el plugin de validación. La librería jQuery es referenciada en "header.jspf" y el plugin de validación es referenciado únicamente en "checkout.jsp" (pues es el único archivo que lo necesita). El plugin es adaptado para ajustarse al formulario de pago de acuerdo a la documentación disponible aquí
 
<script type="text/javascript">

    $(document).ready(function(){
        $("#checkoutForm").validate({
            rules: {
                name: "required",
                email: {
                    required: true,
                    email: true
                },
                phone: {
                    required: true,
                    number: true,
                    minlength: 9
                },
                address: {
                    required: true
                },
                creditcard: {
                    required: true,
                    creditcard: true
                }
            }
        });
    });
</script>

El IDE proporciona soporte para jQuery, permitiendo completar código y acceder a documentación (pulsando ctrl+space). Además nos permite especificar que navegadores serán los objetivos de nuestra aplicación, y es capaz de advertir sobre funciones no soportadas por los browsers seleccionados. 

Validación del lado del servidor

El propósito de la la validación del lado del servidor es asegurar que los datos están en un formato adecuado para su posterior procesamiento o para su almacenamiento. Entendemos por "formato" tanto el tipo de los datos como su tamaño. Las clases de las entidades JPA deben asegurar el mapeo de sus propiedades a los correspondientes tipos de datos definidos para las columnas de las tablas de la base de datos.

La validación del lado del servidor en el proyecto de AffableBean se implementa por medio de una clase Validator. El servlet del controlador crea un objeto Validator e invoca a su método validateForm con los datos del usuario: 
 
// si la validación encuentra un error, devuelve al usuario al formulario de checkout
if (validationErrorFlag == true) {
    request.setAttribute("validationErrorFlag", validationErrorFlag);
    userPath = "/checkout";

    // si no hay error se salva el pedido en base de datos
} else {

    ...
}

Y así quedaría el formulario de pago con el control de errores: 
 
<form id="checkoutForm" action="<c:url value='purchase'/>" method="post">
    <table id="checkoutTable">
      <c:if test="${!empty validationErrorFlag}">
        <tr>
            <td colspan="2" style="text-align:left">
                <span class="error smallText">Please provide valid entries for the following field(s):

                  <c:if test="${!empty nameError}">
                    <br><span class="indent"><strong>name</strong> (e.g., Bilbo Baggins)</span>
                  </c:if>
                  <c:if test="${!empty emailError}">
                    <br><span class="indent"><strong>email</strong> (e.g., b.baggins@hobbit.com)</span>
                  </c:if>
                  <c:if test="${!empty phoneError}">
                    <br><span class="indent"><strong>phone</strong> (e.g., 222333444)</span>
                  </c:if>
                  <c:if test="${!empty addressError}">
                    <br><span class="indent"><strong>address</strong> (e.g., Korunní 56)</span>
                  </c:if>
                  <c:if test="${!empty cityRegionError}">
                    <br><span class="indent"><strong>city region</strong> (e.g., 2)</span>
                  </c:if>
                  <c:if test="${!empty ccNumberError}">
                    <br><span class="indent"><strong>credit card</strong> (e.g., 1111222233334444)</span>
                  </c:if>

                </span>
            </td>
        </tr>
      </c:if>

      ...
    </table>
</form>

La implementación de la validación del lado del servidor en este caso sirve únicamente para demostrar como es posible incorporarla en nuestros proyectos. La lógica de validación contenida en la clase Validator se ciñe a lo más básico y dista bastante (obviamente) de lo que sería deseable en un entorno real de producción.  

Conversión de datos

Algunas veces, después de que los datos hayan pasado la validación, es necesario convertirlos a un formato diferente. Por ejemplo, esto sería aplicable a datos que el usuario ha introducido a mano, o números que se reciben como cadenas de caracteres pero que posteriormente deben ser usados en cálculos. Esta conversión de datos es un paso importante que debe ser tenido en cuenta en el lado del servidor.

Aunque no es algo que vayamos a implementar en la aplicación AffableBean, conviene no perderlo de vista. Por ejemplo, consideremos de nuevo nuestro formulario de pago, ahí tenemos un campo en el que debería introducirse un número de tarjeta de crédito. Tanto en el lado del cliente como en el del servidor la validación permite diferentes formatos numéricos, por ejemplo se podría aceptar el número:

1111222233334444

aunque también

1111-2222-3333-4444

o incluso 

1111 2222 3333 4444

Debido a la naturaleza ambigua en la forma en que podemos adquirir estos datos, se hace necesario poder descartar los guiones -o cualquier otro carácter no numérico- antes de continuar con el proceso de pago, y este paso debe ocurrir justo antes de que los datos queden almacenados.

No hay comentarios:

Publicar un comentario