Jun 04

Es difícil encontrar un buen y completo ejemplo (o tutorial) de cómo validar (autenticar) un usuario mediante LDAP desde una aplicación Java, y es más difícil encontrar un tutorial en español.

En este ejemplo supondremos que el servidor LDAP es OpenDJ (antes OpenDS)

http://www.forgerock.com/opendj.html

Pero este tutorial aplica tanto para Oracle OpenDS como para ForgeRock OpenDJ, porque están basado en los mismos fuentes.

OpenDJ es un “Directory Server” Open Source, que implementa el protocolo LDAP.

Un “Directory Server” es una base de datos “optimizada” que puede guardar todo tipo de información, pero su uso se ha extendido principalmente como Base de Datos de perfilación, y de componentes de dominio.

Un “Directory Server” puede guardar todo tipo de información, maneja índices, filtros y mecanismos de búsqueda muy eficientes, las bases de datos relacionales están basadas en esta tecnología, podríamos decir que un “Directory Server” es equivalente al “Lenguaje C”, y una “Base de Datos Relacional” es equivalente a un lenguaje de alto nivel como “Java” (que se basa en C), en otras palabras si queremos manejar data en tiempo real y con alta carga, una opción adecuada puede ser usar Directory Server.

La información en LDAP se guarda en forma de árbol (tree) con subniveles “ilimitados” parecido a una estructura XML, maneja conceptos de atributos y clases de objetos (objectClass). Veremos una estructura básica de usuarios-organización:

Suponemos que la estructura anterior se implementó usando el “Control Panel” de OpenDJ

Existen algunos conceptos y  atributos básicos de LDAP

  • DN: “Distingished Name”, corresponde al path completo de atributos que referencian únicamente a un registro LDAP, en el ejemplo del árbol para el usuario “cn=juan perez” su DN es:

cn=juan perez,ou=depto_informatica,dc=soaagenda,dc=com

Haciendo una equivalencia con bases de datos relacionales, son los campos     llave con que se identifica únicamente un registro.

  • cn: “common name” corresponde al nombre común del elemento para un usuario su nombre y apellido.
  • ou: “organizational unit” nombre de un departamento de la organización, puede identificar una empresa, una sucursal, un departamento, o un área de la Organización. Tambien se usa para identificar un grupo de personas como “clientes”.
  • dc: “domain component” sire para identificar el dominio, se usar un par por ejemplo para www.soaagenda.com se define con “dc=soagenda, dc=com”.

Otros atributos de un usuario:

  • sn: “surname” es el apellido.
  • givenName: es el nombre de pila
  • uid: “user id” una identificación única más orientado a sistema, mientras que “common name” (cn) está más orientado a personas (nombre común), por ejemplo un uid puede ser un código numerico.
  • userPassword: password del usuario esta encriptada en SHA.

Para nuestro ejemplo:

  • cn= juan perez
  • uid=jperez
  • givenName=Juan
  • sn=Perez
  • mail=juan.perez@soaagenda.com
  • userPassword= claveY
  • telephoneNumber=5550000

Bueno, a lo que vinimos, este es el código de ejemplo para validar un usuario y obtener sus datos.

package com.soaagenda;

import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

/**
 *
 * @author soaagenda.com
 */
public class UtilsLDAP {
   
    private String INITCTX = "com.sun.jndi.ldap.LdapCtxFactory";
    private String HOSTLDAP = "ldap://localhost:389";
    private String BASE = "DC=soaagenda,DC=com";

    public boolean validaUsuario(String userDN, String pass) {           
        Hashtable env = new Hashtable();
        if (pass.compareTo("") == 0 || userDN.compareTo("") == 0)
            return false;
        
        env.put(Context.INITIAL_CONTEXT_FACTORY,INITCTX);
        env.put(Context.PROVIDER_URL, HOSTLDAP);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, new String(userDN));  
        env.put(Context.SECURITY_CREDENTIALS,new String(pass));
        
        DirContext ctx=null;
        try {
            ctx = new InitialDirContext(env);
        } catch (NamingException ex) {
            Logger.getLogger(UtilsLDAP.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }

        try {
            if (ctx!=null) ctx.close();
        } catch (NamingException ex) {
            Logger.getLogger(UtilsLDAP.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }        
        return true;   
    }    
    
   public String obtieneUsuario(String adminDN, String adminPass, String uidBuscar) {           
        Hashtable env = new Hashtable();
 
        env.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX);  
        env.put(Context.PROVIDER_URL, HOSTLDAP);   
        env.put(Context.SECURITY_AUTHENTICATION, "simple");  
        env.put(Context.REFERRAL, "follow" );
        env.put(Context.SECURITY_PRINCIPAL, new String(adminDN));  
        env.put(Context.SECURITY_CREDENTIALS,new String(adminPass));  

       DirContext dctx=null;
       try {
            dctx = new InitialDirContext(env);

        
            String base = BASE;
            String filter = "(&(objectClass=organizationalPerson)(uid="+uidBuscar+"))";

            SearchControls controls = new SearchControls();

            String []strReturningAttr = {"member","uid","sn","cn","mail","userPassword"};

            controls.setReturningAttributes(strReturningAttr);
            controls.setSearchScope(SearchControls.SUBTREE_SCOPE);

            NamingEnumeration answer= null;
            try {
                answer = dctx.search(base, filter, controls);
            } catch (NamingException ex) {
                Logger.getLogger(UtilsLDAP.class.getName()).log(Level.SEVERE, null, ex);
                ex.printStackTrace();
                return "error";
            }

            while (answer.hasMoreElements()) {
                SearchResult sr;
                try {
                    sr = (SearchResult)answer.next();
                } catch (NamingException ex) {
                    Logger.getLogger(UtilsLDAP.class.getName()).log(Level.SEVERE, null, ex);
                    ex.printStackTrace();
                return "error";
                }

                Attributes attrs = sr.getAttributes();

                Attribute attr = attrs.get("mail");
                String mailX="";
                if (attr!=null) try {
                    mailX=(String) attr.get();
                } catch (NamingException ignoreX) {
                    Logger.getLogger(UtilsLDAP.class.getName()).log(Level.SEVERE, null, ignoreX);
                    ignoreX.printStackTrace();
                }

                attr = attrs.get("userPassword");
                Object passW=null;
                if (attr!=null) try {
                    passW= attr.get();
                } catch (NamingException ignoreX) {
                    Logger.getLogger(UtilsLDAP.class.getName()).log(Level.SEVERE, null, ignoreX);
                    ignoreX.printStackTrace();
                }
                
                return sr.getName()+"\nmail="+mailX+"\npassw="+passW.toString();    

            }//while

        }catch (NamingException e) {
            e.printStackTrace();
            return "error";
        }finally{
            try{
                if (dctx!=null) dctx.close();
            }catch (Exception ignore){}
        }        

        return "usuario no existe";   
    }
    
