Ejemplo sencillo de RMI con Java 5

Actualmente estoy realizando un proyecto para la maestria donde se nos pide el uso de varias tecnologías, entre ellas RMI. Para mi sorpresa veo que a partir de la versión 5 de java, ya no es necesaria la ejecución del compilador de RMI (rmic).

A continuación escribo un ejemplo sencillo, donde utilizo el patrón de diseño de Command para la elaboración del servidor.

Como ingredientes para este ejemplo solo necesitaremos dos clases y una interfaz para tener una comunicación entre servidor y cliente, empezare por la interfaz.

ServerRemote.java

package com.av.rmi.interfaces;

import java.rmi.Remote;
import java.rmi.RemoteException;

import com.av.rmi.Parametro;

/**
 * Servicio o punto de entrada en el servidor para ejecucion de distintas
 * acciones
 *
 * @author electrocucaracha
 *
 */
public interface ServerRemote extends Remote {

	public static final String OBJECT_STUB_REGISTRY_NAME = "ServerRemote";

	/**
	 * Accion generica a ser ejecutada en el servidor remoto
	 * @param accion
	 */
	Parametro ejecutar(Parametro parametro) throws RemoteException;
}//ServerRemote

Como ya sabemos dicha interfaz debe de heredar de java.rmi.Remote y todos sus metodos deben lanzar la excepcion java.rmi.RemoteException.

Para finalizar la parte del servidor tenemos su implementacion

ServerRemoteImpl.java

package com.av.rmi.impl;

import java.io.File;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

import org.apache.log4j.Logger;

import com.av.acciones.BaseAccion;
import com.av.exceptions.AvException;
import com.av.rmi.Parametro;
import com.av.rmi.interfaces.ServerRemote;

/**
 * Implementacion de punto de entrada a funciones promovidas en el servidor
 * 
 * @author electrocucaracha
 *
 */
public class ServerRemoteImpl implements ServerRemote {

	private static Logger log = Logger.getLogger(ServerRemoteImpl.class);
	private static final long serialVersionUID = 1L;
	private static final File f = new File("c:/av_server/bin/");
	
	protected ServerRemoteImpl() throws RemoteException {
		super();
		log.info("Inicio - ctor");
		
		log.info("Fin - ctor");
	}//ServerRemoteImpl
	
	@Override
	public Parametro ejecutar(Parametro param) throws RemoteException {
		log.info("Inicio - ejecutar(Accion accion)");
		
		BaseAccion accion = BaseAccion.getAccion(param.getAccion());
		Parametro p = null;
		if(accion != null){
			log.debug("Ejecutar : " + param.getAccion());
			try {
				p = accion.ejecutar(param);
			} catch (AvException e) {
				log.error("Ha ocurrido un error al intentar ejecutar la accion");
				throw new RemoteException("Ha ocurrido un error al intentar " +
						"ejecutar la accion", e);
			}
		}else{
			log.warn("Accion no encontrada");
		}
		
		log.info("Fin - ejecutar(Accion accion)");
		
		return p;
	}//ejecutar
	
	public static void main(String[] args){
		System.out.println("Starting server...");
		
		try {
			LocateRegistry.createRegistry(1099);
		} catch (RemoteException e) {
			log.error("Ha ocurrido un error al ejecutar el comando rmiregistry : " + 
					e.getMessage());
			e.printStackTrace();
		}
		
		System.setProperty ("java.rmi.server.codebase", f.toURI().toString());
		
		try{
			ServerRemoteImpl obj = new ServerRemoteImpl();
			ServerRemote stub = (ServerRemote)UnicastRemoteObject.
				exportObject(obj, 0);
			
			Registry r = LocateRegistry.getRegistry();
			r.bind(OBJECT_STUB_REGISTRY_NAME, stub);
			System.out.println("Server ready");
		}catch(Exception e){
			log.error("Ha ocurrido un error al registrar el servicio : " + 
					e.getMessage());
			e.printStackTrace();
		}
	}//main
}//ServerRemoteImpl

Hay muchos puntos que observar en esta implementación

1. Utilizo una instancia de File para obtener su URL, ya que Windows permite crear nombres de carpetas con espacios.

private static final File f = new File("c:/av_server/bin/");

2. Es necesario escribir el constructor por defecto

