viernes, 19 de diciembre de 2014

Dividir un fichero en dos partes según un criterio que lo clasifique

Descripción del problema

En esta oportunidad explicaré cómo leer un fichero de texto de 'n' cantidad de registros y dividirlo en varios ficheros, según un criterio en particular, usando la herramienta Spring-Batch. No voy a entrar en mucho detalles, simplemente a modo general para ver la solución.

Spring Batch es  un modulo perteneciente al Framework de "Spring" diseñado para dar soporte a las operaciones por lotes (batch)  de nuestras aplicaciones.

Solución del problema

Trabajaremos básicamente con los siguientes elementos:

  • ItemReader: Elemento responsable de leer datos de una fuente de datos, en nuestro caso un fichero
  • ItemWriter: Elemento responsable guardar la información leída por el reader o tratada por el processor y almacenarla en una fuente de datos, en nuestro caso dos ficheros.
  • ClassifierCompositeItemWriter: Elemento responsable de enrutar la salida (el writer) dependiendo de un clasificador.
Necesitamos crear dos clases, adicionales:

  • Item: Sera el elemento que almacene una linea de nuestro fichero.
  • ItemClassifer : Elemento responsable de indicar cual es el elemento que clasificaremos

Clases Adicionales

Se necesita una clase de manera de indicar que campo de una linea del registro queremos que discrimine la salida hacia un fichero o hacia otro.

public class Item implements Serializable {

 /** type to classify **/
 private String type;

 private String inittext;

 private String endtext;

 public String getType() {
  return type;
 }

 public void setType(String type) {
  this.type = type;
 }

 public String getInittext() {
  return inittext;
 }

 public void setInittext(String inittext) {
  this.inittext = inittext;
 }

 public String getEndtext() {
  return endtext;
 }

 public void setEndtext(String endtext) {
  this.endtext = endtext;
 }

 public String getText() {
  return inittext + type + endtext;
 }

}

import org.springframework.classify.annotation.Classifier;

public class ItemClassifer {
 @Classifier
 public String classify(Item item) {
  return item.getType();
 }
}

Ahora configuramos el spring-batch. Debemos tener en cuenta los siguientes elementos:

  • batch:streams: Indicará que necesitamos dos escritores: FileItemWriter1 y FileItemWriter2. Si este elemento no existe, dará un error al intentar escribir el fichero de salida.
  • FormatterLineAggregator: Indicará el formato en que se escribirá la linea en cada fichero
  • ClassifierCompositeItemWriter: Indicará cómo se encargará de clasificar la salida.
  • property name="matcherMap: Será quien discrimine el elemento clasificatorio según el patrón. 

Ejemplo

Para ver con detalles, imaginemos que recibimos un fichero que identifique los coches (file1.dat). Este fichero que recibimos queremos dividirlos en varios ficheros según la marca (FORD y PSA). Queremos generar el fichero file2.dat con los coches FORD y file3.dat con los coches PSA.

Observamos que el fichero file1.dat vienen varios registros (lineas) con varias marcas por ejemplo: FORDSIERRA, PSA208, PSA308, etc. Adicionalmente, para dar un poco de complejidad, queremos que los PSA se almacenen en el mismo fichero. Para ello, usamos * dado que el campo de clasificación devolverá PSA2, PSA3.

Fichero file1.dat
MADRID-ES-FORDSIERRA 8090121231 ASADA
MADRID-ES-PSA208 113141128912 ASADA
MADRID-ES-PSA308 102131341213 ASADA
MADRID-ES-FORDSIERRA 7654121231 ASADA

Fichero file2.dat
MADRID-ES-FORDSIERRA 8090121231 ASADA
MADRID-ES-FORDSIERRA 7654121231 ASADA

Fichero file3.dat
MADRID-ES-PSA208 113141128912 ASADA
MADRID-ES-PSA308 102131341213 ASADA

Configuración del spring-batch



   
   
      
         
            
              
                 
                 
             
             
         
      
   
   
   
      
      
         
            
               
                  
                  
                  
               
            
            
               
                  
               
            
         
      
   
   
   
   
   
   
      
         
            
               
            
            
               
                  
                  
               
            
         
      
   
   
   
        
            
                
            
        
        
   
   
   
   
      
      
   
   
   
   
      
      
   


Unir ficheros mediante batch

Descripción del Problema

Cómo leer registros de múltiples ficheros (en este caso usaremos ficheros CSV), y escribir los diferentes registros en un sólo fichero csv.

Ficheros

Para ilustrar el caso, os proporciono 3 ficheros: domain-1-3-2013.csv, domain-2-3-2013.csv, domain-3-3-2013.csv

csv/inputs/domain-1-3-2013.csv

1,facebook.com

2,yahoo.com

3,google.com


csv/inputs/domain-2-3-2013.csv

200,wherever.com

300,stackoverflow.com

400,oracle.com


csv/inputs/domain-3-3-2013.csv

999,eclipse.org

888,baidu.com


CLASE 

Se requiere una clase que servirá para almacenar temporalmente los dos campos de cada linea del fichero.

package com.ejemplo;

public class Domain {

 int id;
 String domain;

 public int getId() {
  return id;
 }

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

 public String getDomain() {
  return domain;
 }

 public void setDomain(String domain) {
  this.domain = domain;
 }

}

Configuración del Job de Spring

Solo se necesita la siguiente configuración en spring-batch

job-merge-files.xml



  

  

  
    
 
  
 
    
  

  
 
 
  

  
 
   
  
      
    
      
  
  
      
    
      
  
   
 
  

  
 
 
 
   
  
  
    
   
    
  
   
 
  



Configuración del Contexto de Spring-Batch

Este fichero suele cambiar según la configuración que quieras hacer con spring-batch, les facilito la parte más básica.

context.xml



    
    
 
    

    

    
 
    




Clase para ejecutar el job

Se requiere una clase que servirá para ejecutar el job.

package com.ejemplo;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

  public static void main(String[] args) {
 App obj = new App();
 obj.run();
  }

  private void run() {

 String[] springConfig = { "spring/batch/jobs/job-merge-files.xml" };

 ApplicationContext context = new ClassPathXmlApplicationContext(springConfig);

 JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
 Job job = (Job) context.getBean("readMultiFileJob");

 try {

  JobExecution execution = jobLauncher.run(job, new JobParameters());
  System.out.println("Exit Status : " + execution.getStatus());

 } catch (Exception e) {
  e.printStackTrace();
 }

 System.out.println("Done");

  }

}

RESULTADO

Al ejecutar el batch, la salida esperada será un fichero llamado domain.all.csv con la combinación de los registros de los tres ficheros csv de entrada.

csv/outputs/domain.all.csv

1,facebook.com

2,yahoo.com

3,google.coms

200,mkyong.com

300,stackoverflow.com

400,oracle.com

999,eclipse.org

888,baidu.com

Agregar espacios de relleno a un String en Java

Me he encontrado en el código cosas inimaginables... En fin, si deseamos que un String sea de un tamaño determinado y se debe rellenar con caracteres en blanco hasta el final, simplemente se puede usar el siguiente método:

String.format("%1$-20s",cadena);

Nota: 

  • Sustituye el 20 por el tamaño predeterminado que deseas del String.
  • El guión que se indica delante del 20s permite que los espacios de relleno se coloquen detrás de la cadena. Si deseas que el relleno se coloque delante se debe eliminar ese guión. 

miércoles, 17 de diciembre de 2014

Cómo trazar los sql en java e Hibernate

Descripción del problema 

Si usas Hibernate seguramente estarás cansado de ver trazas con los prepared statement y sus símbolos de interrogación "?".  O bien ver las consultas SQL y no poder ver los resultados de la misma sin tener que echar una linea de código. Te parecería interesante que se mostrasen los resultados en el Log:



Pues veras que fácil es la solución.

Solución


log4jdbc-log4j2 es un driver JDBC que actúa como proxy. Este permite trazar todas las llamadas JDBC y las sentencias SQL usando log4J y SLF4J.

