Mostrando entradas con la etiqueta Java. Mostrar todas las entradas
Mostrando entradas con la etiqueta Java. Mostrar todas las entradas

lunes, 24 de octubre de 2011

Log con campos CLOB en Log4j

Recientemente he estado probando diferentes configuraciones del log4j, una de las cosas interesantes era guardar el log en base de datos. usando log4j no representa ningún problema, ya que podemos hacer uso del appender  JDBCAppender para inserciones sencillas.

El problema surge cuando queremos almacenar la traza de un error, y su tamaño por lo general tiene un tamaño considerable (muy superior a los 4000 caracteres que te permite un varchar2 de Oracle, por ejemplo). La solución obvia (en Oracle) es usar un CLOB (Character Large Object), pero actualmente Log4j no permite el uso de CLOB.

Buscando un poco por la red, encontremos algunas soluciones alternativas como la de Danko Mannhaupt, muy recomendada, la cuál permite el uso de CLOB si usamos "prepared statements". En mi caso, no planteamos usar esta solución, ya que implicaba ciertos cambios en el proyecto así como añadir librerías de terceros, etc. Además la estructura de la tabla en BBDD planteada no se ajustaba a lo que requeríamos.

En tal caso, optamos por hacerlo nosotros mismos. Tan solo es necesario heredar de la clase  JDBCAppender (la de Log4j),  y sobreescribir (override) el método execute, el cuál tiene como parámetro de entrada la SQL ya formada, por lo que sólo deberemos tratar la ristra y crear el PreparedStatement.

Veamos un ejemplo:

package com.fraguaDigital.log;

import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.log4j.jdbc.JDBCAppender;

public class CLobJDBCAppender extends JDBCAppender {

 @Override
 protected void execute(String sql) throws SQLException {
  Connection con = null;
  Statement stmt = null;
  try {
   con = getConnection();

   if (sql.contains("#")){
    int initPos = sql.indexOf("#");
    int lastPos = sql.lastIndexOf("#");
    String clobData = sql.substring(initPos+1, lastPos);
    StringReader stringReader = new StringReader(clobData);

    sql = sql.substring(0,initPos) +"?"+ sql.substring(lastPos+1);
    stmt = con.prepareStatement(sql);
    ((PreparedStatement)stmt).setCharacterStream(1, stringReader, clobData.length());          
    ((PreparedStatement)stmt).execute();
   }else{
    stmt = con.createStatement();
    stmt.executeUpdate(sql);         
   }
  } catch (SQLException e) {
   if (stmt != null)
    stmt.close();
   throw e;
  }

  stmt.close();
  closeConnection(con);
 }
}

En este caso usamos el carácter "#" para marcar el comienzo y el fin del campo que es CLOB. Esto es sólo un ejemplo, pero puede generalizarse fácilmente.

Por último, tendríamos que añadir  en la configuración del log4j que use nuestro appender y definir en la cadena sql el campo CLOB usando las "#".

<appender name="LOG_BBDD" class="com.fraguaDigital.log.CLobJDBCAppender">
 <param name="Threshold" value="WARN"/>
 <param name="driver" value="${jdbc.driverClassName}" />
 <param name="URL" value="${jdbc.url}" />
 <param name="user" value="${jdbc.username}" />
 <param name="password" value="${jdbc.password}" />
 <layout class="org.apache.log4j.PatternLayout">
  <param name="ConversionPattern"
   value="INSERT INTO tabla (FECHA, PRIORIDAD, MENSAJE, CLASE, STACKTRACE, USUARIO) VALUES ('%d', '%p', '%m%n', '%C.%M(%L)', #%throwable#)" />
 </layout> 
</appender>

jueves, 27 de enero de 2011

java.lang.OutOfMemoryError: PermGen

En algunas ocasiones tenemos problemas con el espacio de memoria permanente en nuestras aplicaciones Java. Durante la ejecución d cualquier método o servicio la aplicación lanza una excepción java.lang.OutOfMemoryError: PermGen. Esto sucede porque el espacio de memoria Permanente (no es el Heap que se usa  para asignar los objetos), que se usa para colocar contenido generado por la aplicación, como las clases que carga el ClassLoader o las cadenas internas (String.intern), que se usan para optimizar el manejo de las String, se llena y se queda sin espacio. El contenido cargado en este área de memoria no se libera (por defecto).

Sucede habitualmente durante los reinicios en caliente de los servidores de aplicaciones, ya que se producen fugas de memoria al reiniciar el servidor sin apagarlo, es decir como no se apaga no se libera la memoria permanente usada, y tras múltiples reinicios se llena. La solución obvia  en este caso es reiniciar.

También puede suceder por otras circunstancias, como que realmente se esté llenando el espacio de memoria permanente. La solución trivial es aumentar el tamaño de este espacio de memoria, para ello modificaremos las opciones de la Máquina Virtual de Java (JVM) usando la variable de entorno JAVA_OPTS aumentaremos el tamaño máximo con:

 -XX:MaxPermSize=256m