protected ServerRemoteImpl() throws RemoteException {

3. Esta linea es necesaria para evitar el tener que ejecutar el comando rmiregistry antes de la ejecución del servidor. Nota: Utilizo el puerto 1099 ya que es el puerto por defecto de para el servicio de rmiregistry de RMI.

LocateRegistry.createRegistry(1099);

4. Registro en codebase el path de los archivos que expondré sus métodos.

System.setProperty ("java.rmi.server.codebase", f.toURI().toString());

5. En caso de que el servicio o clase no herede direcamente de UnicastRemoteObject es necesario dinamicamente crear una instancia que lo realice. Nota: El segundo parámetro especifica el puerto que utilizara java para hacer las llamadas a nuestro servicio:

ServerRemote stub = (ServerRemote)UnicastRemoteObject.
				exportObject(obj, 0);

Para finalizar el ejemplo, basta crear un cliente que consuma los servicios, para esto he creado esta clase

Cliente .java

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import com.av.db.dataobjects.Usuario;
import com.av.rmi.CatalogoAcciones;
import com.av.rmi.Parametro;
import com.av.rmi.Parametro.Tipo;
import com.av.rmi.interfaces.ServerRemote;

/**
 * Clase temporal para probar RMI
 * 
 * @author electrocucaracha
 *
 */
public class Cliente {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String host = (args.length < 1) ? null : args[0];
		Registry registry = null;
		ServerRemote stub = null;
		Parametro p = new Parametro();
		Usuario[] usuarios = null;
		try {
			registry = LocateRegistry.getRegistry(host);
		    stub = (ServerRemote) registry.
		    	lookup(ServerRemote.OBJECT_STUB_REGISTRY_NAME);
		    p.setAccion(CatalogoAcciones.OBTENER_TODOS_USUARIO);
		    
		    p = stub.ejecutar(p);
		    
		    if(p.getValor(Tipo.OUTPUT) != null && 
		    		p.getValor(Tipo.OUTPUT) instanceof Usuario[]){
		    	usuarios = (Usuario[])p.getValor(Tipo.OUTPUT);
		    	for(Usuario u : usuarios){
		    		System.out.println(u);
		    	}
		    }
		} catch (Exception e) {
		    System.err.println("Client exception: " + e.toString());
		    e.printStackTrace();
		}	}
}//Cliente

Una vez compiladas las clases, solo se requiere que se ejecute primero la clase del servidor y después la del cliente, sin algún parámetro adicional.

ComboBox con checkboxes como items usando XAML

Me parececio bastante sencillo la forma de crear un nuevo control utilizando XAML, en este caso necesitaba una especie de filtro en el cual se pudieran agregar de una lista de opciones, varias. Aunque existe algun otro control que lo permite, me parecio bastante elegante utilizar un ComboBox que permitiera seleccionar varias opciones.

El resultado que se obtiene es como el siguiente:

Para lo cual necesitaremos agregar en nuestro archivo con extension *.xaml el siguiente codigo

        
            
                
                    
                        
                    
                
            
        

y para el control de los eventos, ya sea con C# o VB .Net el siguiente codigo.

VB .Net

Private Sub chk_Checked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
   ' TODO : Agregar el codigo para el manejo del evento
End Sub

Private Sub chk_Unchecked(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
   ' TODO : Agregar el codigo para el manejo del evento
End Sub

C#

        private void chk_Checked(object sender, RoutedEventArgs e)
        {
            // TODO : Agregar el codigo para el manejo del evento
        }

        private void chk_Unchecked(object sender, RoutedEventArgs e)
        {
            // TODO : Agregar el codigo para el manejo del evento
        }

Personalizacion de mensajes de error para JSF

En ocasiones es necesario personalizar los mensajes de error estándar que son mostrados en el cliente, estos mensajes pueden tener un formato comenzando con un prefijo de código de error seguidos por el código del mismo, y por ultimo una descripción del mensaje de error como puede ser algo como lo siguiente:

Validation Error: Value is greater than allowable maximum…

Para poder personalizar dicho mensaje, se necesitan dos cosas, la primera es necesario agregar la ruta del archivo de propiedades que sobreescribira los mensajes estándar, esto se define como un «message-bundle» en el archivo de configuracion de faces-config.xml (el nombre del archivo depende del sistema)

messages

y segundo, como es de suponerse, crear el archivo de propiedades con la ruta que especificamos agregando los valores que sobrescribiremos.

messages.properties

javax.faces.converter.IntegerConverter.INTEGER=Requiere un valor de tipo numerico

En esta pagina puedes encontrar todos los valores posibles para las claves de los mensajes estándar.

Modificadores de acceso – Default

El siguiente modificador de acceso sera Default, algo enredoso, pero muy valioso de tener en cuenta.

Como veremos a continuacion este modificador va de la mano con el paquete al cual pertenece la clase.

Escenario – Declaracion en la clase

  1. Pueden existir un sin fin de clases con el declarador de acceso por defecto pero solo una puede existir una clase publica
  2. ClasePublica.java

    package mipaquete;
    
    public class ClasePublica{
    }
    
    class OtraClase{
    }
    
    class OtraClaseNoPublica{
    }
  3. Puede referenciarse desde clases dentro mismo paquete
  4. OtraClasePublica.java

    package mipaquete;
    
    public class OtraClasePublica {
    	public void invocar(){
    		OtraClase clase = new OtraClase();
    	}
    }
  5. Puede heredarse por clases dentro mismo paquete
  6. ClaseHijaPublica.java

    package mipaquete;
    
    public class ClaseHijaPublica extends OtraClase{
    
    }
  7. No puede referenciarse desde clases de distintos paquetes.
  8. OtraClasePublica.java

    package otropaquete;
    
    import mipaquete.ClasePublica;
    
    public class OtraClasePublica {
    	public void invocar(){
    		OtraClase clase = new OtraClase();
    	}
    }

    OtraClasePublica.java:3: mipaquete.OtraClase is not public in mipaquete; cannot
    be accessed from outside package

  9. No puede heredarse por clases de distintos paquetes.
  10. ClaseHijaPublica.java

    package otropaquete;
    
    import mipaquete.OtraClase;
    
    public class ClaseHijaPublica extends OtraClase{
    }
    }

    ClaseHijaPublica.java:3: mipaquete.OtraClase is not public in mipaquete; cannot
    be accessed from outside package

