martes, 6 de noviembre de 2012

Java RESTfull web service with json and jboss II

En el articulo anterior vimos las principales ventajas e incovenientes (desde el humilde punto de vista de quien suscribe) de los servicios web SOAP y REST.

Hoy vamos a realizar un ejemplo práctico de un servicio REST que produzca datos en formato JSON, para luego consumirlo desde plataformas moviles (iOS y Android).

El software que he usado para hacer este ejemplo es:
  • Mac OS X 10.8
  • JDK 1.6
  • Jboss 7.1.1
  • Eclipse Juno
  • Maven3
Vamos a crear una aplicacion ear con un modulo war en principio donde alojaremos nuestro servicio REST. Al final del post veremos como podemos transformar de una forma sorprendentemente facil nuestro componente web en un EJB de sesión sin estado, lo cual abre muchas otras opciones. El ejemplo tiene un caracter didactico, pero para el proposito nos vale perfectamente.

Vamos a crear una clase que nos ofrezca un servicio web que devolverá una lista de paises. El servicio aceptara un parametro de forma que:
  • Si vale 0 devolverá paises europeos.
  • Si vale 1 devolverá paises no europeos.

Esta distinción en nuestro ejemplo la haremos a hardcode con un 'if'. Pero planteará la base para poder bifurcar y profundizar nuestro código como queramos/necesitemos.

Bien... comenzamos!, primero crearemos la estructura de maven en eclipse, vamos a hacerlo sin asistentes ni ayudas para que todo quede mas claro:

Creamos un nuevo proyecto en eclipse:  A través de File->New->Java Project creamos un nuevo proyecto que llamaremos 'CountryList'. En el protecyo recien creado añadiremos la siguiente estructura:



Con nuestro proyecto seleccionado accedemos a Project -> Properties -> Java Build Path. En el cuadro de diálogo de la derecha seleccionamos la pestaña 'Source'. Pulsamos el botón 'Add Folder...' y seleccionamos la carpeta interna de proyecto CountryList '/war/src/main/java' y pulsamos OK.

Ahora, como se puede ver tenemos un proyecto maven (pom.xml en el directorio raiz) que es padre de otros dos proyectos maven (pom.xml dentro de ear y pom.xml dentro de war). Vamos a dejar la definición de cada pom.xml para el final.

Creamos dentro de nuestro directorio de fuentes del war (que hemos creado hace dos párrafos) el siguiente paquete: 'com.country.list'.

Dentro del nuevo paquete que hemos creado, añadimos una clase llamada CountryList.java.

El codigo de la clase será algo asi como:

package com.country.list;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;


@Path("/countryList")
public class CountryList {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/getCountry")
    public List<String> getCountry(@DefaultValue("") @QueryParam("countryType") String tipo){

        ArrayList<String> lista = new ArrayList<String>();
        if ("0".equals(tipo)) {
        
           lista.add("Espana");
           lista.add("Francia");
           lista.add("Alemania");
           lista.add("Portugal");
         
        } else if ("1".equals(tipo)) {
           lista.add("USA");
           lista.add("Argentina");
           lista.add("Guatemala");
           lista.add("Jamaica");
         
        }

        return lista;
    }
}

Vemos que hemos usado una serie de anotaciones, estas anotaciones pertencen a la librería de java JAX-RS mediante la cual podemos generar servicios web de tipo REST.

Para eliminar los errores de compilacion de las clases que usamos se puede, para aquellos que tengais m2eclipse, usar el menu contextual para usar la opción 'maven -> enable dependency management'. No es mi caso, asi que añadiré la librería de jboss a mi proyecto. La librería está en <JBOSS7.1.1_HOME>/modules/javax/ws/rs/api/main/jboss-jaxrs-api_1.1_spec-1.0.0.Final.jar. A traves de (con el proyecto seleccionado) Project -> properties -> Java Build Path, Pestaña 'Libraries' pulsamos 'Add External jars...' y seleccionamos dicho jar. También podemos copiarla a nuestro directorio 'lib' y usar el botón 'Add Jars...'.
 
Una vez resueltos los problemas de compilación, pasemos describir las anotaciones usadas:

  • @Path -> Especifica el path relativo dentro de la URL para nuestra clase o método.
  • @GET -> Especifica el tipo de peticion HTTP (también existen @POST, @PUT, @DELETE, @HEAD, etc...).
  • @Produces -> especifica el tipo MIME de la respuesta, en este caso JSON. También existe @Consumes para especificar el MIME de la entrada.
  • @DefaultValue -> Permite indicar el valor por defecto de un parametro si no esta especificado en la petición.
  • @QueryParam -> Usado para asociar a un parametro en la peticion con una variable.

Hemos identificado nuestra clase como '/countryList' y a nuestro método como '/getCountry'. Una vez hayamos definido el artifact del war veremos como invocar mediante una URL a nuestro servicio.

¿Ya hemos acabado?