Esta solución puede solucionar el problema, si colocamos el tamaño adecuado, pero si aun así el espacio es insuficiente tan sólo lo retrasaremos. Para ello deberíamos saber cuánta memoria esta usando nuestra aplicación. Para saber cuánta memoria usa la aplcacion debemos añadir en JAVA_OPTS lo siguiente:

            -verbose:gc -XX:+PrintGCDetails

Con los valores anteriores indicamos a la JVM que se muestre los datos de recolector de basura y que muestre los detalles, algo así:

[Full GC [CMS: 168674K->168674K(379016K), 0.9549626 secs] 168688K->168674K(395336K), [CMS Perm : 72024K->72024K(122292K)], 0.9556050 secs]

El texto indicado tiene el siguiente patrón:

[CMS Perm : Memoria Antes(Kb)->Memoria después(Kb)(Memoria en uso(Kb))]

Con lo cuál veremos, cada vez que se ejecute el recolector de basura la memoria en uso, y sabremos que tamaño debemos poner al espacio PermGen.

Una solución mejor es permitir  el recolector de basura que elimine el contenido de la memoria PermGen que no esté en uso, hay muchas clases que se cargan y se quedan ahí sin usarlas más (esto sucede con asiduidad si usamos frameworks como Hibernate o Spring). Para ello debemos añadir a la variable JAVA_OPTS lo siguiente:

  • -XX:+UseConcMarkSweepGC --> Política del garbageCollector, para que trabaje de forma concurrente si hay múltiples procesadores.
  • -XX:+CMSPermGenSweepingEnabled --> Se habilita el barrido por el espacio de memoria permanente (PermGen).
  • -XX:+CMSClassUnloadingEnabled --> Se permite descargar clases que no se usen.


NOTA IMPORTANTE: Si estamos usando Maven para lanzar nuestro servidor de aplicaciones la variable de entrono que debemos modificar es MAVE_OPTS en lugar de JAVA_OPTS.

miércoles, 29 de diciembre de 2010

Fechas con un día menos en Flex y Java usando BlazeDS

   Cuando trabajamos con fechas en entornos con Flex y Java debemos tener cuidado de un curioso efecto, al cargar una fecha correcta en Java en Flex puede verse con un día menos.

   Este efecto se debe a como son transferidas las fechas entre un servidor Java y un cliente Flex, para ello se serializa la fecha en UTC, es decir sin información de la zona horaria. La conversión a la hora local se realiza a nivel del protocolo de forma automática, por lo tanto si el servidor no conoce la zona horaria del cliente no podrá transformarla adecuadamente. Dependiendo de la aplicación puede ser deseable almacenar/recuperar los datos en la zona horaria del cliente que los guarda, pero en la mayoría de los casos no lo es.

La solución (tal como se indica en el CookBook de Flex) consiste en forzar que la fecha se intercambie sin la información de la zona horaria. Para ello modificaremos los métodos accesores (getters y setters) de las fechas tanto del objeto java, como en el objeto en actionscript de forma que intercambiemos la fecha como el numero de milisegundos desde 1970 menos la cantidad añadida por la zona horaria (para que la trate como en GMT+0). Los objetos quedarían  así:
Java
package com.fraguaDigital;
 
import java.io.Serializable;
import java.util.Date;
import java.util.TimeZone;
 
public class MiClase implements Serializable {

    // ... Otros atributos ...
 
 private Date _fecha;
 public Date getFecha() {
  Date newDate = new Date(_fecha.getTime() - TimeZone.getDefault().getOffset(_fecha.getTime()));
  return newDate;
 }
 public void setFecha(Date value) {
  Date newDate = new Date(value.getTime() + TimeZone.getDefault().getOffset(value.getTime()));
  this._fecha = newDate;
 }
}

ActionScript (Flex)
package com.fraguaDigital
{
 [RemoteClass(alias="com.fraguaDigital.MiClase")]
 public class MiClase
 {
     // ... Otros atributos ...
  
  public var _fecha:Date;
  public function set fecha(value:Date):void {
   var newDate:Date = new Date(value.valueOf() - value.timezoneOffset);
   this._fecha = newDate;
  }
  
  public function get fecha():Date {
   var newDate:Date = new Date(_fecha.valueOf() + _fecha.timezoneOffset);
   return newDate;
  }
 }
}

Con esto ya las fechas llegarán correctamente al cliente.

lunes, 23 de agosto de 2010

Identificador único en objetos Java

Recientemente hice una pequeña investigación sobre la mejor forma  de identificar una instancia de un objeto de forma única, de forma que podamos utilizar dicho UID (Unique identifier). Este identificador puede ser muy útil a la hora de cachear instancias , por ejemplo en un HashMap.

