lunes, 27 de agosto de 2012

Java Compresion Alternativa gzip y zip

Saludos,  recientemente teníamos la necesidad de comprimir un archivo pdf, la primera prueba de compresión la hicimos con zip con las librerías nativas de java, el archivo era de aproximadamente 90 megas y el resultado era de 40, se necesitaba comprimir mucho mas, entre las alternativas que se considero pues era rar pero dado que habría que hacerse ejecutándolo fuera de java no me agradaba mucho, buscando en internet también encontramos LZMA SDK, la documentación me pareció un poco confusa y buscando ejemplos de como usarlo me tope con este link de stackoverflow http://stackoverflow.com/questions/5481487/how-to-use-lzma-sdk-to-compress-decompress-in-java en donde indicaba una implementacion nativa del algoritmo LZMA2 en java y dicha librería resulto muy fácil de usar, el link es http://tukaani.org/xz/java.html y en la misma pregunta de stackoverflow se encuentra un ejemplo muy sencillo de su uso.

Solo tenemos que baja el jar y agregarlo a nuestro proyecto, en caso de usar maven tambien se encuentra en el repositorio central y podemos agregarlo como dependencia

<dependency>  
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
<version>1.0</version>
</dependency>


y en el codigo seria algo asi  (tomado de la respuesta de stackoverlfow)


FileInputStream inFile = new FileInputStream("src.tar");
FileOutputStream outfile = new FileOutputStream("src.tar.xz");

LZMA2Options options = new LZMA2Options();

options.setPreset(7); // play with this number: 6 is default but 7 works better for mid sized archives ( > 8mb)
XZOutputStream out = new XZOutputStream(outfile, options);
byte[] buf = new byte[8192];
int size;
while ((size = inFile.read(buf)) != -1)
   out.write(buf, 0, size);

out.finish();

Con la compresion xz el archivo de 90 megas se redujo casi 6 megas, el archivo como lo mencione es un pdf que contiene texto e imagenes, si fuera solo texto pues bastaria usar zip, por eso opte por esta alternativa

jueves, 3 de mayo de 2012

Internacionalización JSF 2.0

Existe varios ejemplos de internacionalización de jsf 2.0, el inconveniente de estos ejemplos sencillos es que nos dejan con un problema ya que en la mayoría de ellos solo nos muestran como hacerlo por un solo request, hay algunas formas de modificar esto, la que más me agrado es la que voy poner aqui, es sencilla y no necesitan modificar sus archivos xhtml.

Vamos a utilizar un PhaseListener de jsf 2, El codigo es el siguiente

package org.nl.example;
import java.util.Locale;
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

public class LifeCycleListener implements PhaseListener {

public PhaseId getPhaseId() {
    return PhaseId.ANY_PHASE;
}

public void beforePhase(PhaseEvent event) {
    System.out.println("START PHASE " + event.getPhaseId());
    FacesContext context;

    if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
        context = event.getFacesContext();
        Application application = context.getApplication();
        ValueExpression ve = application.getExpressionFactory()
                                        .createValueExpression(context.getELContext(),
                                                #{localeManagedBean.locale}", Locale.class);
        try {
             System.out.println("setting locale in phase listener");
             Locale localeToSet = (Locale) ve.getValue(context.getELContext());
             context.getViewRoot().setLocale(localeToSet);

        } catch (Exception e) {
             System.out.println("error in getting locale");
        }
    }
}

public void afterPhase(PhaseEvent event) {
    System.out.println("END PHASE " + event.getPhaseId());
}
}

El PhaseListener debe ser colocado en faces-config.xml asi:


<lifecycle>
    <phase-listener>org.nl.example.LifeCycleListener</phase-listener>
</lifecycle>