Lo primero es agregar las dependencias al proyecto de las librerias de log4jdbc. La versión de la librería dependerá de la versión de JDK que estés utilizando:
  • log4jdbc-log4j2-jdbc3.jar para JDBC 3 que es soportado en  JDK 1.5. 
  • log4jdbc-log4j2-jdbc4.jar for JDBC 4 que es soportado en  JDK 1.6.
  • log4jdbc-log4j2-jdbc4.1.jar for JDBC 4.1 support in JDK 1.7

En Maven habría que agregar las dependencias de la siguiente forma:

  org.bgee.log4jdbc-log4j2
  log4jdbc-log4j2-jdbcXX
  1.16


Hay que agregar el fichero log4jdbc.log4j2.properties a nivel de classpath. Este fichero sirve para indicarle al driver que tipo de logger usar (Log4j2SLF4J)
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

#Optional parameters
#log4jdbc.debug.stack.prefix=^
#log4jdbc.sqltiming.warn.threshold=
#log4jdbc.dump.sql.select=false
#log4jdbc.dump.sql.insert=false
#log4jdbc.dump.sql.update=false
#log4jdbc.dump.sql.delete=false

También se puede agregar como propiedades del sistema en el momento de ejecutar una clase:

-Dlog4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator


El siguiente paso es la configuración de los Loggers. En función de los datos que nos interesa monitorizar, activamos los loggers requeridos. Por ejemplo vamos a configurar log4j.xml, para que imprima un conjunto de resultados en formato de tabla y también el tiempo necesario para ejecutar cada consulta.


 
  
  
   
  
 
 
  
  
 
 
 
  
  
 



Después de configurar los loggers, ejecute el código y puedes observar las trazas.

create table Item (id bigint generated by default as identity, price double not null, product 
varchar(255), quantity integer not null, order_id bigint, primary key (id))  {executed in 10 msec}
create table T_ORDER (id bigint generated by default as identity, customer varchar(255), primary 
key (id))  {executed in 1 msec}
alter table Item add constraint FK22EF339F325255 foreign key (order_id) references T_ORDER 
{executed in 11 msec}
insert into T_ORDER (id, customer) values (null, NULL)  {executed in 1 msec}
insert into Item (id, order_id, price, product, quantity) values (null, NULL, 0.0, NULL, 0) 
{executed in 0 msec}
batching 1 statements: 0: update Item set ORDER_ID=1 where id=1  {executed in 2 msec}
insert into T_ORDER (id, customer) values (null, NULL)  {executed in 0 msec}
insert into Item (id, order_id, price, product, quantity) values (null, NULL, 0.0, NULL, 0) 
{executed in 0 msec}
batching 1 statements: 0: update Item set ORDER_ID=2 where id=2  {executed in 0 msec}
select order0_.id as id1_0_, order0_.customer as customer1_0_ from T_ORDER order0_ where order0_.id=2 
{executed in 0 msec}
|---------|---------|
|ID       |CUSTOMER |
|---------|---------|
|[unread] |null     |
|---------|---------|
select items0_.ORDER_ID as ORDER5_1_2_, items0_.id as id2_, items0_.id as id0_1_, items0_.order_id 
as order5_0_1_, items0_.price as price0_1_, items0_.product as product0_1_, items0_.quantity 
as quantity0_1_, order1_.id as id1_0_, order1_.customer as customer1_0_ from Item items0_ left 
outer join T_ORDER order1_ on items0_.order_id=order1_.id where items0_.ORDER_ID=2  {executed in 0 msec}
|---------|---|---|---------|------|--------|---------|---|---------|
|ORDER_ID |ID |ID |ORDER_ID |PRICE |PRODUCT |QUANTITY |ID |CUSTOMER |
|---------|---|---|---------|------|--------|---------|---|---------|
|2        |2  |2  |2        |0.0   |null    |0        |2  |[unread] |
|---------|---|---|---------|------|--------|---------|---|---------|
insert into T_ORDER (id, customer) values (null, NULL)  {executed in 0 msec}
insert into Item (id, order_id, price, product, quantity) values (null, NULL, 0.0, 'foo', 0) 
{executed in 0 msec}
batching 1 statements: 0: update Item set ORDER_ID=3 where id=3  {executed in 0 msec}
select order0_.id as id1_, order0_.customer as customer1_ from T_ORDER order0_ inner join Item 
items1_ on order0_.id=items1_.ORDER_ID where items1_.product='foo' limit 2  {executed in 6 msec}
|---|---------|
|ID |CUSTOMER |
|---|---------|
|3  |null     |
|---|---------|
select items0_.ORDER_ID as ORDER5_1_2_, items0_.id as id2_, items0_.id as id0_1_, items0_.order_id 
as order5_0_1_, items0_.price as price0_1_, items0_.product as product0_1_, items0_.quantity 
as quantity0_1_, order1_.id as id1_0_, order1_.customer as customer1_0_ from Item items0_ left 
outer join T_ORDER order1_ on items0_.order_id=order1_.id where items0_.ORDER_ID=3  {executed in 0 msec}
|---------|---|---|---------|------|--------|---------|---|---------|
|ORDER_ID |ID |ID |ORDER_ID |PRICE |PRODUCT |QUANTITY |ID |CUSTOMER |
|---------|---|---|---------|------|--------|---------|---|---------|
|3        |3  |3  |3        |0.0   |foo     |0        |3  |[unread] |
|---------|---|---|---------|------|--------|---------|---|---------|


Si utilizas logback.xml, puedes agregar los loggers de esta forma
  

    
        
        logs/access-%d{yyyy-MM-dd}.log
        
        10
    
    
        %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n
    
    
    
        INFO
        ACCEPT
        DENY 
    
    
    
        10MB
    









    






martes, 2 de diciembre de 2014

Pasos para solventar un OutOfMemoryError: Permgen space

Las aplicaciones Java se encuentran autorizadas a utilizar sólo una cantidad limitada de memoria. La cantidad exacta de memoria que usará tu aplicación en particular debe ser especificada durante el inicio de la aplicación. Haciendo las cosas más complejas, la memoria Java es separada en diferentes regiones tal y como se puede observar en el siguiente diagrama:


El tamaño de todas estas regiones, incluyendo el área PermGen, se establece durante el lanzamiento de la JVM. Si tu en el inicio no estableces el tamaño, se utilizarán los valores predeterminados que usa la plataforma.

Entonces, si te aparece: "java.lang.OutOfMemoryError PermGen space" indicará que el tamaño permanente del área en memoria se ha agotado.

Ejemplo de java.lang.OutOfMemoryError


El espacio PermGen usado está fuertemente correlacionado con el número de clases que se cargan el la JVM. El siguiente código sirve de ejemplo para obtener dicho mensaje:

import javassist.ClassPool;

public class MicroGenerator {

 public static void main(String[] args) throws Exception {
  for (int i = 0; i < 100_000_000; i++) {
   generate("eu.plumbr.demo.Generated" + i);
  }
 }

 public static Class generate(String name) throws Exception {
  ClassPool pool = ClassPool.getDefault();
  return pool.makeClass(name).toClass();
 }
}

En este ejemplo, el código fuente esta iterando sobre un loop y generando clases en tiempo de ejecución. La complejidad de la generación de claes es atendida por la libreria javassit. Al lanzar el código anterior, se mantendrá la creación de nuevas clases y esto hará que se carguen las definiciones en el espacio Permgen hasta que llegue al límite y de esta forma se lanzará el error "java.lang.OutOfMemoryError: Permgen space" 

Solución para java.lang.OutOfMemoryError


La primera solución a la OutOfMemoryError debido a PermGen es obvia. Si hemos agotado el espacio de memoria para PermGen tenemos que aumentar su tamaño. Esta solución es realmente útil si sólo no se ha definido en el inicio de la JVM espacio suficiente. Así que se puede modificar su configuración en el lanzamiento de aplicaciones y agregar (o aumentar en caso de que exista) lo siguiente:

 -XX:MaxPermSize=512m

 Este parámetro de configuración indica a la JVM que debe permitir al espacio PermGen crecer hasta 512 MB antes de lanzar una OutOfMemoryError.