Aun no... nos queda un pequeño paso. Indicar a Jboss que tipo de URI debe tratar como servicios web REST, para ello editamos el archivo web.xml, que debe quedar asi:

<web-app id="CountryList" version="2.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee

    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>CountryList</display-name>

    <servlet-mapping>

        <servlet-name>javax.ws.rs.core.Application</servlet-name>

        <url-pattern>/*</url-pattern>

    </servlet-mapping>

</web-app>

Indicamos que el servlet Application debe atender todas la url que cumplan el patron *. Esto es, que toda peticion que llegue al contexto de nuestro war, la trate con un servicio REST. Este servlet es parte del JAX-RS y nos proporciona un 'puente' entre una URL dentro del contexto de nuestra aplicacion web hacía el servicio REST.


El pom.xml del proyecto Raiz quedará como sigue (este es el padre):

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.country.list</groupId>
  <version>1.0</version>
  <artifactId>countrylist</artifactId>
  <packaging>pom</packaging>
  <name>countrylist</name>
  <modules>
  <module>ear</module>
  <module>war</module>
  </modules>

  <repositories>
    <repository>
      <id>java.net2</id>
      <name>Java.Net Maven2 Repository, hosts the javaee-api dependency</name>
      <url>http://download.java.net/maven/2</url>
    </repository>
  </repositories>

  <build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.3.2</version>
      <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <encoding>ISO-8859-1</encoding>
      </configuration>
    </plugin>
  </plugins>
  </build>

  <dependencies>
  <dependency>
       <groupId>org.jboss.spec.javax.ws.rs</groupId>
         <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
          <version>1.0.0.Final</version>
          <scope>provided</scope>
   </dependency>
    </dependencies>
</project>


El pom.xml del subproyecto ear quedará como sigue (/ear/pom.xml)

<project>
   <modelVersion>4.0.0</modelVersion>
   <parent>
    <groupId>com.country.list</groupId>
    <version>1.0</version>
    <artifactId>countrylist</artifactId>
  </parent>
  <groupId>com.country.list</groupId>
  <artifactId>ear</artifactId>
  <packaging>ear</packaging>
  <version>1.0</version>
  <name>Modulo ear Java EE 6</name>
  <url>http://maven.apache.org</url>
  <build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-ear-plugin</artifactId>
      <version>2.3.2</version>
      <configuration>
        <version>1.4</version>
        <defaultJavaBundleDir>lib/</defaultJavaBundleDir>
        <defaultLibBundleDir>lib</defaultLibBundleDir>
        <generateApplicationXml>true</generateApplicationXml>
      </configuration>
    </plugin>
  </plugins>
  <finalName>countrylist</finalName>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.country.list</groupId>
      <artifactId>countrylistWar</artifactId>
      <version>1.0</version>
      <type>war</type>
    </dependency>
  </dependencies>
</project>


Por ultimo, el subproyecto del war quedará como sigue (/war/pom.xml)

<project>
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.country.list</groupId>
   <artifactId>countrylistWar</artifactId>
   <packaging>war</packaging>
   <version>1.0</version>
   <name>Modulo war</name>
   <parent>
      <groupId>com.country.list</groupId>
      <artifactId>countrylist</artifactId>
      <version>1.0</version>
   </parent>
   <dependencies>
      <dependency>
       <groupId>org.jboss.spec.javax.ws.rs</groupId>
         <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
          <version>1.0.0.Final</version>
          <scope>provided</scope>
   </dependency>
  </dependencies>
  <build>
      <resources>
          <resource>
              <directory>
                  src/main/resources
              </directory>
          </resource>
      </resources>  
  </build>
</project>


Ahora que ya tenemos nuestro proyecto completo, abrimos un terminal y navegamos hasta el workspace, necesitamos conectividad con internet para este paso. Si tu conexion a internet está a través de un http Proxy debe editar la configuracion de maven (en maven3): <MAVEN_HOME>/conf/settings.xml, buscamos la entrada '<proxies>' y añadimos el nuestro ahi:

    <proxy>

      <id>ID</id> <!--Identificador que queramos darle -->

      <active>true</active> <!-- Si esta activo, indicamos que si -->

      <protocol>http</protocol> <!-- Protocolo, generalmente http -->

      <username></username> <!-- Usuario, si el proxy requiere autenticacion, si no es asi dejar en blanco -->

      <password></password> <!--Password, si el proxy requiere autenticacion, si no es asi dejar en blanco -->

      <host>IP</host> <!-- Ip de la maquina que hace de proxy -->

      <port>PUERTO</port> <!-- Puerto de escucha de la maquina que hace de proxy -->

      <nonProxyHosts>acme.com|local.net|some.host.com</nonProxyHosts> <!-- Ip y/p host que no queramos que salgan por el proxy  -->

    </proxy>

    
Como ibamos diciendo, abrimos un terminal y accedemos al workspace de eclipse, justo al directorio raiz del proyecto <WORKSPACE_PATH>/CountryList. Ejecutamos 'mvn clean install'. Esta orden compilara y generara nuestro proyecto (nuestro ear), el cual se generará en el directoro 'target' dentro dde ear en nuestro proyecto (F5 para refrescar).

Ahora solo nos falta copiarlo al directorio standalone de jboss, en la ruta <JBOSS7.1.1_HOME>/standalone/deployments y arrancarlo con <JBOSS7.1.1_HOME>/bin/standalone.sh (o .cmd en caso de windows). Cuando arraque, abrimos un navegador y escribimos los siguiente (si tienes jboss escuchando en otro puerto y/o otra ip, actualiza la URL convenientemente):

http://localhost:8080/countrylistWar/countryList/getCountry?countryType=0, lo cual nos dará un array en formato JSON de paises europeos.

http://localhost:8080/countrylistWar/countryList/getCountry?countryType=1 nos dará un array en formato JSON de paises no europeos. Donde:

  • countrylistWar -> Es el contexto de nuestra aplicacion Web
  • countryList -> es el mapeo que le dimos a nuestra clase.
  • getCountry -> es el mapeo que le dimos a nuestro metodo.
  • countryType -> es como llamamos a nuestro parametro.

Y ya está, con un poco de trabajo y de una forma sencilla tenemos nuestro servicio básico REST en java funcionando.

Si queremos que además nuestra clase sea un EJB, solo tenemos que anotarla con @Sateless y añadir la dependencia de la libreria de ejb para maven.

En el proximo articulo realizaremos un cliente Android y otro iOS que consuman este servicios.

lunes, 5 de noviembre de 2012

Java RESTfull web service with json and jboss

SOAP vs REST. Java Web Services  

Aunque la comparativa entre servicios SOAP y RESTfull no es nueva ha adquirido un enfoque especial debido a las plataformas móviles que están en el mercado con una fuerza innegable, cuyos dos estandartes son iOS y Android.

Vamos a ver un poco de historia de ambos, las principales ventajas e inconvenientes bajo la humilde opinión del que suscribe y finalmente en sucesivos artículos veremos un ejemplo de creación de un servicio RESTfull con Java, JSON, maven y jboss 7.1.1, así como un ejemplo de clientes en Android e iOS.


SOAP

Cuando se habla de servicios web a aquellos que nos dedicamos a la consultoría 'estándar', lo primero que nos suele venir a la cabeza es SOAP (Simple Object Access Protocol). SOAP es un estándar de comunicaciones basado en XML, mediante SOAP se define un contrato (WSDL) que es implementado por el servidor y consumido por el cliente.

La tarea de definir un servicio SOAP es relativamente sencilla, una vez definido el servicio las herramientas de los servidores de aplicaciones o los frameworks de terceros se encargan de generar el contrato (WSDL) así como los archivos necesarios para los clientes. SOAP ofrece unas ventajas muy claras como son:

  • Definición de un contrato en todos los métodos que ofrece el servicio web.
  • Independencia de la capa de transporte, se puede usar SOAP sobre HTTP, SMTP, TCP, JMS, etc…
  • Independencia del lenguaje de programación usado para crear el servicio y para consumirlo, se puede crear en servicio en Java y consumirlo en .NET por ejemplo.
  • Permite la interoperabilidad de distintas plataformas.

Un poco de historia de SOAP en java

Los servicios web en java tiene su primera especificación formal en JAX-RPC 1.0 y JAX-RPC 1.1 (Java Api for XML - Remote Procedure Call). Esta especificación fué mejorada con lo que iba a ser JAX-RPC 2.0 que finalmente acabó siendo JAX-WS (Java Api for Xml - Web Services). Hay varias implementaciones de JAX-RPC como por ejemplo Axis (Apache) o JWSDP (Java Web Service Developer Pack, de Sun Microsystems). En cuanto a JAX-WS tenemos Axis2 (apache) o Metro (evolución de JWSDP, Oracle).

JAX_RPC está basado en java 4, mientras que JAX-WS está basado en Java 5 usando anotaciones y demás características introducidas en esta versión de java.

Para detalles concretos os dejo este enlace de ibm.

En la práctica, y bajo mi experiencia profesional y personal la verdad es que cumple con sus ventajas aunque con algunos 'peros':

  • Es cierto que ofrece un contrato cerrado, pero esto también ofrece unas limitaciones con ciertos objetos del lenguaje usado para crear el servicio, lo cual añade una restricción al uso del lenguaje.
  • Hacer un cambio en la signatura de algún método del servicio web implica tener que regenerar el cliente del servicio web, lo cual para los clientes reales -las personas- puede ser un verdadero trastorno, por no hablar de los inconvenientes de los fallos puntuales por la falta de comunicación de estos cambios, bien sea por no transmitirlos o porque el receptor creyó entender otra cosa… algo que sobre el papel parece ridículo, pero que es mas común de lo que se puede desear.
  • El transporte sobre XML es una ventaja porque te desvincula del transporte usado (HTTP, etc..), pero esto también puede ser un problema… usar esto por ejemplo en algo ligero como Javascript puede ser literalmente la muerte en vida… En una plataforma móvil, la cual está limitada en recursos, la carga añadida de trafico de red por el volumen del XML así como su tratamiento de recepción/envío es una carga añadida que nos puede jugar una mala pasada en picos de actividad del dispositivo…

RESTfull

Fue definido por Roy Fielding en su tesis doctoral. REST (Representational State Transfer), es un estilo de arquitectura. Las principales ventajas son:

  • No añade capas adicionales como SOAP lo cual lo hace mas ligero.
  • Usa el transporte HTTP como medio de comunicación.
  • No mantiene estado en las peticiones.
  • Puede transferir los datos a través de JSON (JavaScript Object Notation), a través de XML o de ambos.
  • Se basa en Java 5 o superior, con lo cual aporta anotaciones que facilitan mucho su codificación.
  • Define un método como una URI, en un formato que recuerda mucho al de directorios, y lo hace accesible a través de HTTP.

Historia REST en Java

Al principio, cuando Roy Fielding lo presentó en California en el año 2000, no tuvo una gran transcendencia, pero años después acabó desplazando a SOAP y se convirtió en una parte de la especificación de java, la JSR-311. La implementación de java es JAX-RS.

Sitios como Google, Facebook, Flickr, Twitter, etc… están basados en servicios de este tipo.


SOAP Vs REST

Pues ahora que hemos visto un poco de ambos ya nos podemos hacer una ligera idea de los puntos fuertes y débiles de cada uno… así que al final nuestra decisión de cual usar dependerá de que es lo que queramos hacer,  por poner dos ejemplos:

  • Si necesitamos mantener transacciones, o mantener el estado deberíamos optar por SOAP.
  • Si vamos a integrarnos con plataformas como JavaScript, Android, iOS, etc.. deberiamos optar por REST.

En el próximo articulo vamos a realizar un ejemplo práctico de la creación de un servicio web REST con Java que transmitira datos en formato JSON, para desplegarlo en JBOSS 7.1.1. Finalmente, en un tercer artículo realizaremos un cliente en Android y otro iOS que lo consuman.

sábado, 30 de junio de 2012

Java. Maps Iteration/Recorridos sobre mapas

Hola!

Hoy vamos a ver un poco acerca de los distintos métodos de recorrer mapas en java, esto es válido para toda aquella implementación disponible de Map (HashMap, TreeMap, etc...).

Desde la versión 5 de java en adelante, hay nuevas herramientas para recorrer mapas que pueden acelerar el rendimiento de nuestra aplicación de manera notable, como veremos en la conclusión de este artículo. Vamos a ver varios métodos de recorrer mapas, sus ventajas y sus incovenientes.


Imaginemos que tenemos el siguiente mapa:

Map hm = new HashMap();

for (int i=0; i<100000;i++){
    hm.put(i, i);
}
Ahora, vamos a ver distintos métodos de recorridos sobre mapas: 

long ini = System.currentTimeMillis();

// Metodo1

for (Map.Entry entry : hm.entrySet()) {
    System.out.println("Clave: " + entry.getKey() + ", Valor: " + entry.getValue());
}

long fin = System.currentTimeMillis();
long ini2 = System.currentTimeMillis();

//Metodo 2. Iteramos solo sobre claves
for (Integer clave : hm.keySet()) {
    System.out.println("Clave: " + clave);
}

long fin2 = System.currentTimeMillis();
long ini21 = System.currentTimeMillis();

//Metodo2-1.Iteramos sobre valores
for (Integer valor : hm.values()) {
    System.out.println("Valor: " + valor);
}

long fin21 = System.currentTimeMillis();
long ini3 = System.currentTimeMillis();

//Metodo 3
Iterator&gt; entries = hm.entrySet().iterator();

while (entries.hasNext()) {
    Map.Entry entry = entries.next();
    System.out.println("Clave: " + entry.getKey() + ", Valor: " + entry.getValue());
}

long fin3 = System.currentTimeMillis();
long ini4 = System.currentTimeMillis();

// Metodo 4
for (Integer clave : hm.keySet()) {
    Integer valor = hm.get(clave);
    System.out.println("Clave: " + clave + ", Valor: " + valor);
}

long fin4 = System.currentTimeMillis();
System.out.println("tiempo metodo 1: " + (fin-ini));
System.out.println("tiempo metodo 2: " + (fin2-ini2));
System.out.println("tiempo metodo 21: " + (fin21-ini21));
System.out.println("tiempo metodo 3: " + (fin3-ini3));
System.out.println("tiempo metodo 4: " + (fin4-ini4));


Describimos los métodos que hemos implemetado
  • Método 1: Recorremos el mapa usando Map.Entry, el cual está disponible desde Java 1.5. Este objeto nos da acceso tanto a las claves como a los valores, devuelve un Set de Map.Entry el cual recorremos con un for-each al estilo de java 5 y superior.
  • Método 2: Recorremos solo los valores ( o las claves) ya que no nos hace falta el resto de la información.
  • Método 3: Parece redundante con respecto al método 1, introduce un iterador. En la conclusión veremos las ventajas con respecto al método 1, que las hay.
  • Método 4: Recorremos el mapa con un iterador (las claves), y hacemos get para obtener los valores en cada pasada.
Vamos a realizar 10 ejecuciones, obteniendo el tiempo (en milisegundos) medio de ejecución para cada uno de los métodos:
Metodo1 Metodo2 Metodo21 Metodo3 Metodo4
1 2291 1372 1406 2686 2591
2 2107 1428 1495 2650 2637
3 2033 1488 1453 2821 2709
4 2096 1302 1564 2745 2411
5 2070 1503 1391 2477 2610
6 2161 1417 1453 2627 2584
7 2405 1549 1361 2726 2665
8 2345 1418 1403 2776 2536
9 2343 1357 1547 2592 2675
10 2197 1493 1320 2791 2723
Media 2204,8 1432,7 1439,3 2689,1 2614,1
% 100 64,98 65,28 121,97 118,56
Conclusiones En base a los resultados de las pruebas, podemos concluir que:

  1. Si solo nos interesan los valores o las claves del mapa, debemos decantarnos por el método 2, ya que son un 35% mas eficientes que el mejor del resto de métodos.
  2. Si vamos a recorrer el mapa, debemos usar el método 1, ya que como se ve en los resultados es mas eficiente, un 20%, que el resto de recorridos completos.
  3. Si necesitamos borrar elementos del mapa, debemos usar el método 3, ya que el uso de Map.Entry no nos asegura el borrado. Aunque el rendimiento apriori es un 22% peor que el método 1, al ir eliminando en cada pasada del bucle elementos del mapa, este rendimiento mejorará y se equiparará al método 1 (aunque depende de nuestra política de borrado).
  4. Si estamos en un sistema anterior a Java 5, debemos usar el método 4 obligatoriamente. Si usamos herramientas de calidad como FindBugs, detectará este tipo de operaciones como ineficiente, invitándonos a cambiarla. Usamos el método 1 (ojo, como digo solo podemos hacerlo si estamos en 1.5 o superior).
Bueno, pues esto es todo por hoy :) Espero que sea útil y a alguien el sirva! Hasta pronto!!!

sábado, 26 de mayo de 2012

Java hashCode and equals

Hola!

Hoy quiero hablaros de dos métodos que pasan desapercibidos la mayoría de las ocasiones, pero que pueden optimizar nuestras colecciones y mapas basadas en Hash, como HashMap, HashSet o HashTable de forma sorprendente.

Estos objetos (HashMap, HashSet, etc...) se basan en el hash para almacenar la información, y en equals para determinar colisiones (dos claves iguales), de manera que si necesitamos sobreescribir estos métodos en nuestras clases y usamos éstas como clave de alguno de estos objetos, debemos ser cuidadosos con la estrategia y el diseño que elegimos, ya que veremos con un ejemplo que aún cumpliendo el contrato de 'Object' en cuanto a equals y hashCode, podemos empobrecer el rendimineto de nuestro sistema enormemente.

Veamos un poco de teoria, según la API de java (http://docs.oracle.com/javase/6/docsapi/index.html), el método equals debe ser:

  • Reflexivo: Para cualquier valor de x que no son nulos de referencia, x.equals (x) debe devolver true.
  • Simétrico: Para cualquier no nulo de referencia, los valores x e y, x.equals (y) debe devolver true si y sólo si y.Equals (x) devuelve true.
  • Transitivo: Para los valores de referencia que no son nulos x, y, z, si x.equals (y) devuelve true y y.Equals (z) devuelve true, x.Equals (z) debe devolver true.
  • Consistente: Para cualquier no nulo de referencia los valores x e y, varias invocaciones de x.Equals (y) siempre devuelven verdadero o falso de vuelta constantemente, siempre que no es igual a la información utilizada en las comparaciones de los objetos se ha modificado.
  • Para cualquier valor de x que no son nulos de referencia, x.equals (null) debe devolver false.

El método hashCode debe cumplir:

  • Cada vez que se invoca en el mismo objeto más de una vez durante una ejecución de una aplicación Java, el método hashCode siempre debe devolver el mismo entero, siempre que no es igual a la información utilizada en las comparaciones sobre el objeto se ha modificado. Este entero no tiene por qué mantener la consistencia de una ejecución de una aplicación a otra ejecución de la misma aplicación.
  • Si dos objetos son iguales de acuerdo con el método equals (Object), y luego llamar al método hashCode de cada uno de los dos objetos tiene que producir el resultado mismo entero.
  • Si dos objetos NO son iguales de acuerdo al método 'equals' (java.lang.Object), entonces la llamada al método hashCode de cada uno de los dos objetos que puede producir diferentes resultados enteros. Sin embargo, el programador debe ser consciente de que la producción de distintos resultados desiguales para los objetos enteros puede mejorar el rendimiento de tablas hash.

La propia API nos está indicando que se puede mejorar las hash, pero, veamos un ejemplo. Imaginemos esta clase:


public class MiObjeto {

    int x;
    int y;

    public MiObjeto(int i) {
        x = i;
        y = i;
    }

    @Override
    public int hashCode(){
        return 0;
    }

    @Override
    public boolean equals(Object o){
        if (o instanceof MiObjeto) {
            MiObjeto aux = (MiObjeto) o;
            if (this.x == aux.x && this.y == aux.y){
                return true;
            }
        }
        return false;
    }
}


Esta clase define objetos del tipo 'MiObjeto', tiene dos miembros enteros (x e y) y un constructor publico que recibe un entero y los asigna tanto a x como a y. Se ha redefinido el método equals y el método hashCode de forma que:

Dos objetos serán iguales si sus miembro x e y son iguales. El hashCode es 0.

¿Cumplimos el contrato de hashCode?
Si, si dos objetos son iguales su hashCode será igual, si son distintos será igual también (el contrato dice que si dos objetos son distintos segun equals, su hashCode puede ser igual o no). Luego cumplimos el contrato.

Ahora imaginemos esta clase:

public class HashTest {

    public static void main (String[] args) {

        HashMap<MiObjeto, MiObjeto> hm = new HashMap<MiObjeto, MiObjeto>();

        long ini = System.currentTimeMillis();

        for (int i=0; i<100000; i++) {
            MiObjeto a = new MiObjeto(i);
            MiObjeto b = new MiObjeto(i+1);

            hm.put(a, b);
        }

        System.out.println(hm.size());

        long fin = System.currentTimeMillis();
        System.out.println("tiempo: " + (fin-ini));
    }
}

Creamos en el main 100.000 objetos 'MiObjeto' y los usamos tanto como clave como valor dentro de un HashMap, cada objeto tendrá un valor en x e y distintos para evitar colisiones. Medimos el tiempo que tarda en almacenar estos objetos.

El resultado de esta ejecucion es:
100000
tiempo: 42754

Como vemos, ha empleado en ejecutar el bucle 42,75 segundos.

Vamos a realizar un pequeño ajuste, y a lanzar otra vez el mismo programa, vamos a cambiar el método hashCode para que quede de esta forma:

@Override
public int hashCode(){
    return x;
}

Seguimos cumpliendo el contrato con esta implementación, ejecutamos:

100000
time: 31

Ahora, nuestro programa ha tardado 31 milisegundos en hacer lo mismo. Una mejora de rendimiento del 137.916%... y no, no hablamos de astronomía :). Ha tardado 1379 veces menos.

¿A que se debe esto?

Los objetos basados en Hash determinan, a partir de hashCode, el 'bucket' donde almacenar la pareja clave-valor. Dentro de cada bucket en el caso de HashMap hay una linked-list que va almacenando los objetos, si el hashcode es siempre igual, o cambia poco, el bucket será siempre o casi siempre el mismo por lo que las operaciones de almacenamiento cada vez serán mas pesadas.

Lo visto demuestra que debemos tender a elegir una implementación de hashCode, junto con equals, que repitan valores lo menos posible.

Como aporte, la implementación por defecto heredada de hashCode, devuelve la representación numérica del puntero del objeto en memoria.

Hasta la próxima!! :)

domingo, 22 de abril de 2012

JTA vs XA (weblogic and Oracle) distributed transactions / Transacciones distribuidas

En esta ocasión vamos a ver unas determinadas casuísticas que a veces pueden ser un verdadero quebradero de cabeza, de cara a identificar y resolver este tipo de problemas. Este articulo se basa en tecnologías weblogic y oracle.

En un contexto de transacciones disribuidas, aquellas en las que realizamos cambios en varias BBDD como una única transacción o en las que en una misma transacción participan recursos transaccionales de varios tipos, pueden plantearse problemas como este:

java.sql.SQLException:
Unexpected exception while enlisting XAConnection java.sql.SQLException: Not valid XID...

Cuando weblogic inicia una transaccion (bien sea a través del contenedor de EJBs o bien a través de transacciones de usuario 'UserTransaction') y se usa una conexión XA (transaccional) de oracle, la transacción inciada en Oracle tiene como Transaction Manager (manejador de la transaccion) a la transaccion creada en weblogic, es este 'Transaction Manager' quien se debe de encargar de manejar el ciclo de vida de las transacciones asociadas a el de una manera correcta.

El problema descrito viene ocasionado porque el tiempo de timeout de JTA (manager) es mayor que el tiempo establecido al timeout de la  transaccion XA, por lo que la transaccion en oracle genera un timeout, desvinculándose de su transaction manager de una forma no natural y quedando en un estado inconsistente, acaparando los recursos que ha solicitado hasta que la BBDD identifique a la transaccion como InDoubt y los libere.

¿Como podemos evitar esto?

Una regla a seguir es la siguente:

Debemos asegurar que el tiempo de timeout de las transacciones JTA (bien sea global o específicos de un EJB o una transaccion aislada) está asignado a un valor menor que el mas pequeño de los tiempos de timeout definidos para los recursos XA (como las conexiones XA de Oracle, o algunos recursos Tuxedo).

Para esto asignamos en el connection pool JBDC para conexiones XA el valor de XASetTransactionTimeout a true y XATransactionTimeout a 0 (de esta forma el timeout de XA se ajusta automaticamente al tiempo de timeout del Transaction manager). Si el sistema en cuestion tiene tiempos definidos de timeout que resultan de un estudio de vinculación con otros sitemas, se debe asignar el valor de XATransactionTimeout a un valor mayor al de JTA Transacion timeout.

Podemos realizar estos ajustes desde la consola de weblogic únicamente.

Espero que ayude! :)

sábado, 14 de abril de 2012

Jasper multilanguage i18n / Multiidioma en jasper i18n

Muchas veces hemos tenido la necesidad de crear informes y hacerlos multiidioma de una forma sencilla, en este articulo os mostraré una forma fácil y comoda de hacerlo.

Nuestra aplicación debe manejar la internacionalización de la forma que mas nos convenga (este no es el objeto del artículo, ya que cada framework provee diversas formas de hacerlo), incluso nuestra app si no usa ningún framework puede definir la forma de manejar esta internacionalización.

Debemos definir 2 cosas para poder definir nuestros informes multiidioma:

  • Nuestros ficheros multidioma i18n
Debemos definir un fichero por cada idioma que queramos proveer en nuestra aplicación, como por ejemplo:

Fichero 'fichero18n_es_ES.properties'
saludo=Hola

Fichero 'fichero18n_en_EN.properties'
saludo=Hello

Como vemos, se definen dos ficheros i18n, uno para español (es_ES) y otro para inglés (en_EN) con una clave 'saludo'.

Debemos asegurarnos de poner estos ficheros accesibles a través del classpath, si no nuestra aplicación no podrá localizar dichos ficheros.

  • Definir un parámetro en cada generación de informes y pasarlo al motor Jasper
params = new HashMap<String, Object>();
params.put("REPORT_LOCALE", Locale);

Pasamos un objeto java.util.Locale con la localización que nos interese.

Una vez que hemos definidos estos puntos, en nuestro jasper (jrxml) podemos indicar líneas como esta:

<textFieldExpression class="java.lang.String"><![CDATA[ResourceBundle.getBundle("fichero18n", $P{REPORT_LOCALE}).getString("saludo")]]></textFieldExpression>

Esto es gracias a que lo que añadamos dentro de un textFieldExpression será evaluado por Jasper como una expresion Java, de este modo, una vez que cambiemos el Locale en nuestra aplicación jasper automáticamente cargará los mensajes en el Locale apropiado (o el por defecto si no existe un fichero i18n para el Locale especificado).

Facil, ¿No?. Espero que a alguien le ayude :)

domingo, 8 de abril de 2012

transform .pfx into jks keystore and XML sign / transformar un certificado pfx en un almacen jks y firmar xml

Hola a todos,

En esta ocasión quiero compartir con vosotros algo en lo que todos nos vemos envueltos algunas vez, firmas digitales... toda una aventura :).

En este articulo os mostraré como, a partir de un archivo .pfx, poder realizar peticiones XML firmadas desde java, para ello dividiremos el articulo en dos partes:

Transformar archivo pfx en jks

Existen formas de hacer esta transformacion en varios pasos, con la ayuda de openssl, en este articulo usaremos una característica del comando del jdk keytool que se introdujo en la version 6 de java (no estoy muy seguro de si en la versión 5 de la jdk ya venía).

Usaremos el siguiente comando:

"C:\Archivos de programa\Java\jdk1.6.0_17\bin\keytool" -importkeystore -srckeystore tu_archivo_pfx
-destkeystore FirmaKeystore -srcstoretype pkcs12 -deststoretype jks -srcstorepass clave_de_tu_pfx
-deststorepass clave_para_tu_nuevo_almacen -srcalias c4a3f5f494165a13ccbb1473e3b69c6f_1f97c812-2351-470b-aae7-10698e811751 -destalias nuevo_alias -v

Aunque el ejemplo está indicado para windows, en unix funciona igual. Como se ve, hay algunos valores en azul, los comentamos:
  • srckeystore: Es la ruta a tu archivo pfx de origen.
  • srcstorepass: Clave del archivo pfx, que nos debe dar el proveedor del certificado.
  • deststorepass: Clave que asignaremos a nuestro nuevo almacen (en ejemplo llamado FirmaKeystore).
  • destalias: Alias que asignaremos en nuestro almacen al certificado que se genera.

Aún queda un punto pendiente, en 'srcalias' tenemoms un 'chorizo' que no es aleatorio, es el alias dentro del pfx del certificado, para obtener este dato ejecutamos el siguiente comando:

keytool -list -storetype pkcs12 -keystore your_pfx_file -v | grep Alias

Se observa el comando '| grep Alias' al final, esto solo es valido para unix, en windows lo
copiaremos de la salida de comando en pantalla. Usamos ese valor en la opcion 'srcalias'.

Con esto ya tenemos nuestro almacen generado.

Firmar XMLs

Una vez que tenemos nuestro almacen generado, con el siguiente código firmamos nuestro XML. Se usa el API de Apache (abajo teneis los imports)

   /**
     * Punto de entrada al ejemplo.
     */
    public Document firmarXml(Document doc) throws Exception {

        System.out.println("/ INICIO.");

        // Obtenemos las propiedades para firmar el documento.
        String sTipoAlmacen = "jks";
        String sAlmacen = "ruta_a_nuestro_almacen_generado_en_el_punto1";
        String sClaveAlmacen = "clave_que_dimos_en_deststorepass";
        String sClavePrivada = "valor_de_srcstorepass";
        String sAlias = "valor_de_destalias";

        org.apache.xml.security.Init.init();

        Constants.setSignatureSpecNSprefix("ds"); // Sino, pone por defecto como prefijo: "ns"

        // Cargamos el almacen de claves
        KeyStore ks  = KeyStore.getInstance(sTipoAlmacen);
        ks.load(new FileInputStream(sAlmacen), sClaveAlmacen.toCharArray());

        // Obtenemos la clave privada, pues la necesitaremos para encriptar.
        PrivateKey privateKey = (PrivateKey) ks.getKey(sAlias, sClavePrivada.toCharArray());

        File    signatureFile = new File("signature.xml");
        String  baseURI = signatureFile.toURI().toString();   // BaseURI para las URL Relativas.

        // Instanciamos un objeto XMLSignature desde el Document. El algoritmo de firma será RSA
        XMLSignature xmlSignature = new XMLSignature(doc, baseURI, XMLSignature.ALGO_ID_SIGNATURE_RSA);

        // Añadimos el nodo de la firma a la raiz antes de firmar.
        // Observe que ambos elementos pueden ser mezclados en una forma con referencias separadas
        doc.getDocumentElement().appendChild(xmlSignature.getElement());

        // Creamos el objeto que mapea: Document/Reference
        Transforms transforms = new Transforms(doc);
        transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
        transforms.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS);

        // Añadimos lo anterior Documento / Referencia
        // ALGO_ID_DIGEST_SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1";
        xmlSignature.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);

        // Añadimos el KeyInfo del certificado cuya clave privada usamos
        X509Certificate cert = (X509Certificate) ks.getCertificate(sAlias);

        xmlSignature.addKeyInfo(cert.getPublicKey());
        xmlSignature.addKeyInfo(cert);

        // Realizamos la firma
        xmlSignature.sign(privateKey);

        System.out.println("\\ FIN.");
        return doc;
    }

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;