    public static void main(String[] argv) {
        UtilsLDAP utilX = new UtilsLDAP();

        if (utilX.validaUsuario("cn=Directory Manager","primeras")) 
               System.out.println("USUARIO ADMIN VALIDO");
        else
               System.out.println("USUARIO o PASSWORD ERRONEO PARA ADMIN");

        if (utilX.validaUsuario("cn=juan perez,ou=depto_informatica,dc=soaagenda,dc=com","claveY")) 
               System.out.println("USUARIO VALIDO");
        else
               System.out.println("USUARIO o PASSWORD ERRONEO");
        
        String usuarioX=  utilX.obtieneUsuario("cn=Directory Manager","primeras","jperez"); 
        System.out.println(usuarioX);
    }    
    
}

Lo principal en esta aplicación es que define al principio el servidor LDAP:

private String HOSTLDAP = “ldap://localhost:389″;

private String BASE = “DC=soaagenda,DC=com”;

Y la función de validación se basa en el DN y la password (userPassword)

validaUsuario(“cn=juan perez,ou=depto_informatica,dc=soaagenda,dc=com”,”claveY”)

La aplicación retorna el siguiente resultado (System Out)

USUARIO ADMIN VALIDO

USUARIO VALIDO

cn=juan perez,ou=depto_informatica

mail=juan.perez@soaagenda.com

passw=[B@174cc1f

May 30

Uno de los puntos que viene poco explicado en OpenDS (Directory Server o LDAP Server) es como instalar la aplicación como servicio, en este artículo lo vamos a explicar.

Este articulo supone que se bajó  OpenDS (download)

http://opends.java.net/public/downloads_index.html

Y se instaló siguiendo el tutorial:

http://java.net/projects/opends/pages/2_2_InstallationGuide

El cual obviamente no explica como subirlo como servicio Linux. Vamos a suponer que OpenDS quedo instalado en “/opt/OpenDS-2.2.1/bin/” para el resto de este tutorial.

Se supone que con este comando se genera el script del servicio…

./create-rc-script -f /etc/init.d/opends

Pero el “script” generado tiene algunos problemas, por ejemplo si lo queremos registrar con “chkconfig”:

chkconfig –add opends

Da el siguiente error:

service opends does not support chkconfig

Esto debido a que no viene indicado la descripción del servicio, que normalmente se define como comentario:

# chkconfig: 2345 20 80
# description: OpenDS Directory and LDAP Server
# processname: opends

Y otro problema es que el script no maneja la opción “status” que sirve para que el sistema operativo cuando trate de levantar el servicio sepa si este realmente está “arriba”.

Aquí está el script completo y corregido, este lo deben copiar en “/etc/init.d/opends” (pisando el que ya generamos).

#!/bin/sh
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (the "License").  You may not use this file except in compliance
# with the License.
#
# You can obtain a copy of the license at
# https://OpenDS.dev.java.net/OpenDS.LICENSE.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at
# trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
# add the following below this CDDL HEADER, with the fields enclosed
# by brackets "[]" replaced with your own identifying information:
#      Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
# chkconfig: 2345 20 80
# description: OpenDS Diarectory and LDAP Server
# processname: opends

# Set the path to the OpenDS instance to manage
INSTALL_ROOT="/opt/OpenDS-2.2.1"
export INSTALL_ROOT

# Specify the path to the Java installation to use
OPENDS_JAVA_HOME="/usr/java/jdk1.6.0_31"
export OPENDS_JAVA_HOME

cd ${INSTALL_ROOT}

# Determine what action should be performed on the server
case "${1}" in
start)
"${INSTALL_ROOT}/bin/start-ds" --quiet
exit ${?}
;;
stop)
"${INSTALL_ROOT}/bin/stop-ds" --quiet
exit ${?}
;;
restart)
"${INSTALL_ROOT}/bin/stop-ds" --restart --quiet
exit ${?}
;;
status)
"${INSTALL_ROOT}/bin/status" -D "cn=Directory Manager" -w primeras -X -s
exit ${?}
;;
*)
echo "Usage:  $0 { start | stop | restart | status}"
exit 1
;;
esac
exit 0

Ahora si podemos registrar el servicio sin problema

chkconfig –add opends

Reiniciamos el sistema Linux, y luego lo probamos con la siguiente comando OpenDS (situándonos en el path “/opt/OpenDS-2.2.1/bin/”)

./status -D “cn=Directory Manager” -w password -X -s

Donde “password” debe corresponder a la clave del administrador del “Directory Server”, que se indicó cuando se instaló OpenDS. Este comando debe retornar

“Server Run Status: Started….”

También podemos probarlo usando la consola de OpenDS

./control-panel

Jan 23

“Reverse Ajax” visto desde “afuera”, visto desde el punto de vista de un “usuario”, es el mecanismo que permite que la información en una aplicación Web se “refresqué” automáticamente sin necesidad que lo solicite explícitamente (ejemplo; sin apretar un botón de “actualizar”), y donde no se tenga que volver a cargar la página completa (Ajax). Un ejemplo de esto sería una aplicación de la Bolsa de Comercio donde se muestran los valores de las acciones, los cuales se van actualizando automáticamente a medida que cambia el valor, y no cada vez que el usuario presiona un botón.

