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.