La segunda posibilidad es permitir al GarbageCollector (GC) descargar las clases de PermGen. La JVM estándar tiene un estilo conservador, toda clase que nace debe existir para siempre. Así una vez cargadas, las clases permanecen en la memoria incluso aunque no se vuelva usar jamás.

 Esto puede convertirse en un problema cuando la aplicación está creando un montón de clases de forma dinámica y las clases generadas no son necesarias por largos periodos de tiempo. En tal caso, permitir a la JVM descargar las clases puede ser de gran ayuda. Esto se logra agregando un nuevo parámetro de configuración en el inicio:

 -XX:+CMSClassUnloadingEnabled

Por defecto, se establece a false y para poder habilitarlo es necesario establecerlo de manera explícita siguiendo las opciones de Java Options.

Se habilita CMSClassUnloadingEnabled, la GC limpiará el PermGen y eliminará cualquier clase que no se utiliza. Tenga en cuenta, que esta opción sólo funciona cuando UseConcMarkSweepGC también se habilita.

 -XX:+UseConcMarkSweepGC

Advertencia: Antes de usar estos dos parámetros para proporcionar una solución rápida, ten encuentra que solamente estas enmascarando el error, lo que realmente importa es el detectar si las clases que se instancias deben estar o no y encontrar una solución a ello.

Por ejemplo, si realmente quieres arreglar la fuga en la memoria PermGen, podrías utilizar el siguiente Servlet Context Listener para detectar fallos:

// ServletContextListener
public class JdbcDriverLeakPreventer implements ServletContextListener {
  @Override
  public void contextInitialized(ServletContextEvent sce) {
    //Nothing to do
  }

  @Override
  public void contextDestroyed(ServletContextEvent sce) {
    ClassLoader applicationClassLoader = this.getClass().getClassLoader();
    Enumeration driverEnumeration = DriverManager.getDrivers();
    while (driverEnumeration.hasMoreElements()) {
      Driver driver = driverEnumeration.nextElement();
      ClassLoader driverClassLoader = driver.getClass().getClassLoader();
      if (driverClassLoader != null 
          && driverClassLoader.equals(applicationClassLoader)){
        try {
          DriverManager.deregisterDriver(driver);
        } catch (SQLException e) {
          e.printStackTrace(); //TODO Replace with your exception handling
        }
      }
    }
  }
}

Este Servlet Listener debe ser registrado en el web.xml de tu aplicación.

user.package.JdbcDriverLeakPreventer


Hay diferentes formas de investigar que ha ocupado espacio PermGen en su JVM y si los objetos tengan alguna razón válida para llenarlo.

jueves, 27 de noviembre de 2014

Tuneando la JVM - Argumentos de memoria

Parámetros de la JVM

En la siguiente imágenes podemos ver los parámetros opcionales que se utilizan en la máquina virtual de java.


Argumentos más utilizados


-Xms Este valor indica la cantidad de memoria asignada al iniciar la máquina virtual de java. Mi recomendación para el valor de este argumento es que corresponda a la mitad Xmx o en el caso de servidores al mismo valor que el Xmx.

-Xmx Este valor indica la memoria máxima que podrá alcanzar la maquina virtual de java. Un valor bajo de este argumento puede hacer que la ejecución del código en la JVM lance una excepción de tipo java.lang.OutOfMemoryError, aumentando este argumento podemos evitar este problema.

-Xss Este valor indica la cantidad de memoria de pila por hilo de ejecución. Este parámetro por defecto suele estar en 512k, dependiendo del Sistema operativo y arquitectura del mismo. Cuando se producen excepciones del tipo java.lang.StackOverflowError es recomendable ajustar una mayor memoria de pila por hilo. Un valor muy alto puede ser contraproducente, puesto que el sistema se puede quedar sin memoria para crear nuevos hilos de ejecución.

-Xmn Este valor controla la cantidad de espacio del almacenamiento dinámico que puede consumir la generación joven (llamado Eden), un valor bajo indica que el Garbage Collector se ejecute muchas veces sin tener realmente nada que limpiar y un valor muy alto puede hacer que los objetos basura salgan de la memoria "Eden" de la JVM y se introduzcan en la memoria de generación vieja.

-XX:MaxPermSize Este argumento indica la cantidad de memoria Non-Heap/perm máxima, esta memoria se utiliza para cargar parte de código que utilizara la JVM. Aumentando este parámetro conseguiremos evitar que se produzcan excepciones del tipo java.lang.OutOfMemoryError: PermGen space.

Cómo solventar un java.lang.OutOfMemoryError

Alguna vez te has encontrado la excepción "java.lang.OutOfMemoryError: PermGen space failure". Esta excepción indica que la máquina virtual de Java (JVM) se ha quedado sin memoria PermGen.

¿Cuales son los tipos de memoria que encontramos en la JVM?


En la JVM se pueden diferenciar 3 tipos de memorias:

  1. Memoria de almacenamiento dinámico o memoria Heap: Se almacenan las instancias de los objetos creados por las aplicaciones. 
  2. Memoria de almacenamiento estático o memoria PermGen: Se almacenan las clases que han sido cargadas por la aplicación. También, es utilizada para almacenar la información para la optimización de la aplicación por parte de la JVM. Los elementos que se almacenan en esta zona de la memoria son:
    • Métodos de las clases (incluido bytecode).
    • Nombre de las clases.
    • Pool de Constantes de la JVM.
    • Array de Objetos y array de tipos asociados con una clase.
    • Objetos internos creados por la JVM (java.lang.Object or java.lang.Exception).
    • Información utilizada para la optimización de los compiladores.
    • Cadenas internas. Estas se almacenan para la optimización en el acceso a las mismas.
  3. Memoria dinámica nativa: Se almacena el código de la Interfaz Nativa de Java (JNI) o la bibliotecas nativas de una aplicación y en la puesta en práctica de la JVM asignan memoria del almacenamiento dinámico nativo.



En este artículo, nos referiremos sólo a la Memoria PermGen que es la que genera la excepción anteriormente indicada.

¿Qué causa hace que salte esta excepción?


  • La aplicación carga demasiadas clases: A la hora de cargar las clases, la JVM lo hace bajo demanda. En la práctica esto se transforma en dos posibles casos:
    1. Cuando se crea un objeto relativo a una clase, es decir, llamada mediante el operador new.
    2. Cuando accedemos a algún método o atributo estático de la clase.
Hay que decir que una vez que una clase es cargada en el ClassLoader no será descargada del mismo y a su vez se mantendrá una referencia desde el ClassLoader a la Clase y desde la clase al ClassLoader que la ha cargado. Todo esto será almacenado en la memoria PermGen, de tal forma que conforme vamos accediendo a las diferentes clases que son utilizadas por nuestra aplicación aumentará el tamaño ocupado.
NOTA: Este caso puede ser especialmente dado en los JSP, ya que cada uno de ellos será convertido en un fichero de una clase Servlet. Otros casos se pueden dar en algunos frameworks como Hibernate, EJB … Debido a que estos frameworks generan clases auxiliares para la optimización de los mismos.
  • La aplicación utiliza muchas cadenas ‘internas’: String.intern es una optimización de la JVM para permitir optimizar la comparación entre cadenas. La comparación carácter a carácter entre cadenas es muy lenta, por ello se mantiene un lista de cadenas instanciadas, de tal forma que permite una comparación de identidades que es más rápida. El problema de esto es que la lista utilizada para la comparación de cadenas es establecida en la memoria PermGen y en algunos casos el tamaño de la misma puede ser demasiado elevado. 
Un ejemplo de este caso se da en algunos parses de XML, los cuales genera cadenas ‘internas’ muy grandes, por ello en algunos casos pueden provocar este tipo de excepción.
Document doc = SAXParser.new().parse( cadenaXML );

  • Fugas de Memoria (Memory Leaks): Esto se produce cuando re-desplegamos una aplicación. En los servidores de aplicaciones se suele cargar el ClassLoader de cada una de las aplicaciones que se cargan. Si cuando re-desplegamos la aplicación no se elimina de forma correcta el ClassLoader anterior, este se mantendrá en la memoria PermGen y se sumará al espacio ocupado por la nueva aplicación. Cuando re-desplegamos un par de veces la aplicación, provocará que agotemos la memoria PermGen.