Documentándome un poco por la red, he leído muchas "soluciones", algunas más acertadas y otras menos, y he aquí el verdadero motivo de que escriba esta entrada.

Cualquier desarrollador con un poco de experiencia en Java sabrá que esto se resuelve usualmente mediante el método hashCode, el cuál genera un código de hash para un objeto, que nos permite indexarlo fácilmente. Pero, ¿que pasa cuando se han sobrecargado dicho método generando un override del objeto que cambia su funcionalidad?. Este escenario es bastante habitual, ya que se suelen generar los métodos equals y hashCode, para poder comparar objetos y saber a igualdad de datos si se trata del mismo objeto.

Una cosa que ha llamado mi atención en particular es que en muchos sitios proponen usar el método toString, para obtener a un identificador único, siempre y cuando no se halla sobreescrito dicho método en las clases. Esta suposición no es errónea per se,  pero resulta curiosa la forma de usar dicho método, y las limitaciones que conlleva (ya que no podremos modificar el método toString). Este método se basa en la implementación del método toString en la clase java.lang.Object, el cuál concatena el nombre de la clase con el hashcode y no con la dirección de memoria como cree mucha gente. El formato de la String devuelta  puede verse en la documentación de Java):

public String toString() {
   return getClass().getName() + '@' + Integer.toHexString(hashCode());
 }

Si esto es así, dicha solución tampoco funcionará en todos los casos, si hemos hecho un override del método hashCode basándonos en algunos datos, o cuando menos no podemos decir que la "resistencia a colisiones" sea alta.

En estas situaciones lo mejor sería utilizar el método hashCode original de la clase Object, el cual asegura que dos instancias distintas, aún cuando tengan los mismo datos, devolverán un hashCode diferente. Esto se debe a que el calculo de los datos se hace de forma nativa en base a la dirección de memoria donde se encuentra la instancia:

public int hashCode() {
   return VMMemoryManager.getIdentityHashCode(this);
 }

Para acceder al método de forma independiente de si lo hemos sobreescrito podemos usar la clase System, llamando al metodo identityHashCode.

System.identityHashCode(object);

Otra opción un poco más "delicada" es usar la clase sun.misc.Unsafe, para obtener la dirección real de memoria, podemos leer un poco más sobre ello aquí. esto es algo totalmente irrecomendable, que tiene cabida sólo como investigación para aquellos que gusten de saber como funcionan las cosas (Para que la llamaron Unsafe si no querían que trasteáramos con ella. XD).



jueves, 1 de octubre de 2009

Acceder a servicio SOAP tras un proxy con CXF

Si queremos acceder, con CXF, a un servicio SOAP a través de un Proxy puede suceder que falle la llamada, ya que el resultado que nos devuelve es "troceado", y por tanto al recibir el mensaje de retorno, este no cumple con el formato SOAP.

Nos mostrará un error al acceder a la URL en la que se encuentra el servicio, indicándonos el mensaje "Método de la petición y protocolo no soportados". En el caso de que el proxy sea Squid nos mostrará también el siguiente mensaje "Squid no admite todos los métodos para todos los protocolos de acceso. Por ejemplo, no se puede hacer un POST a un servidor Gopher".

El stackTrace provocado por el error es algo similar a similar a este:


Para solucionar este problemas debemos configurar el CXF para desactivar el "chunking" (troceado), para ello crearemos/modificaremos el fichero de configuración de CXF (por defecto cxf.xml), consistente en un conjunto de beans de Spring, en el que añadiremos o descomentaremos, si ya existe el fichero, lo siguiente:

Podemos ver como configurar el CXF en la sección de configuración de la página oficial de Apache.

sábado, 4 de abril de 2009

Integración BlazeDS con Spring

Una muy buena noticia, para todos los desarrolladores Flex que usamos BlazeDS para comunicarnos con un servidor Java, Spring esta desarrollado "Spring BlazeDS Integration".

Este proyecto nos simplificará enormemente la vida a la hora de conectar una aplicación Flex a un servidor Java desarrollado con Spring. Simplificará mucho los ficheros de configuración, la cuál la realizaremos creando beans (sintaxis < bean>).

Aún esta en Milestone 2, pero ya se plantea liberala como RC1 (Release Candidate 1). Entre otras cosas permitirá definir el messageBroker, los remote objects, pero sin lugar a dudas el aspecto que más me ha gustado viendo la documentación de referencia es que también incluirá la configuración para Spring Security (anteriormente conocido como Acegi Security). Con lo cuál podremos asegurar de forma simple nuestras aplicaciones Flex.

Podemos ver el anunció en la página oficial de Spring. En dicha página también podemos encontrar este enlace a adobeTV, en el que Christophe Coenraets hace una gran introducción a Spring BlazeDS Integration.

P.D: Spring security nos da funcionalidades de autorización y autentificación para el uso de nuestros servicios, pero eso ya lo explicaremos en otros post.

Saludos.