Como resumen cuando omitamos el operador, y sea aplicado el operador por defecto tendremos que siempre mirar al paquete que pertenece la clase.

Modificadores de acceso – Public

Este sera el comienzo de una serie de posts en los cuales tratare de profundizar en el tema, tratando de abarcar la mayoria de los escenarios posibles.

Comenzare con el modificador de acceso Publico, que por ser menor restrictivo puede ser considerado facil de comprender.

Escenario – Declaracion en la clase

  1. Solo puede existir una clase publica por archivo *.java
  2. ClasePublica.java

    package mipaquete;
    
    public class ClasePublica{
    }
    
    class OtraClase{
    }
    
    class OtraClaseNoPublica{
    }
  3. Puede referenciarse desde clases dentro mismo paquete
  4. OtraClasePublica.java

    package mipaquete;
    
    public class OtraClasePublica {
    	public void invocar(){
    		ClasePublica clase = new ClasePublica();
    	}
    }
  5. Puede heredarse por clases dentro mismo paquete
  6. ClaseHijaPublica.java

    package mipaquete;
    
    public class ClaseHijaPublica extends ClasePublica {
    
    }
  7. Puede referenciarse desde clases de distintos paquetes siempre y cuando la clase sea importada
  8. OtraClasePublica.java

    package otropaquete;
    
    import mipaquete.ClasePublica;
    
    public class OtraClasePublica {
    	public void invocar(){
    		ClasePublica clase = new ClasePublica();
    	}
    }
  9. Puede heredarse por clases de distintos paquetes siempre y cuando la clase sea importada
  10. ClaseHijaPublica.java

    package otropaquete;
    
    import mipaquete.ClasePublica;
    
    public class ClaseHijaPublica{
    	public void invocar(){
    		ClasePublica clase = new ClasePublica();
    	}
    }

Con esto creo cubrir la mayoria de las preguntas que pudiesen surgir acerca de este modificador

java.lang.OutOfMemoryError‏

Hace tiempo me tope que esta excepcion y buscando en varios foros sugerian la ejecucion de las clases enviandoles parametros especiales( java -Xmx[memoria]) ya que la maquina virtual de java utiliza 100 Mb para la ejecucion de clases.

Nunca me parecio razonable la solucion, por dos razones la primera, la arquitectura debe de ser dirigida a aprovechar los recursos del sistema, no a malgastarlos;  la segunda, al ser un equipo de desarrollo esta solucion no podria ser aceptable.

Mi solucion:  Subir un servlet que como primera instruccion llamara al recolector de basura

System.gc();

Con esto parece ser que libero la memoria necesaria para que el web server en mi caso iPlanet, respondiera.

System.Attribute vs java.lang.annotation.Annotation

Siempre es necesario contar con informacion adicional sin ser mezclada con las reglas de negocio, esta informacion puede tener distintos propositos, ya sean reglas impuestas por la organizacion o informacion necesaria pero sin afectar las reglas de negocio.

En este post no prentendo crear un debate sobre las distintas tecnologias, mi opinion es neutral y creo que ambas son fabulosas aplicadas correctamente.

En cuanto a esta informacion adicional, ya sean metadatos o atributos, si bien para .Net esta tecnologia lleva varias versiones presente para Java es incoorporada a partir de su version 5.

Para mostrar las diferencias en implementacion entre ambos mostrare un ejemplo que permita agregar datos de autor y version en las clases y metodos declarados. Por cuestiones de antigüedad comenzare con .Net.

[System.AttributeUsage(System.AttributeTargets.Class |
                       System.AttributeTargets.Struct,
                       AllowMultiple = true)]