¿Cómo solucionarlo?


Un primera opción sería modificar nuestro código para evitar el llegar a alguno de los tres posibles casos. Esta opción muchas veces no es válida debido a que dependemos de terceros o no podemos modificar el código de nuestra aplicación. La segunda opción sería modificar los parámetros de la JVM.

A continuación detallo un conjunto de parámetros que pueden ser indicados a la JVM para paliar, en la medida de lo posible, la aparición de esta excepción:


  • Aumentar el tamaño de la memoria PermGen. Ésta suele ser la primera medida a tomar, aunque no siempre resuelve los problemas sino que los aplaza y a veces puede derivar en problemas con el colector de basura o en problemas con la escasez de memoria dinámica.
-XX:MaxPermSize=128m
NOTA: Por defecto el tamaño de la memoria PermGen es de 64MB.


  • En el caso de multiprocesadores podemos establecer un colector de basura concurrente, el cual puede ‘eliminar’ memoria PermGen, cosa que el colector por defecto no puede. Para ello primero cambiamos el algoritmo del colector de basura:
-XX:+UseConcMarkSweepGC


  • Habilitamos en la JVM la posibilidad de ‘eliminar’ memoria PermGen:
 -XX:+CMSPermGenSweepingEnabled


  • Las clases son un caso particular a la hora de gestionarlas en la memoria PermGen, por ello debemos indicarle a la JVM la posibilidad de permitir que descargue las clases:
-XX:+CMSClassUnloadingEnabled

martes, 11 de noviembre de 2014

Remplazar por salto de linea en notepad++

Para reemplazar una coma, por un salto de linea en el Notepad++ es muy sencillo, basta con ir al Menú Buscar  --> Reemplazar o presionar CTRL+R.



Para insertar el salto de linea basta con escribir: \r \n


viernes, 7 de noviembre de 2014

Cómo solucionar el Error SQL: ORA-02292: restricción de integridad


Si alguna vez intentando borrar un registro de una base de datos Oracle y recibes el siguiente error:

Informe de error:
Error SQL: ORA-02292: restricción de integridad (USUARIO_DB.FK_MAN) violada - registro secundario encontrado
02292. 00000 - "integrity constraint (%s.%s) violated - child record found"
*Cause:    attempted to delete a parent key value that had a foreign
           dependency.
*Action:   delete dependencies first then parent or disable constraint.


La solución pasa por encontrar el registro hijo. Para ello puedes lanzar la siguiente consulta:

SELECT OWNER,
       CONSTRAINT_NAME,
       constraint_type,
       TABLE_NAME,
       r_owner,
       r_constraint_name
FROM all_constraints
WHERE OWNER='USUARIO_DB'
AND CONSTRAINT_NAME='FK_MAN';

Con esta consulta sabrás que tabla esta involucrada. Pero para poder saber que columnas son las que se encuentran involucradas, usa la siguiente consulta: 


SELECT ac.owner AS left_owner,
       ac.constraint_name AS left_name,
       ac.table_name AS left_table,
       acc.column_name AS left_column,
       acc.position AS left_position,
       acr.owner AS right_owner,
       acr.constraint_name AS right_name,
       acr.table_name AS right_table,
       accr.column_name AS right_column,
       accr.position AS right_position
FROM all_constraints ac
JOIN all_cons_columns acc ON ac.constraint_name=acc.constraint_name
JOIN all_constraints acr ON ac.r_constraint_name=acr.constraint_name
JOIN all_cons_columns accr ON acr.constraint_name=accr.constraint_name
WHERE ac.owner='USUARIO_DB'  AND ac.constraint_name='FK_MAN';

Ahora ya tienes la información para borrar el contenido de la tabla relacionada:
 "LEFT_TABLE" y LEFT_COLUMN

Ejecuta la sentencia Delete de la relación:

DELETE
FROM LEFT_TABLE
WHERE LEFT_COLUMN= 'ID_RELACIONADO';

Ahora ya puedes borrar el registro de la tabla principal



viernes, 31 de octubre de 2014

JAXBException is not a valid property on class

Si estas trabajando con WebServices y recibes este error: JAXBException <prop> is not a valid property on class <class> posiblemente venga dados por los siguientes motivos:


  1. Si el targetNamespace del WSDL usa la notación: camel case, el paquete de tu clases también debes usar camel case. 
  2. Si usas JAX-WS para generar las clases automáticamente usando un WSDL, es importante que definas el paquete. Si tienes dos WSDL y realizas dos WSImport usando el mismo paquete puede que te aparezca el error. Lo mejor es que definas un paquete distinto para las clases generadas y te funcionara. 

miércoles, 29 de octubre de 2014

Solución al Error 206 de Eclipse: El nombre del archivo o extensión es demasiado largo

Seguramente te haya salido este error muchas veces usando Eclipse.


El error 206 puede indicar que la linea de comandos que ejecuta Eclipse bajo Windows es demasiado larga. Este error puede saltar dado que tengas un nombre de paquete de clases excesivamente largo, o bien que el classpath tenga muchas librerías. Es un bug de Eclipse que ya ha sido corregido.

El problema fue resuelto en versiones posteriores a la version de INDIGO 3.8 en adelante. Si estas en versiones anteriores, hay un pequeño parche.

Debes seguir los siguientes pasos:


  1. Dirígete al siguiente enlace: https://bugs.eclipse.org/bugs/show_bug.cgi?id=327193 , y descargate el siguiente fichero: bug327193_3.4.2_hack.zip de la sección de attachment.
  2. Respalda el fichero existente en: eclipse/plugins/org.eclipse.jdt.launching_3.*.*.jar 
  3. Renombra el fichero actual  org.eclipse.jdt.launching_3.*.*.jar a .zip para que puedas abrirlo.
  4. Abre el fichero org.eclipse.jdt.launching_3.*.*.zip y ubica el directorio org/eclipse/jdt/internal/launching
  5. Abre el fichero bug327193_3.4.2_hack.zip. Copia y pega las classes del patch del fichero zip al directorio  org/eclipse/jdt/internal/launching de tu fichero org.eclipse.jdt.launching JAR (reemplazando los ficheros existentes)
  6. Renombra  org.eclipse.jdt.launching_3.*.*.zip a .jar
  7. Este paso es opcional. Edita el fichero META-INF /MANIFEST.MF de tu fichero org.eclipse.jdt.launching JAR  y remueve todo lo que empieza por la entrada "NAME:". Asegúrate que dejas dos  (2)  saltos de lineas al final del fichero.
  8. Reinicia el eclipse. [Opcional] Usa el comando eclipse -clean para que limpie el workspace.



viernes, 10 de octubre de 2014

Ejemplo: productor y consumidor de mensajes con ActiveMQ


Vamos a ver un ejemplo de un escenario donde podría ser interesante el uso de ActiveMQ.

Supongamos que tenemos una aplicación (o varias) con un elevado número de usuarios. En dicha aplicación queremos poder "trazar" la actividad del usuario, o lo que es lo mismo, queremos saber cómo interactúa el usuario con la aplicación para que, posteriormente, el departamento de negocio pueda explotar esa información.

Como hemos dicho, la aplicación tiene un elevado número de usuarios por lo que se decide que el responsable de procesar y almacenar la información de la actividad de los usuarios sea otra aplicación. De esta forma liberamos a la aplicación principal de carga de trabajo.

Para implementar esta solución haremos uso de ActiveMQ. Cada vez que la aplicación principal detecte una acción de un usuario (ej: cuando el usuario vaya a "Opciones de configuración") enviará un mensaje a nuestro intermediario de mensajes con la información de dicha acción.

Estos mensajes quedarán almacenados en nuestro broker de mensajería a la espera de que la aplicación que procesa los datos los consuma para su posterior tratamiento. Si NO se necesitase un tratamiento instantáneo de la información, podría ser un proceso nocturno quien se encargase de esto (se presupone que la cantidad de mensajes generados es muy elevada).