El PhaseListener uunicamente le importa la fase de RENDER_RESPONSE que es la fase donde faces crea el contenido de respuesta, y ahi obtiene un locale a partir de un managebean, en este caso el managebean es localeManageBean el cual su alcance es de sesion, este a su vez contienen una propiedad de tipo Locale, la cual tiene almacenado la locale de nuestro idioma, tambie podemos actualizarla con una nueva Locale correspondiente al nuevo idioma en el que se presenten las paginas, el codigo es mas o menos asi:

package org.nl.example;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.event.ValueChangeEvent;

@ManagedBean(name = "localeManagedBean")
@SessionScoped
public class LocaleManagedBean implements Serializable {

 private static final long serialVersionUID = 1L;

 private String localeCode;
 private Locale locale;

 private static Map countries;
 static {
  countries = new LinkedHashMap();
  countries.put("English", Locale.ENGLISH); // label, value
  countries.put("Spanish", new Locale("es"));
 }

        public LocaleManagedBean() {
  super();
  locale = (Locale) countries.get("Spanish");
 }

 public Map getCountriesInMap() {
  return countries;
 }

 public String getLocaleCode() {
  return localeCode;
 }

 public void setLocaleCode(String localeCode) {
  this.localeCode = localeCode;
 }

 public Locale getLocale() {
  return locale;
 }

 public void setLocale(Locale locale) {
  this.locale = locale;
 }

 public void countryLocaleCodeChanged(ValueChangeEvent e) {

  String newLocaleValue = e.getNewValue().toString();

  // loop country map to compare the locale code
  for (Map.Entry entry : countries.entrySet()) {

   if (entry.getValue().toString().equals(newLocaleValue)) {
    locale = (Locale) entry.getValue();
    break;
   }
  }
 }

}

Este MeanageBean puede ser utilizado para colocar el nuevo idioma con el metodo countryLocaleCodeChange, no estoy seguro si este método debe retornar a una pagina, dado que en mi caso yo obtengo la Locale a travez de una cookie que fue colocada por spring mvc, espero me comenten si tienen que hacer alguna adecuación.

Espero les sirva les dejo algunas ligas que me sirvieron

http://www.mkyong.com/jsf2/jsf-2-internationalization-example/

http://www.java.net/node/703940

martes, 7 de febrero de 2012

Eclipselink Join Fetch - Cargar Relaciones Jpa

En el post anterior les comentaba mi problema con un ejb remoto y la entidad jpa que este me devolvía, bueno al final les mencione que si tienen que acceder a las relaciones lazy deben inicializarlas, aquí les dejo como cargar las relaciones lazy con un query hint

Creamos el query

Query query = entityManager.createQuery("select p from Person p where p.id = :id");
query.setParameter("id",  10);

Para colocar el query hint hacemos los sigueinte

query.setHint("eclipselink.join-fetch", "p.telefonos" );

Person p = query.getSingleResult();

Ahora en el objeto p sus telefonos ya estan cargados, desafortunadamente si en telefonos su objeto tiene otra relación a la que queremos acceder tendremos que inicializarla también pero seria con código o bueno no encontré una forma de hacerlo con otro queryhint, si la encuentran pues  espero me la digan.

Esto solo tenemos que hacerlo cuando invocamos desde una aplicación un ejb remoto que devuelve una entidad jpa y usamos eclipselink de lo contrario al acceder a una relación no inicializada nos lanzara una excepción que nos indica que debemos inicializarla, si trabajamos las entidades en ejb locales no es necesario inicializar las relaciones, eclipselink lo hace en cuanto se intente acceder a ella

Glassfish, Eclipselink, Ejb Remoto Problema con Entidad

La semana pasada realizando una aplicación que se comunica con otra aplicación atraves de un ejb remoto el cual le devolvía una entidad jpa me resultaba en problemas de null pointer al querer acceder a las propiedades de dicha entidad. Viendo el modo debug me di cuenta de que estaba recibiendo del ejb remoto un objeto el cual tenia todas las propiedades nulas, comentando esto con otro compañero me comento que había tenido el mismo problema y la causa era que no tenían la misma versión de la clase de ese objeto en la aplicación que invoca al ejb remoto y la aplicación que contiene a este (Nota: en este caso  no se trata de una entidad jpa).