Una forma de “simular” esto, es que la pagina (cliente web) tenga un ciclo de consulta recurrente cada X segundos (un loop), el problema es que estriamos llenando de solicitudes (peticiones, o request) el servidor, lo que es poco eficiente.

“Reverse Ajax” corresponde a enviar información desde un servidor a los distintos clientes (Web) sin la necesidad explicita de una solicitud (request) por parte del cliente, este es el concepto de “empujar” (push) la información. Normalmente la información se entrega (push) de acuerdo a un evento que sucede en el servidor, por ejemplo actualización del valor de una acción.

Para lograr esto, los clientes Web (Browser) deben quedar esperando (escuchando) la información que pueda venir del servidor, y el servidor distribuye la información entre los distintos clientes “activos”.

“Reverse Ajax” tiene varias versiones e implementaciones, la técnica estándar es “Long pooling” usando el objeto Request de Ajax, a esta técnica también se le llama Comet. Y las implementaciones más usadas son los frameworks:

• Jetty Continuation:
• CometD
• Grizzly

Las dos primeras son independientes de Servidor de Aplicaciones, trabajan tanto en Jetty, Tomcat, GlassFish, o WAS, pero requieren que el servidor maneje “Servlet 3.0”. La tercera es propietaria de Glassfish, pero cumple los estándares, es decir puede atender a cualquier cliente Comet, y esta optimizada para este servidor.

Aquí hay un ejemplo completo de “reverse Ajax” con Grizzly (Glassfish)

http://docs.oracle.com/cd/E19776-01/820-4496/6nfv5l568/index.html

Este es un tutorial de Jetty y DWR

http://www.ibm.com/developerworks/java/library/j-jettydwr/

Implementacion usando servlets

http://www.ibm.com/developerworks/web/library/wa-cometjava/

Excelente introducción a COMET y revision de implementacion con CometD

http://www.ibm.com/developerworks/web/library/wa-reverseajax1/index.html?ca=drs-

Nov 22

Para calcular la cantidad de días entre dos fechas, no existe una solución en las librerías comunes de Java (java.util.date). Una solución es el siguiente código:

public long periodo(Date fecha1, Date fecha2){
if (fecha1==null || fecha2==null){
return 0;
}
long milli1=fecha1.getTime();
long milli2=fecha2.getTime();
long diffMillis= milli2 - milli1;
if (diffMillis<0) diffMillis= -diffMillis;
long diffDays = diffMillis/(24*60*60*1000);

return diffDays;
}

Pero existen libreras que realizan estas funcionalidades y otras de forma bastante completa  como

  • Date4J

http://www.date4j.net/

  • Joda-Time

http://joda-time.sourceforge.net/

Nov 19

Esta es una vulnaberabilidad de un sitio Web que permite que se  pueda inyectar codigo en una pagina, como inyectra codigo Javascript.

Por ejemplo supongamos hay un sitio Web que tiene un formulario (form) que solicita el login del usuario, el cual se valida y si no es correcto se devuelve una pagina Web indicando “el nombre de usuario XXX es incorrecto”, donde “XXX” corresponde al dato ingresado (nombre usuario) en el formulario.

Si uno ingresa “DIEGO” como nombre de usuario y este no es válido, entonces se va a desplegar:

el nombre de usuario DIEGO es incorrecto


Lo que esta bien, pero el problema es cuando se coloca este texto

“<script>alert(19700328)</script>”

como nombre de usuario, y la pagina muestra una alert Javascript con el numero “19700328”.

En este caso, esa pagina es vulnerable en “Cross Site Scripting”.

Este es el codigo de la pagina formulario del ejemplo:

< %@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %>
< !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>form cross scripting</title>
</head>
<body>
<form id="formID" name="formID" method="post" action="paginaVulnerable.jsp">
<label>Usuario:
<input name="usuarioID" type="text" id="usuarioID" size="30" />
</label>
<br />
<label>
Clave:
<input type="password" name="passwordID" id="passwordID" />
</label>
<input type="submit" name="ok" id="ok" value="OK" />
</form>
</body>
</html>

Y este es el código de la pagina que es “vulnerable”, es decir, se le puede inyectar javascript:

< %@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %>
< !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>vulnerable</title>
</head>
<body>
<strong>Error<br />
</strong>el nombre de usuario < %=request.getParameter("usuarioID")%> es incorrecto
</body>
</html>

La solución es validar a nivel de servidor, de la siguiente forma:

  • Primero validar a nivel del tipo de dato, por ejemplo validar que el campo sea un email válido.
  • Segundo validar que no vengan caracteres “peligrosos”

<> (mayor, menor)

| (pipe)

& (ampersand)

; (punto coma)

$ (signo moneda)

% (porcentaje)

‘ (comilla simple)

” (comilla doble)

\’ (backslash-comilla)

\” (backslash-doble comilla)

() (parentesis)

+ (signo mas)

CR (retorno carro, ASCII 0x0d)

LF (salto linea, ASCII 0x0a)

, (coma simple)

@ (arroba)