Dicho esto, vamos a ver cómo creamos un productor y un consumidor de mensajes.

Productor de mensajes


En el código que viene a continuación podemos ver cómo un emisor de mensajes envía 20 mensajes a nuestro intermediario con la información de las acciones que realizan los usuarios.

Para este ejemplo es necesaria la librería activemq-all-X.X.X.jar. Viene incluida en la distribución de Apache ActiveMQ.

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
 
import javax.jms.*;
import java.util.Random;
 
public class MessageSender {
 
    public enum UserAction {
 
        CONFIGURACION("IR A OPCIONES DE CONFIGURACION"),
        PORTADA("VER PORTADA"),
        LOGIN("ACCEDER A LA APLICACION"),
        SUGERENCIA("ENVIAR SUGERENCIA");
 
        private final String userAction;
 
        private UserAction(String userAction) {
            this.userAction = userAction;
        }
 
        public String getActionAsString() {
            return this.userAction;
        }
    }
 
    private static final Random RANDOM = new Random(System.currentTimeMillis());
 
    private static final String URL = "tcp://localhost:61616";
 
    private static final String USER = ActiveMQConnection.DEFAULT_USER;
 
    private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;
 
    private static final String DESTINATION_QUEUE = "APLICATION1.QUEUE";
 
    private static final boolean TRANSACTED_SESSION = true;
     
    private static final int MESSAGES_TO_SEND = 20;
 
    public void sendMessages() throws JMSException {
 
        final ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USER, PASSWORD, URL);
        Connection connection = connectionFactory.createConnection();
        connection.start();
 
        final Session session = connection.createSession(TRANSACTED_SESSION, Session.AUTO_ACKNOWLEDGE);
        final Destination destination = session.createQueue(DESTINATION_QUEUE);
 
        final MessageProducer producer = session.createProducer(destination);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);
 
        sendMessages(session, producer);
        session.commit();
 
        session.close();
        connection.close();
 
        System.out.println("Mensajes enviados correctamente");
    }
 
    private void sendMessages(Session session, MessageProducer producer) throws JMSException {
        final MessageSender messageSender = new MessageSender();
        for (int i = 1; i <= MESSAGES_TO_SEND; i++) {
            final UserAction userActionToSend = getRandomUserAction();
            messageSender.sendMessage(userActionToSend.getActionAsString(), session, producer);
        }
    }
 
    private void sendMessage(String message, Session session, MessageProducer producer) throws JMSException {
        final TextMessage textMessage = session.createTextMessage(message);
        producer.send(textMessage);
    }
 
    private static UserAction getRandomUserAction() {
        final int userActionNumber = (int) (RANDOM.nextFloat() * UserAction.values().length);
        return UserAction.values()[userActionNumber];
    }
 
    public static void main(String[] args) throws JMSException {
        final MessageSender messageSender = new MessageSender();
        messageSender.sendMessages();
    }
 
}


Si nos fijamos en el código observamos que lo que se está haciendo es enviar un número de mensajes con acciones del usuario al azar. No es exactamente lo que hemos descrito en el punto anterior pero creo que así se entiende mejor.

Con ActiveMQ arrancado lanzamos el ejemplo (método main) y vemos en la consola de administración cómo la cola de mensajes destino (APLICATION1.QUEUE) guarda la información de los mensajes que le acabamos de enviar.


Si además tenemos configurado que los mensajes se almacenen en una base de datos relacional (en nuestro caso MySQL) podemos ver cómo en la tabla "activemq_msgs" tenemos guardados nuestros mensajes.



En caso de que tuviésemos varias colas de mensajes (imaginemos varios productores donde cada uno envía mensajes a una cola), podemos ver la cantidad de mensajes de un vistazo con la consola de administración



consumidor de mensajes.


Nuestro consumidor procesará los todos mensajes que haya en la cola "a demanda" (cuando se ejecute). Si quisiésemos que procesase cada mensaje cuando llega a la cola (no es nuestro caso) deberíamos implementar la interface javax.jms.MessageListener y lanzar nuestro proceso como un hilo que se queda en espera (Thread).

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
 
import javax.jms.*;
import java.util.HashMap;
import java.util.Map;
 
public class UserActionConsumer {
 
    private static final String URL = "tcp://localhost:61616";
 
    private static final String USER = ActiveMQConnection.DEFAULT_USER;
 
    private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;
 
    private static final String DESTINATION_QUEUE = "APLICATION1.QUEUE";
 
    private static final boolean TRANSACTED_SESSION = false;
 
    private static final int TIMEOUT = 1000;
 
    private final Map consumedMessageTypes;
 
    private int totalConsumedMessages = 0;
 
    public UserActionConsumer() {
        this.consumedMessageTypes = new HashMap();
    }
 
    public void processMessages() throws JMSException {
 
        final ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(USER, PASSWORD, URL);
        final Connection connection = connectionFactory.createConnection();
 
        connection.start();
 
        final Session session = connection.createSession(TRANSACTED_SESSION, Session.AUTO_ACKNOWLEDGE);
        final Destination destination = session.createQueue(DESTINATION_QUEUE);
        final MessageConsumer consumer = session.createConsumer(destination);
 
        processAllMessagesInQueue(consumer);
 
        consumer.close();
        session.close();
        connection.close();
 
        showProcessedResults();
    }
 
    private void processAllMessagesInQueue(MessageConsumer consumer) throws JMSException {
        Message message;
        while ((message = consumer.receive(TIMEOUT)) != null) {
            proccessMessage(message);
        }
    }
 
    private void proccessMessage(Message message) throws JMSException {
        if (message instanceof TextMessage) {
            final TextMessage textMessage = (TextMessage) message;
            final String text = textMessage.getText();
            incrementMessageType(text);
            totalConsumedMessages++;
        }
    }
 
    private void incrementMessageType(String message) {
        if (consumedMessageTypes.get(message) == null) {
            consumedMessageTypes.put(message, 1);
        } else {
            final int numberOfTypeMessages = consumedMessageTypes.get(message);
            consumedMessageTypes.put(message, numberOfTypeMessages + 1);
        }
    }
 
    private void showProcessedResults() {
        System.out.println("Procesados un total de " + totalConsumedMessages + " mensajes");
        for (String messageType : consumedMessageTypes.keySet()) {
            final int numberOfTypeMessages = consumedMessageTypes.get(messageType);
            System.out.println("Tipo " + messageType + " Procesados " + numberOfTypeMessages + " (" +
                    (numberOfTypeMessages * 100 / totalConsumedMessages) + "%)");
        }
    }
 
    public static void main(String[] args) throws JMSException {
        final UserActionConsumer userActionConsumer = new UserActionConsumer();
        userActionConsumer.processMessages();
    }
}


Como vemos, lo que hace nuestro consumidor es procesar los mensajes de la cola y mostrar el número de acciones de usuario de cada tipo. Con ActiveMQ arrancado lanzamos el ejemplo y el resultado es el siguiente.



Si consultásemos la consola de administración o la base de datos, veríamos que todos los mensajes de la cola han desaparecido puesto que ya han sido consumidos.

jueves, 9 de octubre de 2014

ActiveMQ para intercambio de mensajes

Apache ActiveMQ


Apache ActiveMQ es un componente MOM (Message Oriented Middleware), es decir, un intermediario (broker) de mensajes que usan dos o más sistemas o aplicaciones para intercambiar mensajes. Ofrece "Características empresariales" tales como clustering, múltiples almacenes para mensajes, así como la capacidad de emplear cualquier administrador de base de datos como proveedor de persistencia JMS, aparte de VM, caché y persistencia de jornales.


En este artículo veremos las características de ActiveMQ, un intermediario de mensajes entre diferentes aplicaciones o sistemas, se describen algunos de los escenarios donde puede ser interesante su uso. Claro está, veremos cómo se instala, cómo se configura y un ejemplo de funcionamiento.