Verificando lo anterior revise que tuviera la misma versión de la entidad en ambas aplicaciones pero aun se mantenía el error, buscando en google me encontré que el problema era provocado por eclipselink (usamos eclipselink para jpa en esta aplicación), eclipselink tiene una optimización llamada weaving, esta optimización la realiza por default al cargar las clases en el classloader, hace modificaciones del bytecode por lo que al hacer esto ya no tenia la misma versión de las clases en la aplicaciones.

Para solucionarlo existen 2 opciones una es deshabilitar el weaving de eclipselink pero esta opción nos quita las optimizaciones por lo que puede impactar en el performance de la aplicación, y la otra opción es utilizar el weaving estático con esta opción se procesan las clases de nuestras entidades para utilizarlas en las aplicaciones por lo que tendríamos el mismo bytecode en cada aplicación.

Para deshabilitar el weaving dinámico podemos colocar la siguiente propiedad en nuestro persistence.xml

< property name="eclipselink.weaving" value="false" / >

Para habilitar el weaving estático son mas pasos, el primero colocar la propiedad de eclipse link con valor estático

< property name="eclipselink.weaving" value="static" / >

El segundo paso es "compilar" las classes entidad que tenemos, esto lo podemos hacer con ant, si tiene la ultima version (1.8.2) el build.xml es diferente al que esta en la pagina de eclipse link, seria así :

<project name="Weaving-Eclipselink" default="clean" basedir=".">
    <description>
        Build file to generate static weaving
    </description>
  <!-- set global properties for this build -->
  <property name="dist"  location="dist"/>
  <property name="jarToWeaving"  value="myjpa.jar"/>

  <target name="init">
    <!-- Create the time stamp -->
    <tstamp/>
    <mkdir dir="${dist}"/>
    <!--copy file="${jarToWeaving}" todir="${dist}"/-->
    <copy todir="${dist}" overwrite="true" >
         <fileset dir=".">
             <include name="${jarToWeaving}" />
         </fileset>
    </copy>
  </target>

  <taskdef name="weave" classname="org.eclipse.persistence.tools.weaving.jpa.StaticWeaveAntTask"/>
 
  <target name="weaving" description="perform weaving" >
    <weave source="${jarToWeaving}"
           target="${dist}/${jarToWeaving}"
           persistenceinfo="./persistence">
      <classpath>
        <pathelement path="./lib/javax.persistence.jar"/>
      </classpath>
    </weave>
  </target>

  <target name="clean" description="clean up" >
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${dist}"/>
  </target>

</project>

Donde myjpa.jar es el jar que contiene las clases que deseo procesar para generar el weaving estático, se crea una carpeta dist y ahí se genera el nuevo jar con el weaving estático, otro punto importante es persisteninfo ahí le indico el archivo persistence.xml en el que se basara para generar el weaving estático, en mi caso cree la carpeta persistence al mismo nivel del build.xml y dentro de ella tengo META-INF y dentro de META-INF se encuentra mi archivo persistence.xml (eclipselink requiere la carpeta META-INF). El classpath de la tarea weaving no me funciono muy bien por lo que tuve que pasar las lib en la ejecución de ant

ant -lib ./lib/eclipselink.jar:./lib/javax.persistence.jar weaving




Una vez que tenemos el jar con el weaving estatico podemos incluir este en las aplicaciones que usan las entidades y con esto al invocar el ejb remoto obtenemos correctamente la entidad, bueno hay que tener cuidado con las relaciones lazy hay que inicializarlas en el ejb remoto si las vamos a utilizar.

Mas informacion de eclipselink weaving

http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_%28ELUG%29#Using_EclipseLink_JPA_Weaving