Luego al devolver o usar el campo, se debe

  • Primero, usar la secuencia de escape adecuada, por ejemplo para las paginas Web (Html) podemos usar “escapeHtml”( http://commons.apache.org/lang/download_lang.cgi ), con lo cual el browser no lo interpreta como un comando Javascript o comando Html.
  • Luego,  si efectivamente el valor entregado es “extraño”, por ejemplo se detecta que tiene palabras reservadas Javascript ni siquiera devolverlo en una pagina, y registrarlo en el log de la aplicación.
< %@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %>
< %@ page import = "org.apache.commons.lang.StringEscapeUtils"%>
< !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>vulnerable</title>
</head>
<body>
<strong>Error<br />
</strong>el nombre de usuario "
< %=StringEscapeUtils.escapeHtml(request.getParameter("usuarioID"))%>
" es incorrecto
</body>
</html>

Lo bueno que para encontrar este tipo de problemas existen herramientas que revisan un sitio y entregan un reporte con toas las vulnerabilidades como IBM AppScan (http://www-01.ibm.com/software/awdtools/appscan/)

Jun 05

DWR (Direct Web Remoting)es una librería Javascript que permite el uso de Ajax (Asynchronous JavaScript and XML) de forma mucho más simple (Este artículo asume que se entiende los conceptos de Ajax, y de Java).

DWR es una librería mas orientada a apoyar la integración, que a apoyar la parte gráfica, de hecho si se buscan Widgets (objetos gráficos) esta no es la librería, pero por otro lado lo fuerte de DWR es que permite “publicar” fácilmente funcionalidad de clases Java para accederlas vía Javascript.

Luego si nuestra funcionalidad o lógica de negocio esta en Java, DWR es una de la mejores opciones para aprovecharla, ya que usar una clase Java que tenemos en un servidor de aplicaciones vía Javascript es tan fácil como definir un archivo de configuración en el servidor.

Ahora si se requiere además darle una interfaz más rica (rich interface) a los usuarios, es bueno combinar DWR con otras librerías Ajax como YUI (Yahoo User Interface), JQuery, Prototype, Scriptaculous, Dojo, o Spry.

Con Ajax se terminan las paginas JSP, o ASP (o deberían terminarse), porque Ajax solo necesita Javascript y HTML para la parte de presentación, esto lo explicaremos mejor en otro articulo, pero por ahora créannos. Y con DWR ni siquiera son necesarios los Servlets, esto en el sentido de que no se necesitan desarrollar servlets para implementar la lógica de negocio, porque DWR si internamente esta basado en Servlets, en otras palabras gracias a DWR no necesitamos implementar nuestros servlets sino solo necesitamos clases Java (POJO).

Si se conoce la tecnología Axis, que permite publicar clases Java como Webservices, este es el símil para publicar clases Java como objetos Ajax (objetos Javascripts), de hecho es muy fácil publicar con DWR un servicio realizado para Axis. Incluso las buenas prácticas o blueprints de Axis para publicar clases como Webservices se aplican totalmente para publicar clases para Ajax., ya que hay que tener los mismos cuidados en cuanto a seguridad y manejo de desempeño (performance).

La definición oficial de DWR es:

DWR permite a Javascript (en un Browser) interactuar con las clases Java en un Servidor, y ayuda a manipular las paginas Web con los resultados.

Si, porque otra característica importante de DWR es que ofrece funcionalidades Javascript que permiten fácilmente manipular el HTML de la página: como obtener los datos de un formulario (HTML Form), o de cualquier otro tag HTML, o setear fácilmente los valores de los tags HTML, ademas aporta facilidades para clonar tags, lo que permite por ejemplo crear nuevas filas (rows) en una tabla (HTML Table), muy útil para mostrar una consulta. DWR no nos provee ningun objeto grafico prehecho, pero si nos permite la flexibilidad para hacer cualquier cosa con el HTML.

Bueno basta de conceptos y vamos directo al código.

Hemos llamado a este tutorial “el Mejor”, solo para llamar su atención, pero la verdad es que es bastante bueno, por lo siguiente:

  • Es lo mas simple posible, tiene lo justo y necesario para entender el concepto principal de DWR, y como funciona.
  • Esta actualizado, otros ejemplos están basados en versiones anteriores de DWR.
  • Esta completo, otros ejemplo ponen solo parte del código.
  • Usa clases y colecciones de clases (Collection) como parámetros de entrada y parámetros de salida, que es lo que típicamente se va a usar en un sistema real, otros ejemplos usan datos más básicos. De esta forma también se muestra como se manejan las colecciones y las clases en Javascript.

Tutorial DWR en 10 Simples Pasos

  1. Lo primero es bajar DWR (http://getahead.org/dwr/download), basta bajar solo la librería (dwr.jar).
  2. Luego hay que modificar el archivo “web.xml”, este se encuentra bajo el directorio “WEB-INF”, ejemplo: ”\dwrEasy\WEB-INF”, se debe incluir las definiciones de los servlets que atienden los requerimientos DWR-AJAX.

Web.xml:

< ?xml version="1.0" encoding="UTF-8"?>
< !DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web_app>
  <display_name>dwrEasy</display_name>
  <servlet>
    <servlet_name>dwr-invoker</servlet_name>
    <display_name>DWR Servlet</display_name>
    <description>Direct Web Remoter Servlet</description>
    <servlet_class>org.directwebremoting.servlet.DwrServlet</servlet_class>
    <init_param>
      <param_name>debug</param_name>
      <param_value>true</param_value>
    </init_param>
  </servlet>
  <servlet_mapping>
    <servlet_name>dwr-invoker</servlet_name>
    <url_pattern>/dwr/*</url_pattern>
  </servlet_mapping>
</web_app>

  1. A continuación se debe implementar la clase Java que va a ofrecer los servicios, esta basta que sea un clase Java simple (POJO), con un constructor sin parámetros, y donde cada método de la clase es un “potencial” servicio Ajax.

Esta clase va a estar del lado del servidor de aplicaciones (ejemplo: Tomcat, JBoss, o Websphere) no necesariamente tiene que ser un servidor J2EE.

EasyService.java:

package com.soaagenda.services;

import com.soaagenda.valueobjects.*;

public class EasyService {
    public EasyService() {
    }
    
    public EasyResponse getProducts(EasyParameter parametersX){
        EasyResponse responseX= new EasyResponse();
        
        
        // si parametros vacios devuelve error, error if empty parameters
        if (parametersX==null || parametersX.getClientID()==null || parametersX.getClientID().length()< =0){
            responseX.setErrorCode(10001);
            responseX.setErrorDescription("Debe indicar ID Cliente. Give us Client ID");
            return responseX;
        }

        //crea lista productos del cliente, fill the client product list
        //para ejemplo en duro, for the example fixed data
        Product[] productsListX= new Product[2];
        
        Product productX= new Product();
        productX.setBarCode("0001");
        productX.setName("Tarjeta Visa, Visa Credit Card");
        productsListX[0]= productX;

        productX= new Product();
        productX.setBarCode("0002");
        productX.setName("Tarjeta American Express, American Express Credit Card");
        productsListX[1]= productX;

        //respuesta exitosa, sucessfull response
        responseX.setErrorCode(0);
        responseX.setErrorDescription("Consulta Banco Exitosa, Succesfull Bank Query");
        responseX.setProducts(productsListX);
        
        return responseX;
    }
}

Este ejemplo simula una consulta de los productos bancarios de un cliente como Tarjetas de Crédito (VISA, Master Card, Dinners, American Express), Cuentas Corrientes, o Creditos de Consumo, para esto se le pasa como parámetro un objeto que tiene el ID del cliente, y el tipo de producto a consultar, el servicio (o método) devuelve otra clase con el código de error, mensaje de error, y la lista de productos (un arreglo de clases producto).

  1. También hay que definir las clases de datos (Value Objects) que va a utilizar el servicio, esta clases deben ser javabeans (atributo privados, con getters y setters):

EasyParameter.java: define los parámetros de entrada del servicio.

package com.soaagenda.valueobjects;

public class EasyParameter {
    private String clientID;
    private String productType;
    public EasyParameter() {
    }

    public void setClientID(String clientID) {
        this.clientID = clientID;
    }

    public void setProductType(String productType) {
        this.productType = productType;
    }

    public String getClientID() {
        return clientID;
    }

    public String getProductType() {
        return productType;
    }
}

EasyResponse.java: define la estructura para los resultados del servicio.

package com.soaagenda.valueobjects;

public class EasyResponse {
    private int errorCode;
    private String errorDescription;
    private Product[] products;
    
    public EasyResponse() {
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public void setErrorDescription(String errorDescription) {
        this.errorDescription = errorDescription;
    }

    public void setProducts(Product[] products) {
        this.products = products;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public String getErrorDescription() {
        return errorDescription;
    }

    public Product[] getProducts() {
        return products;
    }
}

Product.java: define la estructura de un producto.

package com.soaagenda.valueobjects;

public class Product {
    private String barCode;
    private String name;
    public Product() {
        try {
            jbInit();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void jbInit() throws Exception {
    }

    public void setBarCode(String barCode) {
        this.barCode = barCode;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBarCode() {
        return barCode;
    }

    public String getName() {
        return name;
    }
}

  1. Vamos a indicarle a DWR que clases vamos a publicar para Javascript (Ajax), esto se hace en el archivo “dwr.xml”, y también corresponde a un esquema de seguridad porque podemos llegar a especificar solo que métodos de una clase queremos exponer, y que atributos.

Para nuestro ejemplo vamos a publicar toda la clase servicio (com.soaagenda.services.EasyService), es decir todos sus métodos, y todas las clases de datos (com.soaagenda.valueobjects.*).

dwr.xml

< ?xml version="1.0" encoding="UTF-8"?>
< !DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr/dwr20.dtd">
<dwr>
  <allow>
    <!-- define la clase de servicios que se va a publicar mediante DWR -->
    <!-- defines the service class to share across DWR -->
    <create creator="new" javascript="EasyService">
      <param name="class" value="com.soaagenda.services.EasyService"/>
    </create>
    <!-- define las clases de datos que utiliza el servicio -->
    <!-- defines the data classes to share across DWR -->
    <convert converter="bean" match="com.soaagenda.valueobjects.*"/>
    </allow>
</dwr>

  1. Ahora si compilamos las clases, y publicamos nuestro ejemplo Web (deploy), DWR nos presta una utilidad para probar que todo anda bien, esta se accede desde un Browser, en el path “/dwr/”, dentro de nuestro sitio (ejemplo: http://localhost:8028/dwrEasy/dwr/)

DWR Test Page

Seleccionado el servicio, nos aparece primero las librerías javascript necesarias para implementar una pagina Web, luego la lista de servicios (métodos de las clase servicio) a los que tenemos acceso (getProducts).

DWR Test Page EasyService

Como no restringimos los métodos de la clase, incluso nos aparecen los métodos que hereda por ser una clase Java (como hashCode, getClass).

Si presionamos el botón “Execute”, ejecuta el servicio, y para el caso de nuestro ejemplo este retorna un error “Debe indicar ID Cliente”, lo que esta bien porque no le hemos indicado parámetros de entrada, con esto sabemos que todo anda bien, porque esa respuesta la da nuestra clase que esta en el servidor, es decir, ejecutamos desde Javascript (Browser) una clase que esta del lado del servidor, eso es Ajax y DWR!!.

  1. Lo siguiente es crear nuestra pagina HTML que va a consultar los resultados y mostrarlos, esta pagina es sumamente simple, tiene un formulario (Html Form), en realidad solo tiene las variables del form, porque ya no es necesario el tag “<form>” (recuerden que con Ajax la forma de hacer request al Server cambia, ahora es asíncrona), y tiene una tabla (html table) para mostrar el resultado.

Mi Primera Pagina DWR

dwrEasyPage.html

< !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>
  <title>Editable Table Demo</title>
  <meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
  <script type='text/javascript' src='/dwrEasy/dwr/interface/EasyService.js'></script>
  <script type='text/javascript' src='/dwrEasy/dwr/engine.js'></script>
  <script type='text/javascript' src='/dwrEasy/dwr/util.js'> </script>
  <script type="text/javascript" src='dwrEasyJS.js'> </script>
</head>

<body>
<h1>Easy DRW Demo</h1>
    <h3>Buscar / Search</h3>

   <table>
      <tr>
        <td>ID Cliente / Client ID:</td>
        <td><input name="formClientID" type="text" id="formClientID" size="15"/></td>
      </tr>
      <tr>
        <td>Tipo Producto / Product Type</td>
        <td><input name="formProductType" type="text" id="formProductType" size="15"/></td>
      </tr>
      <tr>
        <td colspan="2" align="right">
          <input type="button" value="OK" onclick="javascript:submitProductsRequest()"/>
        </td>
      </tr>
    </table>

    <h3>Productos / Products</h3>
    <p>Respuesta / Response:  
      <input name="serviceResponse" type="text" id="serviceResponse" size="50"/>
    </p>

    <table border="1">
      <thead>
        <tr>
          <th>Codigo / BarCode</th>
          <th>Nombre / Name</th>
        </tr>
      </thead>
      <tbody id="myTable">
        <tr id="myPattern" style="display:none">
          <td>
            <span id="codePattern">code example</span></td>
          <td>
            <span id="namePattern">name example</span></td>
        </tr>
      </tbody>
    </table>
</body>
</html>

Si analizamos el código, lo primero es la referencia a las librerías Javascript (“EasyService.js”, “engine.js”, “util.js”), estas fueron generadas por DWR, luego aparece la librería (“dwrEasyJS.js”) que ya es propietaria (custom), y que debemos construir para realizar la lógica de interacción (esta redescribe a continuación).

Luego aparecen las variables del formulario a solicitar (formClientID, formProductType), y el botón que define la acción de consulta, ejecuta la función Javascript “javascript: submitProductsRequest()”, esta funcion es la que se encuentra definida en la librería (“dwrEasyJS.js), la cual vamos a tener que contruir.

Después viene la tabla que mostrará los datos (myTable).

Podemos ver que hay ciertas características especiales en el código Html, estas le van a servir a DWR para encontrar donde obtener, o donde mostrar los datos:

Los “Id” de los tags html le sirven a DWR para manejar los datos:

· Los id “formClientID”, “formProductType” le permiten encontrar loas parámetros de entrada.

· El id “myTable”, le servirá para saber que tabla limpiar.

· El id “myPattern”, le indicará a DWR que filas (rows) debe “clonar” para mostrar cada registro de producto de la lista, como es un “patrón” en principio va oculto (style=”display:none”).

· Los id “codePattern”, “namePattern” servirán para qeu DWR sepa donde colocar los datos de cada producto, en este caso se usan tags “<span>”.

Como vemos esta pagina es solo Html, y Javascript, que “hermosamente simple”, nada de JSP, ni de ASP, una pagina que se puede publicar en cualquier servidor Web.

  1. Lo que sigue es la librería Javascript propia.

dwrEasyJS.js

function submitProductsRequest() {
  var idX=dwr.util.getValue("formClientID");
  var typeX=dwr.util.getValue("formProductType");

  var parametersX={clientID:idX,productType:typeX};

  EasyService.getProducts(parametersX,showProducts);
  
 }


function showProducts(responseX) {

// borro filas excepto el patron, delete rows except pattern row
        dwr.util.removeAllRows("myTable", { filter:function(tr) { return (tr.id != "myPattern");}});

	if (responseX.errorCode!=0){
  	  alert('Error: '+responseX.errorDescription);
	  return;
	};
	var productsX=responseX.products;
	var lengthX=productsX.length;
	var itemProductX;

 	if (lengthX==0){
  	  alert('No hay productos, Cant find products');
	  return;
	};

	dwr.util.setValue("serviceResponse", responseX.errorDescription);

	var id="00";
	for (var i=0 ; i<lengthx ; i++)
        {
           temProductX= productsX[i];
           id="00"+i;
           dwr.util.cloneNode("myPattern", { idSuffix:id });
           dwr.util.setValue("codePattern" + id, itemProductX.barCode);
           dwr.util.setValue("namePattern" + id, itemProductX.name);
           $("myPattern" + id).style.display = "";
        }
}

  1. Esta librería tiene dos funciones: “submitProductsRequest” la que obtiene los parámetros de entrada y ejecuta el servicio correspondiente (clase Java EasyService.getProducts() vía DWR), y la función que muestra los datos (showProducts).

Finalmente hay que subir las páginas (Deploy) en el sitio Web, quedando el proyecto con la siguiente estructura:

.Proyecto DWR de Ejemplo

Ejecutamos la pagina de prueba “dwrEasyPage” en nuestro Browser (ejemplo “http://localhost:8028/dwrEasy/dwrEasyPage.html”), ponemos un dato en “ID Cliente”, presionamos “OK”, y listo!, nuestra primera aplicación Ajax-DWR.

  1. The End

May 28

Cuando nos da el siguiente error:

[ERROR, org.directwebremoting.impl.DefaultCreatorManager]Creator: ‘NewCreator[XXX]‘ for XXX.js is returning null for type queries.

Se debe a que no encuentra el constructor de la clase que se esta exportando en DWR, esto se puede deber a los siguientes motivos:

  • El nombre de la clase o el package de la clase esta mal definido en el archivo “dwr.xml”.
  • La clase no tiene el constructor por defecto vacio.
  • La clase no se encuentra en el classpath, por ejemplo si es un proyecto Web no se encuentra en el directorio “..\WEB-INF\classes\”, o “..\WEB-INF\lib\” (si esta dentro de un jar).
  • Del anterior se desprende que; suele suceder que cuando se compila la clase no se incluye en ningun jar, luego en el fondo esta mal definido el proyecto en cuanto a la compilación, y generación de librerias (jars).
Oct 17

Generalmente cuando se realiza una consulta a una base de datos se traen “todos” los registros, lo cual funciona con conjuntos de tamaño razonable, pero con mas de 3000 registros ya puede traer problemas de performance, o de memoria (Memory Fault).

Lo mejor en estos caso es paginar, y lo bueno es que JDBC proporciona ciertas facilidades, como la función para saltar a un registro determinado “absolute()”. Y siendo JDBC funciona para la mayoria de las bases de datos: Oracle, MySQL, SyBase IQ, DB2.

Aquí hay un ejemplo completo, se le entrega el numero de pagina, y la cantidad de filas, y retorna un arreglo con los registros. Esta implementación mejora considerablemente el desempeño (performance) de la consulta (query), debido que el conjunto resultado (result set) que se recorre, y maneja es mucho menor.

El ejemplo tambien sirve para ver como se debe hacer una consulta a la base de datos en Java, con JDBC, ya que implementa otras recomendaciones comunes, como el uso de “prepared statement”, que tambien optimiza la ejecución de las consultas, precompilando el query en la Base de Datos, y manteniendolo en “cache” para futuras ejecuciones (donde solo cambia los parametros de busqueda).

public ArrayList seleccionaPaginado(Connection conexionX, int nroPaginaX, int cantidadFilasX)throws Exception{

	  String sqlX;
	  ArrayList arreglo = new ArrayList();

	  int filaActual = 0;
	  if (nroPaginaX > 0) {
		  filaActual = (nroPaginaX - 1) * cantidadFilasX;
	  }

	  sqlX = "select * from EJEMPLO";
	  arreglo.clear();
	  String codigoX;
	  String tituloX;

	  try{
		  PreparedStatement pStatemenX = conexionX.prepareStatement(sqlX,
				  ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
		  ResultSet rSetX = pStatemenX.executeQuery();

		  if (filaActual>0){
			  boolean loHizo = rSetX.absolute(filaActual);
		  }   

		  int i=0;
		  while (rSetX.next() && i < cantidadFilasX) {
			  codigoX = rSetX.getString("CODIGO");
			  tituloX = rSetX.getString("TITULO");

			  arreglo.add(codigoX+", "+tituloX);
			  i++;
		  }

		  rSetX.close();
	  }catch (Exception e){
		  System.out.println(e.toString());
		  throw e;
	  }

	  return arreglo;
}
Aug 22

Un requqerimiento común en Java, y XML, es realizar un traspaso de un string XML al objeto XML estandar que es DOM.

El siguiente ejemplo muestra como pasar desde un objeto Document (org.w3c.dom.Document) al String XML respectivo, y viceversa.

    public String DOM2String(Document doc)
    {
        TransformerFactory transformerFactory =TransformerFactory.newInstance();
        Transformer transformer = null;
        try{
            transformer = transformerFactory.newTransformer();
        }catch (javax.xml.transform.TransformerConfigurationException error){
            coderror=123;
            msgerror=error.getMessage();
            return null;
        }

        Source source = new DOMSource(doc);

        StringWriter writer = new StringWriter();
        Result result = new StreamResult(writer);
        try{
            transformer.transform(source,result);
        }catch (javax.xml.transform.TransformerException error){
            coderror=123;
            msgerror=error.getMessage();
            return null;
        }

        String s = writer.toString();
        return s;
    }

    public Document string2DOM(String s)
    {
        Document tmpX=null;
        DocumentBuilder builder = null;
        try{
            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        }catch(javax.xml.parsers.ParserConfigurationException error){
            coderror=10;
            msgerror="Error crando factory String2DOM "+error.getMessage();
            return null;
        }
        try{
            tmpX=builder.parse(new ByteArrayInputStream(s.getBytes()));
        }catch(org.xml.sax.SAXException error){
            coderror=10;
            msgerror="Error parseo SAX String2DOM "+error.getMessage();
            return null;
        }catch(IOException error){
            coderror=10;
            msgerror="Error generando Bytes String2DOM "+error.getMessage();
            return null;
        }
        return tmpX;
    }
Aug 14

Vamos a comenzar desde lo primordial: los estándares de programación para clases Java y páginas JSP, porque generalmente se parte programando sin ninguna estructura, y esto desencadena en que en una Empresa se tienen distintos “estilos” de programación, lo cual hace más difícil entender el programa realizado por otra persona.

Los estándares están definidos por Sun en los siguientes lugares:

http://java.sun.com/docs/codeconv/

http://java.sun.com/developer/technicalArticles/javaserverpages/code_convention/

Nosotros vamos a presentar un resumen de estos estándares mediante una plantilla base de los 2 módulos básicos de programación, un clase Java, y un página JSP, estas plantillas pueden servir a un departamento de Control de Calidad para verificar que los programas se ciñen al estándar Java, pero la mejor forma de seguir estos estándares es utilizar un IDE (ambiente de desarrollo) como Eclipse, o usar verificadores de código automático.

Otra cosa que muestran estas plantillas es la forma de comentar los fuentes, para que se pueda obtener el JavaDoc correspondiente (documentación automática de Java).

Plantilla de Codificación Java.

Esta plantilla Java se puede extender a otras clases como Servlets.

Una clase Java tiene el siguiente orden:

  1. Comentarios de Inicio
  2. Definición Package
  3. Declaraciones de Import
  4. Declaraciones de la Clase

4.1. Comentario Documentación de la Clase

4.2. Estamento class

4.3. Atributos o Variables Estaticas

4.3.1. public

4.3.2. protected

4.3.3. private

4.4. Atributos

4.4.1. public

4.4.2. protected

4.4.3. private

4.5. Constructores

4.6. Metodos

La siguiente plantilla resume los principales estándares de codificación propuestos por Sun.

/*

* @(#)Plantilla.java version 1.01 2007/01/01

* Copyright (c) 2007 SOA agenda.

*/

package com.soaagenda.ejemplos;

import com.soaagenda.librerias.*; //import de librerias y clases a utilizar

/**

* Descripción de la Clase, ejemplo: Plantilla que muestra

* principales estándares de codificación.

*

* @version 1.01 01 Ene 2007

* @author SOA Team

*/

public class Plantilla extends ClasePadre {

/* Comentario de implementacion, ejemplo: Esta clase no tiene funcionalidades . */

/** atributo1 comentario documentacion atributo

* puede ser de mas de una linea

*/

public static int atributo1; //comentario linea: primero las variables estaticas,

//en orden 1.-public, 2.-protected, 3.-private

/** atributo2 comentario documentacion */

public Integer atributo2; //luego var de instancia, mismo orden 1.-public, 2.-protected, 3.-private

/** atributo3 comentario documentacion */

protected Integer atributo3;

/**

* Descripción para el constructor.

*/

public Plantilla() {

// …implementacion …

}

/**

* Descripción de un metodo.

* @param par1 descripcion primer parametro

* @param par2 descripcion segundo parametro

* @return descripcion de salida (return) del metodo, en caso que no es void

*/

public String hacerAlgo(Integer par1, String par2) {

int entero = 0; //una declaración de variable por linea y al inicio del {bloque}

/* A continuacion mostraremos ejemplos de la identación y formato de las distintas sentencias Java*/

if (entero == 0) {

int entero2 = 1; //una declaración de variable por linea y al inicio del {bloque}

} else if (entero == 1) {

entero++; // solo un estamento por linea

} else {

entero–;

}

for (int i=0; i < 5; i++){

entero=i;

}

while (entero > 0){

entero–;

}

do {

entero++;

} while (entero < 10);

switch (entero) {

case 0:

entero++;

break;

case 2:

entero–;

break;

default:

entero=1;

break;

}

try {

entero=2/entero;

} catch (Exception e) {

System.out.println(“error división”);

} finally {

entero=1;

}

return (“Ok”);

}

}

Buenas Practicas Básicas de Programación Java.

  • Acceso a Instancia y Variables de Clase: Evitar el uso de variable publicas.

  • Asignación Variables: Evitar asignar mas de una variable en un misma sentencia.
    • a = b = c +1; //evitar esto!!
    • if (c++ == d++) { //evitar esto!!

  • Uso de Constantes: Usar siempre cantantes para valores fijos.
    • if (c == 1) { //evitar esto!!
    • if ( c == ESTADO_ACTIVO ) { //asi si!!

  • Uso Paréntesis: Usar explícitamente para definir precedencia, para mejor entendimiento del programador.
    • if ( a = = b && c = = d || e == f ) { //evitar esto!!
    • if ( ( (a = = b) && (c = = d) ) || (e = = f) ) { //así si!, no hay forma de entender precedencia.

  • Valores de Retorno: Evitar “return” de condiciones simples.
    • if (booleanExpression) { //evitar esto!!

return true;

         }else{

                          return false;

          }

    • return booleanExpression; //esto si!!
    • if (condition) { //evitar esto!!

    return x;

    } else {

    return Y;

    }

      • return ( (condicion) ? x : y); //esto si!!

      • Expresiones condicionales ‘?’: La condición debería ir siempre entre paréntesis.
        • x >=0 ? x : -x; //evitar esto!!
        • ( x >=0 ) ? x : -x; //así si!!

      • Clases como parámetros de entrada: de forma de reducir la cantidad de parámetros de entrada de un método, de ser orientado a objetos, y hacer mas estable el método, por ejemplo para un método “actualizarCliente()” puede que ahora solo necesitemos actualizar 2 variables “nombre” y “email”, y solo esas pasamos como parametros, pero si mañana necesitamos una tercera, debemos cambiar todas las llamadas al método del sistema, por otro lado si pasamos una clase Cliente, solo se cambia la clase internamente.
        • public void actualizaCliente( String rut, String nombre, String email) //evitar esto!!
        • public void actualizaCliente( ClaseCliente cliente) // esto si!! es Orientado Objetos

      Plantilla de Codificación JSP.

      El orden dentro de una pagina JSP es:

      1. Comentarios de Inicio
      2. Directivas JSP
      3. Directivas Librerías de Tags
      4. Declaraciones JSP
      5. HTML y tags JSP

      La siguiente plantilla muestra los principales estándares JSP, esta plantilla se centra en los estándares JSP, y no incluye estándares HTML.


      <%–

      - Author: SOA Team

      - Date: 28 Marzo 2007

      -

      - Derechos Reservados Soa Agenda.

      - @(#)

      - Description: Estos son los “Comentarios de Inicio” de la Plantilla Ejemplo Estandares JSP.

      –%>

      <%– 2.-Directivas JSP –%>

      <%@ page session=”true”

      import=”java.util.*”

      errorPage=”../principal/paginaError.jsp”

      %>

      <%– 3.-Directivas Librerias Tags –%>

      <%@ taglib uri=”/WEB-INF/jsp/libreriatags.tld” prefix=”tags” %> <%– Aqui van las librerias de tags –%>

      <%– 4.-Declaraciones JSP: instancias variables, y metodos de la JSP –%>

      <%

      private int entero;

      public int transformaEntero(float Numero) {

      //implementaciòn

      }

      %>

      <%– 5.-HTML y tags JSP –%>

      <html>

      <head>

      <title>Titulo de la Pagina que aparece en el Browser</title>

      </head>

      <body>

      <jsp:useBean id=”cliente” class=”com.SOAagenda.segurosvida.Cliente” /> <%– declaracion de un javabeans –%>

      <h1>

      Rut:

      <tagsSAgenda:formateaRut value=”${cliente.getRut()}” /> <%– un tag que usa al beans –%>

      </h1>

      <hr />

      <table border=”1″ cellpadding=”5″>

      <%– Un if en JSP y ejemplo identación –%>

      <% if (entero == 0) { %>

      <tr>

      <td>Nombre:</td>

      <td><%= cliente.getNombre()%></td> <%– expresion explicita %>

      </tr>

      <% } %>

      <tr>

      <td>Apellidos:</td>

      <%– expresion Javabeasn muestra –%>

      <td><jsp:getProperty name=”cliente” property=”apellidos”/></td>

      </tr>

      </table>

      <%– incluir otra pagina –%>

      <%@ include file=”../principales/piePagina.jsp” %>

      </body>

      </html>

      Buenas Practicas de Programación JSP.

      • Solo Lógica de Presentación: Una pagina JSP debe evitar tener lógica de negocio, y lo que “nunca” debería tener es lógica de acceso a base de datos, se debe tener ojalá solo lógica de presentación, esto es, solo instrucciones de creacion de JavaBeans, instrucciones para mostrar sus atributos (getters) y uso de funciones de presentación (como transformaciones), también puede incluir cualquier estamento condicional (if, else, while, do while, switch).

      · Una pagina jsp debe evitar tener definición de métodos:

      public int procesarPago() { //esto NO!!

      //implementación

      }

      · Debe evitarse tener llamadas a método de negocio:

      cliente.calculaSaldo();//esto NO!!

      Si puede tener llamadas a getters de un bean:

      cliente.getSaldo(); //esto SI!!.

      · Debe evitarse tener grandes porciones de código Java, que no tengan que ver con lógica de presentación , por ejemplo si dentro de los tags jsp”<% %>” hay sobre 10 líneas, este código ya es “sospechoso” de incluir lógica de negocio, lo mas probable es que dicha lógica deba ir dentro de un Servlet, o clase Java:

      <%

      //…mas de 10 líneas entre estos tags JSP , es Sospechoso!!

               %>