public class Author : System.Attribute
{
    private string name;
    public double version;
    public Author(string name)
    {
        this.name = name;
        version = 1.0;
    }
}

La explicacion es sencilla, se hereda de la clase Attribute, se pueden agregar atributos a esta clase como pueden ser si la clase es serializable, si es visible en un componente COM, que alcance tiene la clase que en nuestro ejemplo solamente tiene aplicacion en clases y metodos, a continuacion agrego otra clase la cual utilizaria este atributo.

[Author("H. Ackerman", version = 1.1)]
[Author("M. Knott", version = 1.2)]
class SampleClass
{
    // H. Ackerman's code goes here...
    // M. Knott's code goes here...
}

Estos ejemplos fueron tomados de la pagina de msdn

Ahora metadatos con Java

import java.lang.annotation.*;

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Author{
    String name();
    double version() default 1.0;
}

Segun se en java no se permite repetir las anotaciones, por lo que veo una desventaja en su contra, a continuacion el ejemplo. En cuanto a su explicacion, segun he visto se crea a partir de una clase generica, aun cuando esta es una interface, y sus miembros parecen ser funciones.

@Author(name ="H. Ackerman", version = 1.1)
class SampleClass
{
    // H. Ackerman's code goes here...
}


Spring .Net

¿Y porque en .Net no? Los buenos frameworks son para compartirse.

Primero, analicemos un poco de que trata dicho framework y que ventajas nos puede traer para nuestros desarrollos. Comencemos con un poco de historia de los problemas con los cuales nos hemos enfrentado despues de varios cientos de miles de lineas de codigo. Recordemos que las buenas practicas de programacion fueron trasmitidas con base en la experiencia, y su fin es adaptarnos mejor a los cambios, cambios que cada vez son mas continuos y exigentes o caprichosos y cuando una aplicacion llega a ser lo bastante grande y dichas modificaciones pueden llegar a ser grandes dolores de cabeza.

Es aqui cuando entra en juego dos palabras clave desacoplar y centralizar, si buscamos que nuestro codigo se mantenga centralizado y desacoplado o desarticulado, es decir, que este no mantenga fuertes dependecias entre si(hablando de librerias, tecnologias, base de datos, etc.), lograremos que las modificaciones solicitadas podamos realizarlas de manera rapida y confiable.

Y para que volver a inventar la rueda, aqui es donde entra en juego Spring .Net al basarse, en tecnologias maduras y probadas por desarrolladores Java. Dicho framework nos facilita la tarea de centralizar y desacoplar nuestro codigo, apoyandonos de archivos de configuracion. Creo que habra que poner especial atencion, por mencionar unos cuantos, a los provedores de informacion ya que estos suelen cambiar.

Por ultimo, hace falta agregar dos definiciones importantes en este tema, el primero es Inversion of Control(IoC) el cual, implementando en una aplicacion, permite el control configurable del flujo del programa.  El segundo y no menos importante es Dependency Injection(DI), permitiendo el acoplamiento de clases de manera modificable o configurable, en el caso de Spring .Net, se localiza en un archivo xml.

Desarrollo guiado por pruebas

Hace tiempo que no posteo algo y que mejor que regresar con una buena metodologia. Se trata de Test-driven development (TDD) una metodologia agil que se enfoca a satisfacer los casos de prueba del cliente dejando en segundo plano el un profundo diseño y analisis, pero reforzando el vinculo entre desarrollo y pruebas, al estilo de Code and fix V.2.

Los pasos de realizacion de dicha tecnica son sencillos e iterativos.

1. Consiste en identificar claramente los casos de prueba.

2. Mas tarde, se implementan los casos de prueba, al no existir aplicacion o codigo previo dichos casos de prueba fallaran lo cual es el objetivo. Para este punto y verificar de manera adecuada que los casos de prueba fallen, nos apoyamos de herramientas de casos de prueba unitaria como son NUnit (en el caso de .Net) y JUnit (para Java), aunque NUnit se basa en JUnit las diferencias entre ambos puden ser abismales. Al carecer java de atributos en el lenguaje es necesario seguir un estandar para la implementacion de casos de prueba; por otro lado cuenta con un gran soporte dentro de sus IDE para dicho framework.

3. Despues, se implementa el codigo para que dichos casos de prueba sean superados, con lo cual nos apoyaremos de dichas herramientas para lograr nuestro objetivo.

4. Por ultimo, se refactorizara y buscara el performance de nuestro codigo, y se generaran nuevos casos de prueba iterando asi, dicho ciclo.

Dicha metodologia me parecio bastante util en el diseño de librerias y frameworks, aunque apenas voy conociendo su potencial me agrada la idea de no borrar codigo, ni hacer mezclas extrañas, y por supuesto, realizar las pruebas primero. Considero valioso la incorporación de dicha tecnia en futuros proyectos.