viernes, 27 de junio de 2008

Depurar PHP en NetBeans 6.1

Como lo prometí en el anterior post: Usando NetBeans IDE Early Access for PHP comentaré sobre cómo depurar la ejecución del PHP desde NetBeans.

Antes de empezar...



Algo que no mencioné en mi anterior post. Tenemos que establecer la ubicación del intérprete del PHP. Si estamos en Windows, necesitamos saber dónde se encuentra el php.exe. Si estás utilizando algún WAMP, búscalo dentro de la carpeta donde se instaló el wamp.



Yo tengo el PHP en c:\opt\php-5.2.5-Win32



Ahora, dentro del NB, entramos a Tools > Options, y seleccionamos la opción "Misceláneas"; y dentro seleccionamos la ficha PHP. En la entrada  "PHP 5 Interpreter", debemos especificar la ubicación exacta del php.exe.





Hacemos clic en "Ok".



Para probar, hagamos un proyecto simple de PHP, creemos un archivo llamado test.php que contendrá el siguiente código:



test.php

1 

2<?php print "La versión del PHP es ".phpversion();

3 ?>

4

5







Le damos clic derecho al archivo desde el explorador de proyectos, y seleccionamos "Run in command line". Esto debería mostrar algo similar a esto en el panel inferior de Output del NB.







xdebug



Necesitamos descargar el xdebug. Es una herramienta de depuración para PHP, y NB está preparado para usarlo.



Buscamos la versión adecuada para nuestro PHP. En mi caso, como el PHP que estoy usando es la 5.2.5, debería usar la versión PHP 5.2.1 - 5.2.6.



Guardamos el archivo en la subcarpeta "ext" de PHP. Puede estar en cualquier lado, pero esta es la mejor ubicación para ello.



Ahora editaremos el archivo php.ini y agregaremos las siguientes líneas al final



zend_extension_ts="C:/opt/php-5.2.5-Win32/ext/php_xdebug-2.0.3-5.2.5.dll"

xdebug.remote_enable=1