Características principales


  • Es Código Open Source. 
  • Actúa como mediador de mensajes entre aplicaciones emisoras y receptoras.
  • Proporciona comunicación asíncrona entre aplicaciones.
  • Pese a ser una implementación de la especificación JMS (Java Message Service), proporciona una gran cantidad de APIs para diferentes lenguajes como PHP, C/C++, .Net, Ruby, etc.
  • Soporta diferentes protocolos de conexión como HTTP, TCP, SSL, etc.
  • Tiene una GUI de administración.

Instalación


La instalación de Apache ActiveMQ es sencilla.Primero debemos descargar el software de: http://activemq.apache.org/download.html.  Una vez descargado el fichero, debemos seguir los siguientes pasos:
  1. Descomprimir la distribución descargada en el directorio deseado. 
    • Para entornos Unix: tar zxvf apache-activemq-X.X.X-bin.tar.gz
    • Comprobar que el script de arranque de la aplicación tiene permisos de ejecución. Entramos en el directorio bin  que se encuentra dentro de la carpeta de instalación, y asignamos al script "activemq" permisos de ejecución. chmod 755 activemq
  2. Arrancamos Apache ActiveMQ de la siguiente forma: directorio_instalacion_activemq]/bin/activemq start 
    • También podemos "parar" la aplicación, "reiniciarla" o comprobar su "estado" con: stop, restart o status.
  3. Para comprobar que nuestro intermediario de mensajes ha arrancado correctamente: netstat -an|grep 61616, donde 61616 es el puerto por defecto.
También podemos acceder a la consola de administración en http://localhost:8161/admin/

Para que puedas acceder:

  • Usuario: admin
  • Password: admin

Configuración


La configuración del ActiveMQ se encuentra en un fichero activemq.xml localizado en el directorio [directorio_instalacion_activemq]/conf/. No obstante, se puede cambiar el fichero de configuración con el que arrancar ActiveMQ de la siguiente forma:

[directorio_instalacion_activemq]/bin/activemq start xbean:[nombre_del_fichero_configuracion].xml

A continuación destacamos cada una de las propiedades principales:

Transport Connector

Define la conexión con el "broker" de mensajería. Se configura en el tag transportConnectors (dentro
del tag broker) de activemq.xml de la siguiente forma:

    ...
    
        
    
    ...



Se pueden definir varios tipos de conexiones dependiendo del protocolo de transporte (ver lista completa en http://activemq.apache.org/configuring-transports.html).

A continuación se muestran dos ejemplos por VM y TCP:


  • VM Transport permite la conexión con un cliente Java que corra bajo la misma JVM. Se define de la siguiente forma: vm://nombre_del_broker?transportOptions, donde:
    • nombre_del_broker: es el atributo brokerName del tag broker del activemq.xml
    • transportOptions: distintas opciones de la comunicación. Se puede ver la lista de opciones en http://activemq.apache.org/vm-transport-reference.html


    ...
    
        
    
    ...



  • TCP Transport permite la conexión entre el cliente y el gestor de mensajería mediante protocolo TCP. Se define de la siguiente forma: tcp://nombre_o_ip_de_la_maquina:puerto?options, donde:
    • nombre_o_ip_de_la_maquina: máquina o IP donde reside ActiveMQ
    • puerto: puerto de la máquina donde escucha ActiveMQ (por defecto suele ser el 61616)
    • options: opciones de la conexión. Se puede ver la lista entera de opciones en http://activemq.apache.org/tcp-transport-reference.html

    ...
    
        
    
    ...



http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=ActiveMQ


Persistencia de mensajes

Define la manera de persistir los mensajes en caso de que sea necesario (ver especificación JMS). En este artículo hablaremos de dos tipos: con KahaDB y con JDBC.

Persistencia con KahaDB:
KahaDB es un sistema de almacenamiento propio de ActiveMQ diseñado específicamente para las necesidades de un intermediario de mensajes. Proporciona un rendimiento muy alto. Más que cualquier base de datos relacional. Es un sistema de almacenamiento en ficheros combinado con una caché en memoria.

A continuación un ejemplo de configuración de broker con persistencia en KahaDB, donde:

  • directory: directorio donde se almacenará la información.
  • maxDataFileLength: máximo número de bytes que tendrán los ficheros que compondrán KahaDB.



    ...
    
        
    
    ...




Persistencia con JDBC:

ActiveMQ tiene soporte para poder almacenar mensajes en la mayoría de bases de datos.



    ...
    
        
    
    ...

 


    
    
    
    
    



Importante: es necesario añadir el driver JDBC de conexión de cualquier base de datos al classpath de ActiveMQ, para ello basta con copiarlo en el directorio [directorio_instalacion_activemq]/lib/

Más información sobre persistencia de mensajes en http://activemq.apache.org/persistence.html.

Control de flujo


Existe un problema muy típico en ActiveMQ cuando se usan mensajes no persistentes (almacenados en memoria) y es que el buffer de almacenamiento termine con toda la RAM. Para solucionar esto existe un sistema de control de flujo, mediante el cual, es posible limitar el número de mensajes en memoria de uno o varios productores de mensajes no persistentes.

A continuación un ejemplo de configuración:


    ...
    
        
            
        
    
    ...



El valor del atributo queue igual a >, significa que este control de flujo se aplicará a cualquier cola de mensajes. Podríamos aplicar distintas políticas de control de flujo según el nombre de la cola. Ejemplo:

  • queue="TEST.FOO" : para todos los mensajes de la cola TEST.FOO
  • queue="TEST.>" : para todos los mensajes de las colas que comiencen por TEST.

Más info sobre control de flujo en http://activemq.apache.org/producer-flow-control.html

Uso del sistema



Es posible realizar una configuración más genérica de determinadas partes del sistema como puede ser el uso de memoria o el límite de almacenamiento físico o temporal.

Aquí vemos ejemplo:

....

    ...
    
        
            
                
            
            
                
            
            
                
            
        
    
    ...



Nótese que el límite de almacenamiento no influye en bases de datos externas aunque sí en KahaDB.


Ejemplo: productor y consumidor de mensajes.

En el siguiente artículo encontrarán un ejemplo para ActiveMQ

miércoles, 8 de octubre de 2014

Uso de Jax-WS Handlers para leer encabezados (headers) en los Servicios Web

La semana pasada un compañero de trabajo me comento que no encontraba como leer un encabezado de un mensaje soap mediante el uso de web-services. He decidido compartir esta información para que sea más accesible de encontrar en la web. La respuesta es fácil... usa handlers.

Jax-WS Handlers

El framework JAX-WS (el último API de Java usado para la creación de servicios web basados en SOAP: para publicar y consumir servicios Web) también proporciona un framework Handlers.

Los Handlers (controladores) proporcionan un medio para inspeccionar y manipular los mensajes SOAP entrantes o salientes (tanto en el cliente como en el servidor). Ellos actúan como poderosos interceptores de mensajes que pueden realizar una variedad de funciones tales como la transformación de mensajes, filtrado de contenidos, seguimiento (tracking), etc. De hecho, los handlers se utilizan a menudo en entornos de ejecución para implementar las especificaciones de SOAP y servicios web tales como WS-Security, WS-ReliableMessaging, etc.

Los JAX-WS Handlers son similares a los interceptores EJB o filtros de servlets. Tanto los Handlers, como los interceptores y los filtros, recomiendan a los desarrolladores a seguir la cadena del patrón de responsabilidad.

Tipos de JAX-WS Handlers

JAX-WS ofrece dos tipos de handlers (con dos interfaces): LogicalHandler y SOAPHandler.

  • LogicalHandlers son el protocolo de mensajes neutral. 
  • LogicalHandlers sólo tienen acceso a la carga útil del mensaje de un mensaje. 
  • SOAPHandlers también se conocen como controladores de mensajes o de protocolo. 
  • SOAPHandlers tienen acceso a todo el mensaje SOAP. 
nos enfocaremos en los SoapHandlers.

SOAPHandlers 

La interfaz SOAPHandler requiere que la clase del Handler implemente cuatro métodos:
handleMessage, handleFault, close y getHeaders. 

  • El handleMessage suele ser el método de mayor interés en un handler. El método handleMessage es invocado para el procesamiento normal por el framework como lo es el envío o recepción del mensaje (entrante y saliente). 
  • El método handleFault es invocado por el framework cuando se produce un error de SOAP durante el procesamiento de mensajes. 
  • El método close es invocado por el framework justo antes del envío del mensaje. El método de close permite cualquier limpieza o liberación de recursos al final del procesamiento del handler.

A continuación se muestra un ejemplo de un SOAPHandler (menos el método getHeaders que se explicará a continuación) que se encarga de contar el número de elementos en el encabezado (header) y el cuerpo (body) de un mensaje SOAP. 

//required imports here

public class HeaderHandler implements SOAPHandler {

    @Override 
    public void close(MessageContext context) { 
    }

    @Override 
    public boolean handleFault(SOAPMessageContext context) { 
        return false; 
    }

    @Override 
    public boolean handleMessage(SOAPMessageContext messagecontext) { 
        Boolean outbound = (Boolean) messagecontext 
                .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); 
        if (outbound) { 
            System.out.println("SOAP message departing…"); 
        } else { 
            System.out.println("SOAP message incoming…"); 
        } 
        try { 
            SOAPMessage message = messagecontext.getMessage(); 
            SOAPHeader header = message.getSOAPHeader(); 
            if (header != null) { 
                Iterator i = header.getChildElements(); 
                System.out.println("Number of header elements:  " 
                        + countElements(i)); 
            } 
            SOAPBody body = message.getSOAPBody(); 
            if (body != null) { 
                Iterator i = body.getChildElements(); 
                System.out.println("Number of body elements:  " 
                        + countElements(i)); 
            } 
        } catch (Exception e) { 
            System.out.println(e); 
            e.printStackTrace(); 
        } 
        return true; 
    }

    private int countElements(Iterator i) { 
        int count = 0; 
        while (i.hasNext()) { 
            count++; 
            i.next(); 
        } 
        return count; 
    }
}