import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

La entrada del método es el XML a firmar en formato org.w3c.dom.Document, el cual es devuelto al final del proceso.

Recordar que debemos importar a nuestro almacen cualquier certificado intermedio o de root que
complete la ruta de certificación del certificado que hemos transformado, ya que si no
no se podrá completar la ruta de certificación.

Espero que os sirva, y cualquier cosa me comentais

Hasta pronto!!

UPDATE: El comando correcto es -importkeystore, no -import keystore como estaba incialmente indicado. Ya está corregido!! :)

Debugging applets with eclipse / Depurar applets con eclipse

Hola,

Esta es mi primera entrada en el blog, mi estreno en esto .

Quiero compartir con vosotros algo que es sencillo, a la vez que útil de cara a poder hacer debug en un applet en el lado cliente.

Los applets se ejecutan a través del java plug-in del lado cliente, en windows podemos acceder al panel de control y en programas acceder a la configuracion del plugin java

Debemos añadir este trozo de código bajo la pestaña 'Java' (en configuración del java runtime environment), pulsamos 'Ver' y añadimos lo siguiente en el apartado 'parámetros del entorno':

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

Para que estos cambios surtan efecto, debemos reiniciar el plug-in de java, normalmente reiniciando el navegador y/o eliminando el proceso java.





Una vez que tengamos el plug-in levantado de nuevo, desde eclipse pulsamos 'Debug As...' y elegimos 'Java remote application' (el puerto elegido ha de corresponderse con el parametro address indicado arriba).

Una vez conectado podemos insertar un punto de ruptura y depurar nuestro código java normalmente.

Espero que a alguien le sirva!!