Notar cómo se está indicando la ruta del archivo .dll. No son con backslash ("\") sino con slash ("/"). Guardamos el archivo php. ini y listo.



Reiniciamos el ApacheServer para que recargue la configuración del PHP.



Depurando aplicaciones PHP



No es cosa del otro mundo hacer esto. NB soporta breakpoints, depuración de variables, etc.





martes, 24 de junio de 2008

Autenticación con LDAP en ActiveDirectory

ActiveDirectory es un servicio de Windows Server que mantiene información de la red tales como los usuarios registrados, los equipos conectados, etc. Soporta el protocolo LDAP, por lo que este consistirá en hacer una simple clase que permita autenticar un usuario y una contraseña utilizando el ActiveDirectory.



Si estamos haciendo una aplicación que necesite un inicio de sesión, convendría mejor que el usuario usara el mismo nombre de usuario y contraseña que utiliza cuando trabaja en red.



Un ejemplo simple de autenticación con LDAP se encuentra aquí LDAP Autentication.

Obteniendo la configuración del dominio de red

En una red windows existe el servidor que contiene toda la información de la red. Este se llama "Servidor de Dominio". Debemos consultar con nuestro administrador de red cuál es el nombre de este equipo para hacer nuestra aplicación.



Para nuestro ejemplo, nuestro servidor de red se llamará spdom01.



Necesitamos, además, saber cuáles son las cadenas de utiliza el LDAP. Podemos consultarle, también, a nuestro administrador de red. Pero si él piensa que nos hemos vuelto espesos, mejor averiguémoslo nosotros mismos.



Para ello descargaremos un programa que me resulta muy útil. Se llama Softerra LDAP Browser. Es gratuito.







Una vez descargado e instalado, crearemos un perfil. Entramos a la opción File > New profile. Escribimos un nombre de referencia, por ejemplo "Nuestra red".



Cuando aparezca la ventana "Host information" debemos indicar el nombre del servidor de dominio en la casilla "Host"



Y hacemos clic en el botón "Fetch DNS".

Con esto, el LDAP Browser consultará al servidor y obtendrá las cadenas de base DN. A mi me aparecen unas cadenas que por seguridad no las publicaré.



Clic en "Siguiente".



Luego nos pedirá nuestras credenciales. Pues aquí ingresamos nuestro nombre de usuario y nuestra contraseña.



No sé si está mal configurado este servicio en mi red, ya que no soy el administrador. Yo he tratado de colocar mi usuario normal, pero no ingresa. Después de muchas pruebas, logré ingresar escribiendo el nombre de mi usuario seguido de "@" y el dominio, como si fuera mi correo electrónico. Algo así:



Digo que podría estar mal, ya que mi correo electrónico es diferente. Anyway!.



Escribimos nuestra contraseña, clic en "Finalizar"



Y listo, ya deberíamos visualizar los objetos del servidor.

Ahora, examinemos un poco estos objetos. No podemos malograr nada porque supuestamente somos simples usuarios y no administradores del dominio.



Revisemos la carpeta "Users" y busquemos nuestro nombre de usuario.



Yo lo encontré, y esta es la información. Pero hay que notar las siguientes cadenas:

  • cn
  • DisplayName
  • distinguedName

La cadena CN es lo que nos importa realmente. Revisa el título del LDAP Browser. Allí aparecerá la cadena completa de nuestro usuario.

CN=DIEGO ENRIQUE SILVA LIMACO,CN=Users,DC=andes,DC=com,DC=pe


El mio está mal configurado y me aparece todo mi nombre completo. Es decir, que si deseo iniciar sesión usando LDAP debería usar mi nombre completo como nombre de usuario. Bueno, hagamos una clase así, siguiendo el ejemplo de Sun.



Login.java
19 public class Login {

20

21 static final String LDAP_URL = "ldap://spdom01:389/DC=andes,DC=com,DC=pe";

22

23 public boolean login(String username, String password) {

24 Hashtable env = new Hashtable();

25 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

26 env.put(Context.PROVIDER_URL, LDAP_URL);

27 env.put(Context.SECURITY_AUTHENTICATION, "simple");

28 env.put(Context.SECURITY_PRINCIPAL, "CN="+username.toUpperCase()+ ", cn=Users, DC=andes,DC=com,DC=pe");

29 env.put(Context.SECURITY_CREDENTIALS, password);

30 try {

31

32 DirContext ctx = new InitialDirContext(env);

33 return true;

34 } catch (NamingException ex) {

35 Logger.getLogger(Login.class.getName()).log(Level.SEVERE, null, ex);

36 }

37 return false;

38

39

40 }

41 }

42

43

Y cuando queramos usar la clase, simplemente le pasamos el usuario y contraseña.

LoginTest.java
28         String username = "DIEGO ENRIQUE SILVA LIMACO";

29 String password = "LoremIpsum";

30 Login instance = new Login();

31 boolean result = instance.login(username, password);



Pero en mi caso, como es muy incómodo escribir todo mi nombre como nombre de usuario, modificaré mi clase Login y colocaré:

28         env.put(Context.SECURITY_PRINCIPAL, username.toUpperCase()+ "@andes.com.pe");

Y lo invocaré así:

28         String username = "dsilva";

29 String password = "LoremIpsum";

30 Login instance = new Login();

31 boolean result = instance.login(username, password);

Y listo

viernes, 20 de junio de 2008

AJAX con DWR en NetBeans 6.1

Una de las bibliotecas más fáciles que he visto para programar en AJAX es el Direct Web Remoting - DWR.
En este post veremos algunas de sus características que nos ayudará a tener aplicaciones enriquecidas con ajax. Para ello usaremos:

  1. NetBeans 6.1
  2. DWR

Si deseas saber lo que es AJAX, puedes revisar mi anterior post llamado (justamente) AJAX.

Instalando DWR en NetBeans

La biblioteca DWR consta únicamente de un archivo .jar. Este lo podemos descargar de aquí: http://getahead.org/dwr/download. A la fecha de este post la versión del DWR es la 2.0.4.

Guardaremos el archivo en una carpeta que será destinada para las bibliotecas de los proyectos. Yo, en Windows, lo guardo en d:\proys\lib\DWR, y en Linux lo guardo en ~/proys/lib

Adicionalmente DWR necesita de la biblioteca commons-logging. Esta la puedes descargar de aquí: http://commons.apache.org/downloads/download_logging.cgi

Descomprimamos el archivo descargado de commons-logging en la misma carpeta lib.

Entramos a la opción Tools > Libraries:


Hacemos clic en "New Library" para crear una nueva biblioteca, y llamaremos DWR.

Clic en OK. Ahora agregamos los archivos .jar correspondientes. Hacemos clic en Add Jar/Folder y seleccionamos el archivo commons-logging-1.1.1.jar y dwr.jar


Clic en OK parar cerrar la ventana.

El proyecto Web

Ahora crearemos un aplicación web, que no usará ningún framework. Será una aplicación totalmente "simple". La llamaremos DwrSamples.

En las propiedades del proyecto web creado, entramos a sus propiedades haciendo clic derecho sobre el ícono del proyecto, y seleccionamos"Properties". En esta ventana seleccionamos del margen izquierdo la categoría "Libraries". Luego agregamos la biblioteca DWR haciendo clic en el botón "Add Library". Después de esto, deberá lucir así:



Crearemos una clase a la que llamaremos Calculadora y estará en el paquete logica. Esta clase, básicamente, tendrá los métodos que realizarán las operaciones de manera asíncrona.

package logica;
public class Calculadora {

public int sumar(int a, int b) {
return a + b;
}

public int restar(int a, int b) {
return a - b;
}
}


Ahora, necesitamos que esta clase sea leíble por DWR, por lo que usaremos anotaciones para "publicar" la clase como objeto javascript, pero sólo publicaremos el método sumar() para que sea ajax. Usaremos la anotación de DWR @RemoteProxy para la clase Calculadora, y @RemoteMethod para el método sumar(). Deberá lucir así:

package logica;

import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;

@RemoteProxy
public class Calculadora {

@RemoteMethod
public int sumar(int a, int b) {
return a + b;
}

public int restar(int a, int b) {
return a - b;
}
}



Ahora, abrirmos el archivo web.xml (podemos presionar Shift + Alt + O para abrir el buscador de archivos y escribir web para que nos seleccione el archivo que estamos buscando). En la barra superior hacemos clic en el botón "Servlets" para visualizar los servlets de nuestra aplicación. Una vez allí, hacemos clic en el botón "Add Servlet".
El nuevo servlet que crearemos, le pondremos el nombre "DWR", la clase del servlet será "org.directwebremoting.servlet.DwrServlet", y el patrón URL será "/dwr/*", es decir, este serlet responderá las peticiones en la dirección "/dwr/". Deberá lucir esta ventana así:



Hacemos clic en "OK". Luego, agregaremos un parámetro de inicio. Hacemos clic en el botón "Add.." de la sección "Initialization Parameter".

El parámetro de inicio lo llamaremos classes, y tendrá una clase llamada logica.Calculadora.

Clic en OK, y deberá lucir así la ventana de los servlets.

O si lo prefieres en xml, el archivo web.xml deberá lucir así.
Ahora, crearemos nuestra interfaz. Abrimos el archivo index.jsp, le pondremos un formulario:
        <h2>Calculadora</h2>
<form action="">
Valor 1: <input type="text" name="valor1" id="valor1"/><br/>
Valor 2: <input type="text" name="valor2" id="valor2"/><br/>
<input type="button" value="Sumar" onclick="sumar()"/><br/>
Resultado:<div id="suma"></div>
</form>

Nota que el botón no es un submit, sino un tipo "button". Tampoco olvidar los atributos id de los tags. Estos nos ayudará a identificar un tag en toda la página.

Ahora, necesitamos importar las bibliotecas de DWR. Para ello, agregamos las siguientes lineas en la cabecera del jsp.
        <script type="text/javascript" src="<%=pageContext.getServletContext().getContextPath()  %>/dwr/interface/Calculadora.js"></script>
<script type="text/javascript" src="<%=pageContext.getServletContext().getContextPath() %>/dwr/engine.js"></script>
<script type="text/javascript" src="<%=pageContext.getServletContext().getContextPath() %>/dwr/util.js"></script>



Nota que estos javascript están bajo la carpeta /dwr, que es el servlet que hemos creado párrafos arriba. Además, hay un Calculadora.js, que tiene el mismo nombre de nuestra clase java que hemos creado.

Crearemos la función sumar() que es llamada desde el botón "sumar" de nuestro formulario:
            function sumar(){
var valor1=dwr.util.getValue("valor1");
var valor2=dwr.util.getValue("valor2");
Calculadora.sumar(valor1,valor2,mostrarSuma);
}


En las dos primeras líneas estamos obteniendo los valores de los tag "valor1" y "valor2". Este nombre es el nombrado en los atributos id de los input:text. Por ello es importante que los ID identifiquen a un único tag en toda la página.

La tercera linea llama al objeto Calculadora. Este objeto es el que DWR creó como contraparte a nuestra clase java Calculadora.

Nota que llama al método sumar() y recibe tres parámetros. Los dos primeros son los mismos parámetros que hemos declarado en nuestra clase java Calculadora. Pero el tercer parámetro, que se llama mostrarSuma, es el nombre de una función en javascript que se encargará de recibir y manejar el resultado que devuelto por el método sumar() de java. Así es la convención de DWR. Notar que se le está pasando solo el nombre, sin paréntesis.

La función javascript mostrarSuma() será la siguiente:
            function mostrarSuma(resultado){
dwr.util.setValue("suma",resultado);
}


Vemos que el resultado devuelto por el método de java sumar() es el parámetro de esta función javascript. Tomamos el valor y lo mostramos en el tag que tiene nombre "suma" (que es un <div>).

El index.jsp completo es el siguiente:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>

<script type="text/javascript" src="<%=pageContext.getServletContext().getContextPath() %>/dwr/interface/Calculadora.js"></script>
<script type="text/javascript" src="<%=pageContext.getServletContext().getContextPath() %>/dwr/engine.js"></script>
<script type="text/javascript" src="<%=pageContext.getServletContext().getContextPath() %>/dwr/util.js"></script>
<script type="text/javascript">
function sumar(){
var valor1=dwr.util.getValue("valor1");
var valor2=dwr.util.getValue("valor2");
Calculadora.sumar(valor1,valor2,mostrarSuma);
}
function mostrarSuma(resultado){
dwr.util.setValue("suma",resultado);
}
</script>
</head>
<body>

<h2>Calculadora</h2>
<form action="">
Valor 1: <input type="text" name="valor1" id="valor1"/><br/>
Valor 2: <input type="text" name="valor2" id="valor2"/><br/>
<input type="button" value="Sumar" onclick="sumar()"/><br/>
Resultado:<div id="suma"></div>
</form>
</body>
</html>


Lo corremos y probamos:


¡Magia!

Con base de datos

A esta altura verás la simpleza del DWR, y que una aplicación con base de datos no sería de lo más difícil. Haríamos una clase que accede la base de datos y lo muestre en la web. Pero ¿cómo lo mostramos en una tabla?. Aquí mostraremos el ejemplo:

Crearemos una clase llamada PersonasService y lo guardaremos en el paquete logica. Esta clase tendrá la anotación @RemoteProxy pero con un parámetro que cambiará el nombre del objeto javascript. Es decir, no se llamará PersonasService en javascript, sino, se llamará solamente Personas.
package logica;

import org.directwebremoting.annotations.RemoteProxy;
@RemoteProxy(name = "Personas")
public class PersonasService {


}


Ahora, crearemos un método llamado getLista() que devolverá una lista de objetos Persona que tendrá los registros de la base de datos. Para ello, primero crearemos la clase beans.Persona.
package beans;

import java.util.Date;


public class Persona {


private int id;

private String nombre;

private String titulo;

private boolean viajeroFrecuente;

private Date ultimaActualizacion;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getNombre() {
return nombre;
}

public void setNombre(String nombre) {
this.nombre = nombre;
}

public String getTitulo() {
return titulo;
}

public void setTitulo(String titulo) {
this.titulo = titulo;
}

public Date getUltimaActualizacion() {
return ultimaActualizacion;
}

public void setUltimaActualizacion(Date ultimaActualizacion) {
this.ultimaActualizacion = ultimaActualizacion;
}

public boolean isViajeroFrecuente() {
return viajeroFrecuente;
}

public void setViajeroFrecuente(boolean viajeroFrecuente) {
this.viajeroFrecuente = viajeroFrecuente;
}
}

Por alguna razón, los beans con anotaciones no son convertidos por DWR a objetos JavaScript. Al menos con esta versión. He seguido la documentación que indica cómo usar un bean con @DataTransferObject y nada. Si alguien lo puede lograr, lo agradeceré un montón.

Pero para poder enviar beans en DWR, crearemos un archivo llamado dwr.xml y lo guardamos dentro del directorio WEB-INF (en la misma ubicación del archivo web.xml). En ese archivo colocaremos lo siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
"http://directwebremoting.org/schema/dwr20.dtd">
<dwr>
<allow>

<convert converter="bean" match="beans.Persona">
</convert>
</allow>
</dwr>


Ahora, volvemos a la clase PersonasService. Hacemos clic derecho sobre el fondo del código fuente, y seleccionamos Enterprise Resource > Use Database.

Hacemos clic en el botón "Add"

Pondremos como nombre de la referencia, el valor de travelDS. Además, hacemos clic en el botón "Add" de "Project Data sources", escribimos el nombre del JNDI jdbc/travel y seleccionamos de la lista el URL del JDBC referido a la base de datos travel.

Clic en OK

Clic en OK

Clic en OK

Veremos que NetBeans nos ha creado un método llamado getTravelDS()
    private DataSource getTravelDS() throws NamingException {
Context c = new InitialContext();
return (DataSource) c.lookup("java:comp/env/travelDS");
}

Pues ya con esto, podemos hacer nuestro manejo a la base de datos.

Ahora sí, crearemos el método getLista():
    @RemoteMethod
public List<Persona> getLista() {
try {
DataSource ds = getTravelDS();
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM person");
List<Persona> lista = new ArrayList<Persona>();
while (rs.next()) {
Persona p = new Persona();
p.setId(rs.getInt("PERSONID"));
p.setNombre(rs.getString("NAME"));
p.setTitulo(rs.getString("JOBTITLE"));
p.setViajeroFrecuente(rs.getBoolean("FREQUENTFLYER"));
p.setUltimaActualizacion(rs.getDate("LASTUPDATED"));
lista.add(p);
}
return lista;

} catch (SQLException ex) {
Logger.getLogger(PersonasService.class.getName()).log(Level.SEVERE, null, ex);
} catch (NamingException ex) {
Logger.getLogger(PersonasService.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}


La interfaz web


Ahora, para terminar, lo más importante de una aplicación: la interfaz web.
Crearemos una tabla donde se colocarán los datos de los registros obtenidos de la base de datos. Esta tabla será así:
        <input type="button" onclick="mostrarPersonas()" value="Mostrar personas"/>
<input type="button" onclick="limpiarCuadro()" value="Limpiar cuadro"/>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Cargo</th>

</tr>
</thead>

<tbody id="personas">
<tr id="pattern" style="display:none">
<td id="id"></td>
<td id="nombre"></td>
<td id="titulo"></td>
</tr>
</tbody>
</table>


Nota que hay una fila que está invisible (style="display:none"). Esta será nuestra plantilla llamada "pattern". Cuando llenemos los datos de la base de datos, lo que haremos será duplicar esta fila por cada registro.

Vemos también que hay un botón que se llama "Mostrar Personas" que llama a la función JavaScript mostrarPersonas(), que es la está aquí:
            function mostrarPersonas(){
Personas.getLista(listarPersonas);
}


Es decir, estamos llamando al método getLista() del objeto JavaScript Personas que el DWR creó. El método correspondiente en java no tiene parámetros, pero aquí estamos pasándole un argumento. Este argumento es el nombre de la función JavaScript que procesará el resultado devuelto por el método java. Y la función listarPersonas() está aquí:
            function listarPersonas(data){
limpiarCuadro();
for(var i=0;i<data.length;i++){
fila=data[i];
var $id=fila.id;

dwr.util.cloneNode("pattern",{idSuffix:$id});
dwr.util.setValue("id"+$id,fila.id);
dwr.util.setValue("nombre"+$id,fila.nombre);
dwr.util.setValue("titulo"+$id,fila.titulo);
$("pattern"+$id).style.display="";
}
}


Lo que primero hace es limpiar el cuadro (luego presento la función que limpia la tabla). Vemos también que está recibiendo un parámetro. Como vimos en el ejemplo anterior, este es el resultado que está devolviendo el objeto java.
Lo que hacemos es recorrer todas filas de la lista, tomamos una fila y obtenemos el ID de ese objeto.
Este ID nos permitirá identificar a esa única fila, porque sabemos que cada ID de la tabla es única.
Luego clonamos la plantilla:
dwr.util.cloneNode("pattern",{idSuffix:$id});

Al clonarla, hacemos que el ID de la fila creada tenga como subfijo el ID del objeto java. Es decir, al clonar una fila, toda la fila queda idéntica al patrón, incluyendo los nombres de los ID, que según el patrón es "pattern". Si el primero registro de la tabla tiene el valor "1" en el campo "ID", cuando clone la fila pondrá como nombre del ID "pattern" y terminará con el valor "1", o sea, se llamará finalmente "pattern1", y las celdas "nombre1", "titulo1" . Para la segunda fila será "pattern2" y las celdas "nombre2","titulo2"... y así sucesivamente.
Ya clonamos la fila pattern, ahora colocaremos los valores a cada celda. Como ya tenemos el ID unico de cada celda, simplemente colocaremos el valor en ellas
dwr.util.setValue("id"+$id,fila.id);
dwr.util.setValue("nombre"+$id,fila.nombre);
dwr.util.setValue("titulo"+$id,fila.titulo);

Pero como también se clonó el estilo - que indica que debe estar oculto - le indicamos que se muestre.
$("pattern"+$id).style.display="";

...y listo.
A continuación las funciones que borran el contenido de la tabla:
            function limpiarCuadro(){
dwr.util.removeAllRows("personas",{filter:filtroBorrado});
}

function filtroBorrado(tr){
return (tr.id!="pattern");
}


Existe la función filtroBorrado() para evitar que la fila "pattern" sea borrada. Sino, ¿como hacemos la clonación?
Y el resultado es el ya esperado...


Para terminar...

Revisa la documentación de DWR, ahí encontrarás muchas cosas muy interesantes.

Recursos

El proyecto utilizado para este post lo puedes descargar de aquí http://diesil-java.googlecode.com/files/dwrsamples.tar.gz.

martes, 17 de junio de 2008

Get Firefox 3 now!!!

La mejor versión de Firefox está disponible en la web

http://getfirefox.com

Incluye decenas de nuevas características, entre ellas:
Barra de direcciones inteligente, a medida que escribes la dirección o el nombre de una página, Firefox te muestra las posibles direcciones que cumplan con tu criterio.



Añade tus páginas al bookmarks con un solo clic. Además, etiquétalas con varias palabras claves para que puedas siempre encontrar lo que buscas


Las paginas cifradas se las reconocerá más rápido.


Ya no más Phising. Firefox identifica si la página que visitas utiliza phising

y en fin.. todas las características la podrás ver aquí:
http://www.mozilla-europe.org/es/firefox/features/
Aprovecha en descargar HOY mismo el Firefox 3 y participarás del Download Day (hasta las 17:00 GMT del 18 de junio de 2008), para entrar al Libro de Los Records Guinness por la mayor descarga de un software en un solo día.

lunes, 2 de junio de 2008

AOP Programación Orientada a Aspectos con Spring 2.5 y NetBeans 6.1

Mucho se ha oído o leído sobre la Programación Orientada a Aspectos (AOP), pero ¿qué es realmente? Simplemente, es una ayuda para los programadores que permite reducir código de rutinas que siempre deberían ejecutarse y no se puede usar la herencia. Además, cada lógica de negocio solo tendrá lógica de negocio, y no código adicional que son repetitivas y no son parte del negocio. Por ejemplo, un método que se encargue de una transferencia de dinero, debería ser tan simple como esto
void transfer(Account fromAccount, Account toAccount, int amount){
  if (fromAccount.getBalance() < class="br0">) {
    throw new InsufficientFundsException();
  }

  fromAccount.withdraw(amount);
  toAccount.deposit(amount);
}



Pero realmente, se vuelve así:
void transfer(Account fromAccount, Account toAccount, int amount) throws Exception {
if (!getCurrentUser().canPerform(OP_TRANSFER)) {
throw new SecurityException();
}

if (amount < 0) {
throw new NegativeTransferException();
}

Transaction tx = database.newTransaction();
try {

if (fromAccount.getBalance() < amount) {
throw new InsufficientFundsException();
}
fromAccount.withdraw(amount);
toAccount.deposit(amount);

tx.commit();
systemLog.logOperation(OP_TRANSFER, fromAccount, toAccount, amount);
}
catch(Exception e) {
tx.rollback();
throw e;
}
}


Donde se incorpora rutinas de registro (log), inicio de transacciones de la base de datos, validación de cantidades, además de posibles excepciones que se puedan lanzar.

Y si hacemos un método que se encargue únicamente del depósito, el resultado sería similar. Aquí es donde entra los Aspectos.

Algunos conceptos

A continuación, mencionaré algunos conceptos utilizados en la Programación Orientada a Aspectos
  • Aspect (Aspecto) es la funcionalidad que se cruza a lo largo de la aplicación (cross-cutting) que se va a implementar de forma modular y separada del resto del sistema. El ejemplo más común y simple de un aspecto es el logging (registro de sucesos) dentro del sistema, ya que necesariamente afecta a todas las partes del sistema que generan un suceso.
  • Jointpoint (Punto de Cruce) es un punto de ejecución dentro del sistema donde un aspecto puede ser conectado, como una llamada a un método, el lanzamiento de una excepción o la modificación de un campo. El código del aspecto será insertado en el flujo de ejecución de la aplicación para añadir su funcionalidad.
  • Advice (Consejo) es la implementación del aspecto, es decir, contiene el código que implementa la nueva funcionalidad. Se insertan en la aplicación en los Puntos de Cruce.
  • Pointcut (Puntos de Corte) define los Consejos que se aplicarán a cada Punto de Cruce. Se especifica mediante Expresiones Regulares o mediante patrones de nombres (de clases, métodos o campos), e incluso dinámicamente en tiempo de ejecución según el valor de ciertos parámetros.
  • Introduction (Introducción) permite añadir métodos o atributos a clases ya existentes. Un ejemplo en el que resultaría útil es la creación de un Consejo de Auditoría que mantenga la fecha de la última modificación de un objeto, mediante una variable y un método setUltimaModificacion(fecha), que podrían ser introducidos en todas las clases (o sólo en algunas) para proporcionarlas esta nueva funcionalidad.
  • Target (Destinatario) es la clase aconsejada, la clase que es objeto de un consejo. Sin AOP, esta clase debería contener su lógica, además de la lógica del aspecto.
  • Proxy (Resultante) es el objeto creado después de aplicar el Consejo al Objeto Destinatario. El resto de la aplicación únicamente tendrá que soportar al Objeto Destinatario (pre-AOP) y no al Objeto Resultante (post-AOP).
  • Weaving es el proceso de aplicar Aspectos a los Objetos Destinatarios para crear los nuevos Objetos Resultantes en los especificados Puntos de Cruce. Este proceso puede ocurrir a lo largo del ciclo de vida del Objeto Destinatario:
    • Aspectos en Tiempo de Compilación, que necesita un compilador especial.
    • Aspectos en Tiempo de Carga, los Aspectos se implementan cuando el Objeto Destinatario es cargado. Requiere un ClassLoader especial.
    • Aspectos en Tiempo de Ejecución.

Comenzando AOP con Spring 2.5

Antes de comenzar, debemos recordar (o conocer) lo que es el Patrón de Diseño Proxy. Para resumir toda la definición, un proxy es un objeto que luce como otro objeto pero añade funcionalidad especial de forma transparente. En Spring, una interfaz puede ser usado tras un proxy.

Proxy

En NB 6.1, creemos un proyecto simple (Java > Java Application) llamado SpringProxy. Luego, agregamos la biblioteca Spring Framework 2.5

Luego, crearemos la interfaz Conversacion, que tendrá un método llamado iniciarSesion().
package springproxy;


public interface Conversacion {

void iniciarSesion();
}

Y tenemos la siguiente implementación de la Interfaz
package springproxy;

public class ChatInterno implements Conversacion{

private String servidor;


public void setServidor(String servidor) {
 this.servidor = servidor;
}

public void iniciarSesion() {
 System.out.println("["+this.getClass().getName()+":"+this.toString()+"]");
 System.out.println("Iniciando sesion del servidor "+servidor);
}

}
Ahora, crearemos un archivo .xml para Spring. Entramos a File > New File, y seleccionamos Other | Spring XML Configuration File.
Lo llamaremos "aop-spring", y lo colocaremos en la carpeta src del proyecto.
Clic en "Next".
No seleccionamos nada en la última página, y hacemos clic en "Finish".
Notar que solo se está llamando al esquema "beans".

Escribimos el siguiente código en el archivo aop-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="chatInterno" class="springproxy.ChatInterno">
    <property name="servidor" value="Servidor Spring Framework"/>
</bean>
<bean id="chatProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="springproxy.Conversacion" />
    <property name="target" ref="chatInterno"/>
</bean>
</beans> 

Notemos cómo NB 6.1 nos ayuda a escribir el código, llenando los atributos, asignando variables de beans ya creados, etc.

Ahora, escribamos el siguiente código de nuestra clase Main.
 public static void main(String[] args) {
     ApplicationContext ctx = new ClassPathXmlApplicationContext("aop-spring.xml");
     Conversacion chat = (Conversacion) ctx.getBean("chatInterno");
     chat.iniciarSesion();
     System.out.println("corrido de " + chat.getClass());
     System.out.println();

     Conversacion proxy = (Conversacion) ctx.getBean("chatProxy");
     proxy.iniciarSesion();
     System.out.println("corrido de " + proxy.getClass());
 }

Ejecutemos el proyecto y veamos la salida:
[springproxy.ChatInterno:springproxy.ChatInterno@633e5e]
Iniciando sesion del servidor Servidor Spring Framework
corrido de class springproxy.ChatInterno

[springproxy.ChatInterno:springproxy.ChatInterno@633e5e]
Iniciando sesion del servidor Servidor Spring Framework
corrido de class $Proxy0 

Vemos que se trata del mismo objeto (vemos el ID ChatInterno@633e5e en ambos casos) pero realmente son dos clases diferentes. El primero es de la clase springproxy.ChatInterno y el segundo es de $Proxy0.
El Proxy contiene al objeto de ChatInterno, y cuando se ejecuta los métodos del Proxy, llama al método del objeto contenido.
El objeto de ChatInterno no sabrá de donde fue llamado. El Proxy podría realizar un código antes o después de llamar al método del objeto ChatInterno. Esto es lo que queremos hacer al usar AOP.

Un simple Advice

Lo que haremos ahora es que se ejecute un código antes de iniciar la sesión del chat. Para ello crearemos la clase ChatLogger que implementará la interfaz MethodBeforeAdvice.
package springproxy;

import java.lang.reflect.Method;
import java.util.Date;
import org.springframework.aop.MethodBeforeAdvice;


public class ChatLogger implements MethodBeforeAdvice{

  public void before(Method metodo, Object[] args, Object objetivo) throws Throwable {
      System.out.println("["+(new Date())+"] "+metodo.getName()+" llamado desde "+objetivo);
  }

} 

Ahora, lo instanciemos en el Spring
<bean id="chatLogger" class="springproxy.ChatLogger" /> 

y lo agreguemos en la lista de interceptores
 <bean id="chatProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="proxyInterfaces" value="springproxy.Conversacion" />
      <property name="target" ref="chatInterno"/>
      <property name="interceptorNames">
          <list>
              <value>chatLogger</value>
          </list>
      </property>
  </bean> 

Ejecutemos el proyecto, y veremos lo que pasa:
[springproxy.ChatInterno:springproxy.ChatInterno@17494c8]
Iniciando sesion del servidor Servidor Spring Framework
corrido de class springproxy.ChatInterno

[Tue Jun 03 00:52:34 PET 2008] iniciarSesion llamado desde springproxy.ChatInterno@17494c8
[springproxy.ChatInterno:springproxy.ChatInterno@17494c8]
Iniciando sesion del servidor Servidor Spring Framework
corrido de class $Proxy0

AOP usando proxy. Como ves, no hemos modificado nada de la clase ChatInterno.
En la clase ChatLogger podemos agregar el código que deseemos, sin modificar la funcionalidad de nuestra lógica de negocio.

El proyecto de este post se encuentra aquí: http://diesil-java.googlecode.com/files/SpringProxy.tar.gz

... esta historia continuará...

Bibliografía