El último método, getHeaders, es justo el punto de interés de esta entrada del blog. El método getHeaders recupera los bloques de la cabecera que pueden ser procesados por el handler. No toma ningún parámetro y devuelve un conjunto de nombres completos de encabezado.

El método GetHeaders()

El método getHeaders() obtiene el encabezado soap  (header:soap) así como el tiempo de ejecución del servicio de hosting (alojamiento), de los cuales el handler es el encargado de procesar. Devuelve los QNames del elemento exterior de cada encabezado SOAP que el handler entiende. 

MustUnderstand Not Understood! 


Si has trabajado con los mensajes SOAP, seguramente te habrás dado cuenta que los elementos de header SOAP pueden venir con un atributo global SOAP llamado: mustUnderstand. Los valores válidos para mustUnderstand son true | false (SOAP 1.2) ó 1 | 0 (SOAP 1.1). Este atributo es utilizado para indicar si se requiere o no el receptor o intermediario de servicios web  para entender el elemento de la cabecera antes de procesar el mensaje. 

El SOAPHandler puede servir como un "intermediario" que procesa el encabezado SOAP antes de que el servicio real se encargue de procesar el resto (Body) del mensaje. 

Por ejemplo, a continuación un ejemplo de mensaje SOAP que contiene falsas credenciales de seguridad para ser usado por el lado del servidor para autenticar/autorizar alguna invocación del servicio. Observa donde esta el atributo mustUnderstand entre nombre de usuario (usernameHeader) y contraseña (passwordHeader) de los elementos que tiene la cabecera? 

 
    
      Jim 
      supersecret 
    
    
       
         4 
       
    


si algo no procesa los elementos de la cabecera: nombre de usuario y contraseña, este mensaje podría retornar una falla al cliente. Por ejemplo, supongamos que el servicio no maneja los elementos de la cabecera y el metodo getHeaders del SOAPHandler se codifica como se muestra a continuación.


@Override 
    public Set getHeaders() { 
        return null; 
    }


Dado que nada maneja el usuario y contraseña de la cabecera, el mensaje SOAP resultante dirigido al cliente incluiría un código de fallo MustUnderstand como se muestra aquí

 
    
       
         soap:MustUnderstand 
         MustUnderstand headers: [{urn:com.intertech.secty}password, {urn:com.intertech.secty}username] are not understood. 
       
    


Usando getHeaders() para entender el MustUnderstand


Sin embargo, el método getHeaders () puede ser codificado para indicar al entorno de ejecución que el handler de SOAP se va a encargar de los elementos de la cabecera mustUnderstand devolviendo un conjunto de objetos QName (nombre completo) que coinciden con los elementos de la cabecera mustUnderstand. A continuación, un ejemplo con el nuevo método getHeaders () que maneja el usuario y contraseña de las cabeceras.

@Override 
public Set getHeaders() { 
    System.out.println("Inside SOAP handler of get Headers"); 
    QName securityUsernameHeader = new QName("urn:com.intertech.secty", 
            "username"); 
    QName securityPasswordHeader = new QName("urn:com.intertech.secty", 
            "password"); 
    HashSet headers = new HashSet(); 
    headers.add(securityUsernameHeader); 
    headers.add(securityPasswordHeader); 
    System.out.println("got Headers:  " + headers); 
    return headers; 
}

Ahora, si el mismo elemento SOAP con los elementos de la cabecera: nombre de usuario y contraseña se envía a un servicio, mediante el HeaderHandler del ejemplo, no se genera un fallo. Se supone por el tiempo de ejecución que los elementos de la cabecera fueron consumidos por el handler. Por supuesto, en un mundo real para el SOAP handler, en el método handleMessage () deberías agregar algún código para manejar efectivamente estos encabezados.

Espero que esta información pueda serles de gran utilidad.


jueves, 2 de octubre de 2014

Rest Api - Conceptos

¿Qué es REST?


REST (REpresentational State Transfer) es un tipo de arquitectura de desarrollo web que se apoya totalmente en el estándar HTTP.

REST nos permite crear servicios y aplicaciones que pueden ser usadas por cualquier dispositivo o cliente que entienda HTTP, siendo más simple y convencional que otras alternativas que se han usado en los últimos años (SOAP, XML-RPC, etc)

Existen tres niveles de calidad a la hora de aplicar REST en el desarrollo de una aplicación web y, concretamente en una API. Estos niveles son:

  • Uso correcto de URIs
  • Uso correcto de HTTP.
  • Implementar Hypermedia.

Además de estas tres reglas, nunca se debe guardar el estado en el servidor, toda la información que se requiere para mostrar la información que se solicita debe estar en la consulta por parte del cliente. Al no guardar el estado, REST puede escalar mejor sin tener que preocuparnos por temas como el almacenamiento de variables de sesión e incluso, podemos jugar con distintas tecnologías para servir determinadas partes o recursos de una misma API.

Nivel 1: Uso correcto de URIs


Cuando desarrollamos una aplicación web, las URLs nos permiten acceder a cada uno de los recursos: páginas, información de una sección, a las imagenes, ficheros o documentos del sitio web. El recurso por lo tanto es la información a la que queremos acceder (consultar, modificar, borrar) independientemente de su formato.

Las URL (Uniform Resource Locator) son un tipo de URI (Uniform Resource Identifier) que además de permitir identificar de forma única el recurso, nos permite localizarlo para poder acceder a él o compartir su ubicación.

Una URL se estructura de la siguiente forma:

{protocolo}://{dominio o hostname}[:puerto (opcional)]/{ruta del recurso}?{consulta de filtrado}

Por ejemplo, http://innovaskynet.blogspot.com/2013/conceptos-sobre-apis-rest/, sería la URL para visualizar este artículo.

Existen varias reglas básicas para ponerle nombre a la URI de un recurso:
  • Los nombres de URI no deben implicar una acción, por lo tanto debe evitarse usar verbos en ellos.
  • Deben ser únicas, no debemos tener más de una URI para identificar un mismo recurso.
  • Deben ser independiente de formato.
  • Deben mantener una jerarquía lógica.
  • Los filtrados de información de un recurso no se hacen en la URI.
  • Las URIs no deben implicar acciones y deben ser únicas

Por ejemplo, la URI /facturas/234/editar sería incorrecta ya que tenemos el verbo editar en la misma.

Para el recurso factura con el identificador 234, la siguiente URI sería la correcta, independientemente de que vayamos a editarla, borrarla, consultarla o leer sólo uno de de sus conceptos: /facturas/234

Las URIs deben ser independientes de formato


Por ejemplo, la URI /facturas/234.pdf no sería una URI correcta, ya que estamos indicando la extensión pdf en la misma.

Para el recurso factura con el identificador 234, la siguiente URI sería la correcta, independientemente de que vayamos a consultarla en formato pdf, epub, txt, xml o json: /facturas/234

Las URIs deben mantener una jerarquía lógica


Por ejemplo, la URI /facturas/234/cliente/007 no sería una URI correcta, ya que no sigue una jerarquía lógica.

Para el recurso factura con el identificador 234 del cliente 007, la siguiente URI sería la correcta: /clientes/007/facturas/234

Filtrados y otras operaciones.


Para filtrar, ordenar, paginar o buscar información en un recurso, debemos hacer una consulta sobre la URI, utilizando parámetros HTTP en lugar de incluirlos en la misma.

Por ejemplo, la URI /facturas/orden/desc/fecha-desde/2007/pagina/2 sería incorrecta ya que el recurso de listado de facturas sería el mismo pero utilizaríamos una URI distinta para filtrarlo, ordenarlo o paginarlo.

La URI correcta en este caso sería: /facturas?fecha-desde=2007&orden=DESC&pagina=2


Nivel 2: HTTP


Conocer bien HTTP no es opcional para un desarrollador web al que le importe su trabajo. Aunque el RFC es sencillo de leer, si estás interesado en aprender bien las bases de este protocolo es muy recomendable la guía de O’Reilly sobre el mismo.

Para desarrollar APIs REST los aspectos claves que hay que dominar y tener claros son:
  • Métodos HTTP
  • Códigos de estado
  • Aceptación de tipos de contenido

Métodos

Como hemos visto en el anterior nivel, a la hora de crear URIs no debemos poner verbos que impliquen acción, aunque queramos manipular el recurso.

Para manipular los recursos, HTTP nos dota de los siguientes métodos con los cuales debemos operar:

  • GET: Para consultar y leer recursos
  • POST: Para crear recursos
  • PUT: Para editar recursos
  • DELETE: Para eliminar recursos.
  • PATCH: Para editar partes concretas de un recurso.

Por ejemplo para un recurso de facturas:

GET /facturas Nos permite acceder al listado de facturas

POST /facturas Nos permite crear una factura nueva

GET /facturas/123 Nos permite acceder al detalle de una factura

PUT /facturas/123 Nos permite editar la factura, sustituyendo la totalidad de la información anterior por la nueva.

DELETE /facturas/123 Nos permite eliminar la factura

PATCH /facturas/123 Nos permite modificar cierta información de la factura, como el número o la fecha de la misma.


Quizá debido al desconocimiento o el soporte de ciertos navegadores, los desarrolladores web han usado, durante los últimos años, únicamente los métodos GET Y POST para realizar todas estas acciones. Si trabajamos con REST, esto sería un error de base y puede darnos problemas incluso a la hora de nombrar nuestros recursos, obligándonos a poner verbos en las URLs.

Códigos de estado


Uno de los errores más frecuentes a la hora de construir una API suele ser el reinventar la rueda creando nuestras propias herramientas en lugar de utilizar las que ya han sido creadas, pensadas y testadas. La rueda más reinventada en el desarrollo de APIs son los códigos de error y códigos de estado.

Cuando realizamos una operación, es vitar saber si dicha operación se ha realizado con éxito o en caso contrario, por qué ha fallado.

Un error común sería por ejemplo:

Petición
========
PUT /facturas/123

Respuesta
=========
Status Code 200
Content:
{
  success: false,
  code:    734,
  error:   "datos insuficientes"
}

En este ejemplo se devuelve un código de estado 200, que significa que la petición se ha realizado correctamente, sin embargo, estamos devolviendo en el cuerpo de la respuesta un error y no el recurso solicitado en la URL.


Este es un error común que tiene varios inconvenientes:

  • No es REST ni es estándar.
  • El cliente que acceda a este API debe conocer el funcionamiento especial y cómo tratar los errores de la misma, por lo que requiere un esfuerzo adicional importante para trabajar con nosotros.
  • Tenemos que preocuparnos por mantener nuestros propios códigos o mensajes de error, con todo lo que eso supone.
HTTP tiene un abanico muy amplio que cubre todas las posibles indicaciones que vamos a tener que añadir en nuestras respuestas cuando las operaciones han ido bien o mal.

Es imperativo conocerlos y saber cuándo utilizarlos, independientemente de que desarrolles siguiendo REST.

El siguiente ejemplo sería correcto de la siguiente forma:

Petición
========
PUT /facturas/123

Respuesta
=========
Status Code 400
Content:
{
  message: "se debe especificar un id de cliente para la factura"
}

Tipos y formatos de contenido


Cuando hablamos sobre URLs, vimos también que no era correcto indicar el tipo de formato de un recurso al cual queremos acceder o manipular.

HTTP nos permite especificar en qué formato queremos recibir el recurso, pudiendo indicar varios en orden de preferencia, para ello utilizamos el header Accept.

Nuestra API devolverá el recurso en el primer formato disponible y, de no poder mostrar el recurso en ninguno de los formatos indicados por el cliente mediante el header Accept, devolverá el código de estado HTTP 406.

En la respuesta, se devolverá el header Content-Type, para que el cliente sepa qué formato se devuelve, por ejemplo:

Petición
========
GET /facturas/123
Accept: application/epub+zip , application/pdf, application/json

Respuesta
=========
Status Code 200
Content-Type: application/pdf


En este caso, el cliente solicita la factura en epub comprimido con ZIP y de no tenerlo, en pdf o json por orden de preferencia. El servidor le devuelve finalmente la factura en pdf.

Nivel 3: Hypermedia.


El concepto y la finalidad de Hypermedia es conectar mediante vínculos las aplicaciones clientes con las APIs, permitiendo a dichos clientes despreocuparse por conocer de antemano del cómo acceder a los recursos.

Con Hypermedia básicamente añadimos información extra al recurso sobre su conexión a otros recursos relacionados con él.

Aquí tenemos un ejemplo:

 666
 Procesado
 
   

http://example.com/api/pedido/666/factura

   
 


En este ejemplo vemos cómo indicar en un xml que representa un pedido, el enlace al recurso de la factura relacionada con el mismo.

Sin embargo, necesitamos que el cliente que accede a nuestra API entienda que esa información no es propia del recurso, sino que es información añadida que puede utilizar para enlazar el pedido con la factura.

Para ello conseguir esto, debemos utilizar las cabeceras Accept y Content-Type, para que tanto el cliente como la API, sepan que están hablando hypermedia.

Por ejemplo:

Petición
========
GET /pedido/666
Accept: application/nuestra_api+xml, text/xml

Respuesta
=========
Status Code: 200
Content-Type: application/nuestra_api+xml
Content:


 666
 Procesado
 
   
http://example.com/api/pedido/666/factura
 


Como vemos, el cliente solicita el formato application/nuestra_api+xml de forma preferente al formato text/xml. De esta forma, le indica al servicio web, que entiende su formato hypermedia y puede aprovecharlo.

El servicio web por lo tanto, como implementa hypermedia, le devuelve la información de recurso y la información de hypermedia que puede utilizar el cliente.

Hypermedia es útil, por ejemplo, para que el cliente no tenga que conocer las URLs de los recursos, evitando de esta forma tener que hacer mantenimientos en cada uno de los mismos si en un futuro dichas URLs cambian (cosa que no debería pasar). También es útil para automatizar procesos entre APIs sin que haya interacción humana.

Conclusión

Los principios básicos para construir APIs REST se basan en conocer sobre todo HTTP, algo que no es opcional para un desarrollador web.