Páginas

martes, 11 de diciembre de 2012

HQSLDB + Hibernate + Spring + JSF2

En esta entrada vamos a comentar cómo trabajar con Hibernate, Spring y HSQLDB. El objetivo es montar un ejemplo minimizando la parte declarativa (ficheros applicationContext.xml y faces-config.xml) y maximizando el uso de anotaciones.

Las anotaciones que aparecerán en cada una de las capas son:
  • Presentación: @ManagedBean y @SessionScoped
  • Negocio: @Service, @Autowired y @Transactional
  • DAO's: @Repository, @Autowired
  • Pojos: @Entity, @Table, @Id, @Column
Partiremos de un proyecto Maven donde tendremos los directorios estándar src/main/java y src/main/resources.

Vamos a empezar desde abajo. Tendremos una tabla llamada "EJEMPLO" con dos columnas, la columna "ID" que será la clave primaria y la columna "DESCRIPCION" que contendrá algún texto. Por tanto tendremos una clase como la siguiente:

package es.ine.sgtic.TestHSQL.pojo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table (name="Ejemplo")
public class EjemploPojo {

    private String id;
    private String descripcion;
   
    @Id
    @Column(name="id")
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    @Column(name="descripcion")
    public String getDescripcion() {
        return descripcion;
    }
    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }
}


El fichero src/main/resources/META-INF/persistence.xml contendrá:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

    <persistence-unit name="PERSISTENCE_UNIT" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
    </persistence-unit>
</persistence>


En la capa de acceso a datos tendríamos un interfaz IEjemploDao.java:

package es.ine.sgtic.TestHSQL.dao;

import java.util.List;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

public interface IEjemploDao {
    public void save(EjemploPojo pojo);
    public List<EjemploPojo> getEjemplos();
}


En la capa de acceso a datos tendríamos también una implementación JPA del interfaz anterior EjemploDaoJpa.java:

package es.ine.sgtic.TestHSQL.dao.jpa;

import java.util.List;
import javax.persistence.EntityManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Repository;
import es.ine.sgtic.TestHSQL.dao.IEjemploDao;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

@Repository
public class EjemploDaoJpa implements IEjemploDao {

    private Log logger = LogFactory.getLog(EjemploDaoJpa.class);

    @Autowired
    private EntityManager entityManager;
   
    public void save(final EjemploPojo pojo) {
        logger.info("Save(pojo)");    
        try {
            entityManager.persist(pojo);
        } catch (RuntimeException re) {
            logger.error("persist failed", re);
            throw re;
        }
    }
   
    public List<EjemploPojo> getEjemplos() {
        logger.info("getEjemplos()");       
        try {
            final StringBuilder query = 

            new StringBuilder("from ").append(EjemploPojo.class.getName()).append(" c ");
            final JpaTemplate jpaTemplate = new JpaTemplate(entityManager);
            return (List<EjemploPojo>) jpaTemplate.find(query.toString());
        } catch (RuntimeException re) {
            logger.error("getEjemplos failed", re);
            throw re;
        }
    }
}


A destacar las siguientes anotaciones:
  • @Repository. Anotación de Spring que indica que la presente clase es un DAO.
  • @Autowired. Permite que Spring inyecte el valor apropiado al atributo asociado tras invocarse el constructor de la clase.
En la capa de negocio tendríamos un interfaz IGestorEjemplo.java:

package es.ine.sgtic.TestHSQL.negocio;

import java.util.List;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

public interface IGestorEjemplo {
    public void salvarEjemplo(EjemploPojo pojo);
    public List<EjemploPojo> recuperaEjemplos();
}



En la capa de negocio tendríamos también una implementación del interfaz anterior GestorEjemploImpl.java:

package es.ine.sgtic.TestHSQL.negocio.impl;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import es.ine.sgtic.TestHSQL.dao.IEjemploDao;
import es.ine.sgtic.TestHSQL.negocio.IGestorEjemplo;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

@Service("gestorEjemplo")
public class GestorEjemploImpl implements IGestorEjemplo {

    private Log logger = LogFactory.getLog(GestorEjemploImpl.class);

    @Autowired
    private IEjemploDao ejemploDao;
   
    @Transactional
    public void salvarEjemplo(EjemploPojo pojo){
        logger.info("salvarEjemplo()");
        ejemploDao.save(pojo);
    }

    @Transactional
    public List<EjemploPojo> recuperaEjemplos(){
        logger.info("recuperaEjemplos()");
        return ejemploDao.getEjemplos();
    }
}


A destacar las siguientes anotaciones:
  • @Service. Anotación de Spring que indica que la presente clase es un servicio de la clase de negocio.
  • @Autowired. Permite que Spring inyecte el valor apropiado al atributo asociado tras invocarse el constructor de la clase.
  • @Transactional. Marcamos el método asociado como transaccional.
En la capa de presentación pondremos por ejemplo un bean JSF2 de ámbito "Session", que dará respaldo funcional a una o varias pantallas. En nuestro ejemplo el bean sería la clase SesionBean.java:

package es.ine.sgtic.TestHSQL.bean;

import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import es.ine.sgtic.TestHSQL.negocio.IGestorEjemplo;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

@ManagedBean
@SessionScoped
public class SesionBean {

    @ManagedProperty(value="#{gestorEjemplo}")
    private IGestorEjemplo gestorEjemplo;
   
    public void setGestorEjemplo(IGestorEjemplo gestorEjemplo) {
        this.gestorEjemplo = gestorEjemplo;
    }




    //Atributos que se usarán en diversas pantallas
    private String codigo;
    private String descripcion;
    private List<EjemploPojo> lst;
    

    //Métodos setter/getter de los atributos
    public String getCodigo() {
        return codigo;
    }
    public void setCodigo(String codigo) {
        this.codigo = codigo;
    }
    public String getDescripcion() {
        return descripcion;
    }
    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }
    public List<EjemploPojo> getLst() {
        return lst;
    }
    public void setLst(List<EjemploPojo> lst) {
        this.lst = lst;
    }


    public String insertaEjemplo(){
        final EjemploPojo pojo = new EjemploPojo();
        pojo.setId(this.codigo);
        pojo.setDescripcion(this.descripcion);
        gestorEjemplo.salvarEjemplo(pojo);
        lst = gestorEjemplo.recuperaEjemplos();

        return null;
    }
}


A destacar las siguientes anotaciones:
  • @ManagedBean. Anotación JSF2 que marca la clase como un bean JSF.
  • @SessionScoped. Anotación JSF2 que marca indica a JSF que el Bean tiene ámbito de sessión, esto es, ofertará sus funciones/atributos accesibles a cualquier página de la capa de presentación en lo que se mantenga activa la sesión del usuario.
  • @ManagedProperty. Anotación JSF2 que indica qué gestor de negocio debe inyectarse al atributo asociado.
Nos queda únicamente comentar el contenido de los ficheros de configuración: applicationContext.xml, faces-config.xml y el pom.xml de maven.

El contenido del applicationContext.xml es el siguiente:

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <bean id="dataSourcePool" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${proyecto.web.jdbc.driverName}" />
        <property name="url" value="${proyecto.web.jdbc.url}" />
        <property name="username" value="${proyecto.web.jdbc.user}" />
        <property name="password" value="${proyecto.web.jdbc.password}" />
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSourcePool" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="${hibernate.show_sql}" />
                <property name="generateDdl" value="true" />
                <property name="databasePlatform" value="${hibernate.database_platform}" />
            </bean>
        </property>
    </bean>

    <bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <tx:annotation-driven />
    <context:annotation-config base-package="es.ine.sgtic" />
    <context:component-scan base-package="es.ine.sgtic" />
</beans>


Comentarios:
  • El fichero está repleto de ${xxxxxxxx} que son los valores que se definen desde el fichero pom.xml de maven a la hora de montar el proyecto para distintos entornos: localhost, desarrollo, producción, ...
  • <context:annotation-config base-package="es.ine.sgtic" /> Activa la detección de anotaciones: @Autowire en el paquete especificado y sus dependientes.
  • <context:component-scan base-package="es.ine.sgtic" /> Activa la detección de anotaciones @Service y @Repostitory en el paquete especificado y sus dependientes.
  • showSql="${hibernate.show_sql}". Indicamos a hibernate que genere una traza en la consola por cada sentencia lanzada sobre la base de datos. Esta línea debería estar deshabilitada en producción.
  • generateDdl="true". Cuando se cree la SessionFactory de Hibernate, se creará el esquema de base de datos, cuando se cierre el  SessionFactory las tablas serán eliminadas. Este ajuste es provisional y para desarrollo, pero si ya existe una base de datos con información deberíamos dejar la línea comentada o a false.
  • databasePlatform="${hibernate.database_platform}". Este parámetro indica a hibernate la base de datos con la que va a trabajar para que realice los ajustes correspondientes en las sentencias. No todas las bases de datos entienden un SQL 100% idéntico, no todas las bases de datos colocan un Integer en el mismo tipo de campo, etc.
El contenido del faces-config.xml es el siguiente:

<?xml version="1.0" encoding="UTF-8"?>

<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
 

    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
        <resource-bundle>
            <base-name>mensaje.Mensaje</base-name>
            <var>MensajeApp</var>
        </resource-bundle>
        <locale-config>
            <default-locale>es</default-locale>
            <supported-locale>ca</supported-locale>
            <supported-locale>ca_VA</supported-locale>
            <supported-locale>eu</supported-locale>
            <supported-locale>gl</supported-locale>
            <supported-locale>en</supported-locale>
        </locale-config>
    </application>
</faces-config>


Comentarios: En el fichero desaparecen todas las declaraciones de los bean JSF y las referencias a los gestores de negocio que pudiesen usar.

El contenido del pom.xml es el siguiente:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>TestHSQL</artifactId>
    <packaging>war</packaging>
    <version>1.0</version>
    <build>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>*.properties</exclude>
                    <exclude>applicationContext*.xml</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.properties</include>
                    <include>applicationContext*.xml</include>
                </includes>
            </resource>
        </resources>
        <finalName>${proyecto.web.final_name}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${proyecto.build.jdk_version}</source>
                    <target>${proyecto.build.jdk_version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>tomcat-maven-plugin</artifactId>
                <configuration>
                    <warFile>${project.build.directory}/${project.build.finalName}.war</warFile>
                    <update>true</update>
                    <path>/${project.build.finalName}</path>
                    <charset>${project.build.sourceEncoding}</charset>
                    <url>http://${proyecto.tomcat_deploy.server_port}/manager</url>
                    <username>${proyecto.tomcat_deploy.user}</username>
                    <password>${proyecto.tomcat_deploy.password}</password>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>es.ine.sgtic.lib.spring.2.5.0.j2ee</groupId>
            <artifactId>el-api</artifactId>
            <version>1.5.0_06</version>
            <type>jar</type>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.lib.tomcat6</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <type>jar</type>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.lib.spring.2.5.0.junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.4</version>
            <type>jar</type>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.comun.lib.webapp</groupId>
            <artifactId>jsf2mojarra-hibernate-bbddunica-lib</artifactId>
            <version>2.0</version>
            <type>pom</type>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <artifactId>ine-util-comunes</artifactId>
                    <groupId>es.ine.sgtic.comun.util.utilidades</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.lib.spring.2.5.0.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.comun.util.utilidades</groupId>
            <artifactId>ine-util-base</artifactId>
            <version>2.0</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <properties>
        <!-- Propiedades del proyecto -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <proyecto.build.jdk_version>1.6</proyecto.build.jdk_version>

        <!-- Propiedades del plugins -->
        <proyecto.tomcat_deploy.server_port>localhost:8080</proyecto.tomcat_deploy.server_port>
        <proyecto.tomcat_deploy.user>admin</proyecto.tomcat_deploy.user>
        <proyecto.tomcat_deploy.password>admin</proyecto.tomcat_deploy.password>

        <!-- Ajustes hibernate  -->
        <hibernate.show_sql>true</hibernate.show_sql>
        <hibernate.database_platform>org.hibernate.dialect.HSQLDialect</hibernate.database_platform

        <proyecto.web.jdbc_comun.driverName>org.hsqldb.jdbcDriver</proyecto.web.jdbc_comun.driverName>

        <!-- Log de Aplicacion -->
        <log4j.debug.ruta_fichero>/var/log/tomcat6/logs/TestHSQL/local</log4j.debug.ruta_fichero>
        <log4j.debug.MaxFileSize>9999KB</log4j.debug.MaxFileSize>
        <log4j.debug.MaxBackupIndex>3</log4j.debug.MaxBackupIndex>
        <log4j.debug.appender_activo>CONSOLA,FICHERO</log4j.debug.appender_activo>
        <log4j.debug.nivel_traza.general>ERROR</log4j.debug.nivel_traza.general>
        <log4j.debug.nivel_traza.spring>
ERROR</log4j.debug.nivel_traza.spring>
        <log4j.debug.nivel_traza.hibernate>
ERROR</log4j.debug.nivel_traza.hibernate>
        <log4j.debug.nivel_traza.myfaces>
ERROR</log4j.debug.nivel_traza.myfaces>
        <log4j.debug.nivel_traza.apache_commons>
ERROR</log4j.debug.nivel_traza.apache_commons>

        <!-- Propiedades del application-context -->
        <proyecto.web.final_name>TestHSQL</proyecto.web.final_name>

        <!-- BBDD -->
        <!-- StandAlone (El servidor de BBDD levantado en otro sitio) -->
        <!-- <proyecto.web.jdbc_padron.url>jdbc:hsqldb:hsql://localhost/dbname</proyecto.web.jdbc_padron.url> -->
        <!-- Fichero (El fichero en la raíz del classpath) -->
        <proyecto.web.jdbc_padron.url>jdbc:hsqldb:file:dbname</proyecto.web.jdbc_padron.url>
        <!-- Fichero (El fichero en el disco duro) -->
        <!--<proyecto.web.jdbc_padron.url>jdbc:hsqldb:file:/home/egdepedro/Escritorio/dbname</proyecto.web.jdbc_padron.url>-->
        <!-- Memoria (No hay fichero) -->
        <!-- <proyecto.web.jdbc_padron.url>jdbc:hsqldb:mem:dbname</proyecto.web.jdbc_padron.url> -->
        <proyecto.web.jdbc_padron.user>sa</proyecto.web.jdbc_padron.user>
        <proyecto.web.jdbc_padron.password></proyecto.web.jdbc_padron.password>
    </properties>
</project>



Comentarios:

En este fichero se fijan los parámetros que definen la base de datos, el comportamiento de hibernate, etc. En este caso no se han definido profiles, pero podríamos tener varios "desa", "prod", ...
En cuanto a la BBDD HSQLDB, se plantean tres posibilidades, en la primera tenemos una base de datos en la raíz del classpath: "jdbc:hsqldb:file:dbname", en la segunda tenemos la base de datos en cierta ubicación del disco duro: "jdbc:hsqldb:file:/home/xxxxxx/yyyyyy/dbname" y en la tercera tenemos la BBDD permanentemente levantada en cierto servidor: "jdbc:hsqldb:hsql://xxx.xxx.xxx.xxx/dbname"



jueves, 8 de noviembre de 2012

Configurar Log4j en Apache CXF

En la anterior entrada dejamos montado un ejemplo con Apache CFX y WS-Security. Pero como Apache CXF utiliza por defecto slf4j el lugar de log4j, teníamos el log de las peticiones y respuestas en ficheros distintos al resto de los módulos, donde usamos log4j.

Vamos a repasar como ajustar ambos módulos para que se genere la traza en nuestro fichero de log habitual.

Aplicativo cxf-web

1) Deberemos ajustar el fichero /src/main/resources/log4j.properties con el siguiente contenido:

log4j.rootCategory=INFO,CONSOLA

log4j.logger.es.ine.cxf.ws=INFO

log4j.logger.org.apache.cxf.interceptor.LoggingInInterceptor.level=INFO
log4j.logger.org.apache.cxf.interceptor.LoggingOutInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JInInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JOutInterceptor.level=INFO

log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.FICHERO=org.apache.log4j.RollingFileAppender
log4j.appender.FICHERO.file=${log4j.debug.ruta_fichero}/cxf-web.log
log4j.appender.FICHERO.append=true
log4j.appender.FICHERO.MaxFileSize=${log4j.debug.MaxFileSize}
log4j.appender.FICHERO.MaxBackupIndex=${log4j.debug.MaxBackupIndex}
log4j.appender.FICHERO.layout=org.apache.log4j.PatternLayout
log4j.appender.FICHERO.layout.ConversionPattern=[%d{dd-MM-yyyy HH\:mm\:ss,SSS}] thread\:[%t] nivelTraza\:[%p] lugarTraza\:[%c] mensajeTraza[%m]%n


2) Deberemos crear un fichero llamado:

/src/main/resources/META-INF/cxf/org.apache.cxf.Logger

El contenido de este fichero será:

org.apache.cxf.common.logging.Log4jLogger

3) Deberemos ajustar nuestra clase cliente así:

package es.ins.sgtic.app;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

import es.ine.cxf.ws.impl.Calculadora;
import es.ine.cxf.ws.impl.CalculadoraImplService;

public class AppTest {

    private static final QName SERVICE_NAME = new QName("http://impl.ws.cxf.ine.es/", "CalculadoraImplService");
   
    public static void main(String[] args) {

        //Definimos el servicio web a usar
        final URL wsdlURL = CalculadoraImplService.WSDL_LOCATION;
        final CalculadoraImplService calcService = new CalculadoraImplService(wsdlURL, SERVICE_NAME);
        final Calculadora calculadora = calcService.getCalculadoraImplPort();
               
        //Obtenemos la referencia al endpoint del ws
        final Client client = ClientProxy.getClient(calculadora);
        final Endpoint cxfEndpoint = client.getEndpoint();
       
        //Ajustamos el log del cliente
        final LoggingInInterceptor incerceptorLogIn = new LoggingInInterceptor();
        incerceptorLogIn.setPrettyLogging(true);
        final LoggingOutInterceptor incerceptorLogOut = new LoggingOutInterceptor();
        incerceptorLogOut.setPrettyLogging(true);
        client.getInInterceptors().add(incerceptorLogIn);
        client.getOutInterceptors().add(incerceptorLogOut);

       
        //Configuramos el interceptor de cifrado/firmado para los envíos
        final Map<String,Object> outProps = new HashMap<String,Object>();
        outProps.put("action", "Timestamp Signature Encrypt");
        outProps.put("user", "edugarci");
        outProps.put("signaturePropFile", "props/client-key-store.properties");
        outProps.put("signatureKeyIdentifier", "DirectReference");
        outProps.put("passwordCallbackClass", "es.ine.cxf.ws.sec.PasswordCallback");
        outProps.put("signatureParts", "{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body");
        outProps.put("encryptionPropFile", "props/client-trust-store.properties");
        outProps.put("encryptionUser", "desarrollo-internet");
        outProps.put("encryptionParts", "{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body");
        outProps.put("encryptionSymAlgorithm", "http://www.w3.org/2001/04/xmlenc#tripledes-cbc");
        outProps.put("encryptionKeyTransportAlgorithm", "http://www.w3.org/2001/04/xmlenc#rsa-1_5");
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps); //request
        cxfEndpoint.getOutInterceptors().add(wssOut);
        cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());
       
        //Configuramos el interceptor de descifrado/verificacion-firma para las recepciones
        Map<String,Object> inProps= new HashMap<String,Object>();
        inProps.put("action", "Timestamp Signature Encrypt");
        inProps.put("signaturePropFile", "props/client-trust-store.properties");
        inProps.put("passwordCallbackClass", "es.ine.cxf.ws.sec.PasswordCallback");
        inProps.put("decryptionPropFile", "props/client-key-store.properties");
        WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps); //response
        cxfEndpoint.getInInterceptors().add(wssIn);
        cxfEndpoint.getInInterceptors().add(new SAAJInInterceptor());
       
        //Lanzamos unas peticiones de ejemplo
        System.out.println(String.format("port.suma(15, 3) --> %d",calculadora.suma(15, 3)));
        System.out.println(String.format("port.resta(15, 3) --> %d",calculadora.resta(15, 3)));
        System.out.println(String.format("port.multiplica(15, 3) --> %d",calculadora.multiplica(15, 3)));
        System.out.println(String.format("port.divide(15, 3) --> %d",calculadora.divide(15, 3)));
    }
}


Aplicativocxf-client

1) Deberemos ajustar el fichero /src/main/resources/log4j.properties con el siguiente contenido:

log4j.rootCategory=INFO,CONSOLA

log4j.logger.es.ine.cxf.ws=INFO

log4j.logger.org.apache.cxf.interceptor.LoggingInInterceptor.level=INFO
log4j.logger.org.apache.cxf.interceptor.LoggingOutInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JInInterceptor.level=INFO
log4j.logger.org.apache.cxf.ws.rm.security.wss4j.WSS4JOutInterceptor.level=INFO

log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.FICHERO=org.apache.log4j.RollingFileAppender
log4j.appender.FICHERO.file=${log4j.debug.ruta_fichero}/cxf-web.log
log4j.appender.FICHERO.append=true
log4j.appender.FICHERO.MaxFileSize=${log4j.debug.MaxFileSize}
log4j.appender.FICHERO.MaxBackupIndex=${log4j.debug.MaxBackupIndex}
log4j.appender.FICHERO.layout=org.apache.log4j.PatternLayout
log4j.appender.FICHERO.layout.ConversionPattern=[%d{dd-MM-yyyy HH\:mm\:ss,SSS}] thread\:[%t] nivelTraza\:[%p] lugarTraza\:[%c] mensajeTraza[%m]%n


2) Deberemos crear un fichero llamado:

/src/main/resources/META-INF/cxf/org.apache.cxf.Logger

El contenido de este fichero será:

org.apache.cxf.common.logging.Log4jLogger

3) Deberemos ajustar el contenido del fichero applicationContext.xml, dejándolo así:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xmlns:cxf="http://cxf.apache.org/core"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                        http://cxf.apache.org/jaxws
                        http://cxf.apache.org/schemas/jaxws.xsd
                        http://cxf.apache.org/core
                        http://cxf.apache.org/schemas/core.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <bean id="firmaPeticion" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos la verificación de la firma de las peticiones que nos
                    llegan -->
                <entry key="signaturePropFile" value="props/server-trust-store.properties" />
                <!-- Ajustamos el desencriptado de los datos de la petición que recibimos -->
                <entry key="decryptionPropFile" value="props/server-key-store.properties" />
                <entry key="passwordCallbackClass" value="es.ine.cxf.ws.sec.PasswordCallback" />
            </map>
        </constructor-arg>
    </bean>

    <bean id="firmaRespuesta" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos el firmado de las respuestas -->
                <entry key="signaturePropFile" value="props/server-key-store.properties" />
                <entry key="user" value="desarrollo-internet" />
                <!-- Ajustamos el encriptado de las respuestas -->
                <entry key="encryptionPropFile" value="props/server-trust-store.properties" />
                <entry key="encryptionUser" value="edugarci" />
                <entry key="signatureKeyIdentifier" value="DirectReference" />
                <entry key="passwordCallbackClass" value="es.ine.cxf.ws.sec.PasswordCallback" />
                <entry key="signatureParts"
                    value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body" />
                <entry key="encryptionParts"
                    value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body" />
                <entry key="encryptionKeyTransportAlgorithm" value="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
                <entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
            </map>
        </constructor-arg>
    </bean>

    <jaxws:endpoint xmlns:tns="http://impl.ws.cxf.ine.es/"
        id="calculadora" implementor="es.ine.cxf.ws.impl.CalculadoraImpl"
        wsdlLocation="wsdl/calculadoraimpl.wsdl" endpointName="tns:CalculadoraImplPort"
        serviceName="tns:CalculadoraImplService" address="/CalculadoraImplPort">
        <jaxws:outInterceptors>
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
            <ref bean="firmaRespuesta" />
        </jaxws:outInterceptors>
        <jaxws:inInterceptors>
            <ref bean="firmaPeticion" />
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
        </jaxws:inInterceptors>
    </jaxws:endpoint>

    <bean id="abstractLoggingInterceptor" abstract="true">
        <property name="prettyLogging" value="true" />
    </bean>
    <bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor"
        parent="abstractLoggingInterceptor" />
    <bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"
        parent="abstractLoggingInterceptor" />

    <cxf:bus>
        <cxf:inInterceptors>
            <ref bean="loggingInInterceptor" />
        </cxf:inInterceptors>
        <cxf:outInterceptors>
            <ref bean="loggingOutInterceptor" />
        </cxf:outInterceptors>
        <cxf:outFaultInterceptors>
            <ref bean="loggingOutInterceptor" />
        </cxf:outFaultInterceptors>
        <cxf:inFaultInterceptors>
            <ref bean="loggingInInterceptor" />
        </cxf:inFaultInterceptors>
    </cxf:bus>

</beans>
 

Apache CXF + WS-Security + Tomcat 6

Finalidad

Se ha montado una prueba de cifrado y firmado de comunicaciones de servicios web con Apache CXF. El objetivo es montar un servicio web y un cliente donde  se cifren y firmen tantos las peticiones de los clientes como las respuestas del servicio web.

Entorno

- Eclipse Helios
- Apache Tomcat 6.0.35
- Apache CXF 2.4.10
- Maven2
- JDK 1.6.0_31

Preparación del entorno

Como ya teníamos instalado Eclipse Helios, el plugin para Maven y el Tomcat 6.0.35, empezaremos el tutorial con la descarga e instalación de Apache CXF.

1) Descargamos Ápache CXF de la URL:

http://www.apache.org/dyn/closer.cgi?path=/cxf/2.4.10/apache-cxf-2.4.10.tar.gz

2) Lo descomprimimos en algún directorio de nuestro disco. Por aquello de tener las cosas organizadas y prepararnos por si tuviésemos que manejar otras versiones de apache-cxf en el futuro, es recomendable ubicarlo en:

/home/xxxxx/apache-cxf/apache-cxf-2.4.10

3) Abrimos Eclipse Helios y definimos un nuevo workspace, por ejemplo:

/home/xxxxx/wks-helios/WksTestApacheCXFJetty

4) Definimos el servidor Tomcat 6.0.35.

- Hacemos clic en la opción de menú: Window/Show View/Other/Servers
- Sobre la vista Servers sacamos el menú contextual pulsado el botón derecho y seleccionamos la opción New/Server eligiendo el Tomcat 6 y definiendo la ruta de instalación del mismo en el sistema (donde se descomprimió).

5) Definimos la ubicación de las librerías de Apache CXF 2.4.10.


- Hacemos clic en la opción de menú: Window/Preferences/Web Services/CXF 2.X Preferences
- Pulsamos el botón Add e indicamos el path de descompresión de Apache CXF 2.4.10

Proyecto padre

Dado que estamos con maven, montamos un proyecto multimodular "cxf" con dos módulos. El primer módulo "cxf-web" alojará los servicios web. El segundo módulo "cxf-client" alojará un aplicativo java stand-alone que ejercerá de cliente.

El fichero pom.xml de este proyecto padre sería:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

    http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>es.org.sgtic</groupId>
    <artifactId>cxf</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>
    <build>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>*.properties</exclude>
                    <exclude>applicationContext*.xml</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.properties</include>
                    <include>applicationContext*.xml</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${maven-compiler-plugin.source}</source>
                    <target>${maven-compiler-plugin.target}</target>
                    <encoding>${maven-compiler-plugin.encoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <!-- Propiedades plugin: maven-compiler-plugin -->
        <maven-compiler-plugin.source>1.6</maven-compiler-plugin.source>
        <maven-compiler-plugin.target>1.6</maven-compiler-plugin.target>
        <maven-compiler-plugin.encoding>UTF-8</maven-compiler-plugin.encoding>
        <!-- Propiedades plugin: tomcat-maven-plugin -->
        <tomcat_maven_plugin.deploy_user>admin</tomcat_maven_plugin.deploy_user>
        <tomcat_maven_plugin.deploy_password>admin</tomcat_maven_plugin.deploy_password>
        <tomcat_maven_plugin.sourceEncoding>UTF-8</tomcat_maven_plugin.sourceEncoding>
        <tomcat_maven_plugin.server_port>localhost:8080</tomcat_maven_plugin.server_port>
        <!-- Propiedades del modulo cxf-web -->
        <proyecto.web.final_name>cxf-web</proyecto.web.final_name>
        <!-- Propiedades del modulo cxf-client -->
        <proyecto.cliente.final_name>cxf-client</proyecto.cliente.final_name>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>
    <modules>
        <module>cxf-web</module>
        <module>cxf-client</module>
    </modules>
</project>


Módulo cxf-web

1) El fichero pom.xml de este módulo sería:

<?xml version="1.0"?>
<project
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

    http://maven.apache.org/xsd/maven-4.0.0.xsd"
    xmlns="http://maven.apache.org/POM/4.0.0" 

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>cxf</artifactId>
        <groupId>es.org.sgtic</groupId>
        <version>1.0</version>
    </parent>
    <groupId>es.org.sgtic</groupId>
    <artifactId>cxf-web</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>es.org.sgtic.lib.spring.2.5.0.j2ee</groupId>
            <artifactId>el-api</artifactId>
            <version>1.5.0_06</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>es.org.sgtic.lib.tomcat6</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-rm</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-security</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-addr</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-policy</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
            <version>2.5.6</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>*.properties</exclude>
                    <exclude>applicationContext*.xml</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.properties</include>
                    <include>applicationContext*.xml</include>
                </includes>
            </resource>
        </resources>
        <finalName>${proyecto.web.final_name}</finalName>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>tomcat-maven-plugin</artifactId>
                <version>2.1-alpha-1</version>
                <configuration>
                    <warFile>${project.build.directory}/${project.build.finalName}.war</warFile>
                    <update>true</update>
                    <path>/${proyecto.web.final_name}</path>
                    <charset>${tomcat_maven_plugin.sourceEncoding}</charset>
                    <url>http://${tomcat_maven_plugin.server_port}/manager</url>
                    <username>${tomcat_maven_plugin.deploy_user}</username>
                    <password>${tomcat_maven_plugin.deploy_password}</password>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


2) La interfaz de nuestro servicio web, en este caso una calculadora muy simple, sería:

package es.org.cxf.ws.impl;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService(name = "Calculadora", targetNamespace = "http://impl.ws.cxf.org.es/")
public interface Calculadora {

    @WebMethod(operationName = "suma", action = "urn:Suma")
    public int suma(int x, int y);

    @WebMethod(operationName = "resta", action = "urn:Resta")
    public int resta(int x, int y);

    @WebMethod(operationName = "multiplica", action = "urn:Multiplica")
    public int multiplica(int x, int y);

    @WebMethod(operationName = "divide", action = "urn:Divide")
    public int divide(int x, int y);

}


3) La implementación de la calculadora sería:

package es.org.cxf.ws.impl;

import javax.jws.WebService;

@WebService(targetNamespace = "http://impl.ws.cxf.org.es/", endpointInterface = "es.org.cxf.ws.impl.Calculadora", portName = "CalculadoraImplPort", serviceName = "CalculadoraImplService")
public class CalculadoraImpl implements Calculadora {
   
    public int suma(int x, int y){
        return x+y;
    }
    public int resta(int x, int y){
        return x-y;
    }
    public int multiplica(int x, int y){
        return x*y;
    }
    public int divide(int x, int y){
        return x/y;
    }
}


4) La clase que devolverá la password de acceso al certificado usado para firmar los envíos del ws y para desencriptar los datos de las peticiones recibidas desde los clientes sería:

package es.org.cxf.ws.impl;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class PasswordCallback implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        final WSPasswordCallback pc = (WSPasswordCallback)callbacks[0];
        pc.setPassword("password");
    }
}

5) Las clases de las peticiones y respuestas de cada uno de los métodos ofertados por el servicio web serían idénticas a las de la operación suma que adjuntamos:

package es.org.cxf.ws.impl.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "suma", namespace = "http://impl.ws.cxf.org.es/")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "suma", namespace = "http://impl.ws.cxf.org.es/", propOrder = {"arg0", "arg1"})
public class Suma {

    @XmlElement(name = "arg0")
    private int arg0;
    @XmlElement(name = "arg1")
    private int arg1;

    public int getArg0() {
        return this.arg0;
    }
    public void setArg0(int newArg0)  {
        this.arg0 = newArg0;
    }
    public int getArg1() {
        return this.arg1;
    }
    public void setArg1(int newArg1)  {
        this.arg1 = newArg1;
    }
}


package es.org.cxf.ws.impl.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
 

@XmlRootElement(name = "sumaResponse", namespace = "http://impl.ws.cxf.org.es/")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "sumaResponse", namespace = "http://impl.ws.cxf.org.es/")
public class SumaResponse {

    @XmlElement(name = "return")
    private int _return;
    public int getReturn() {
        return this._return;
    }
    public void setReturn(int new_return)  {
        this._return = new_return;
    }
}


6) El applicationContext.xml sería:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

    <bean id="firmaPeticion" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos la verificación de la firma de las peticiones que nos llegan -->
                <entry key="signaturePropFile" value="props/server-trust-store.properties" />
                <!-- Ajustamos el desencriptado de los datos de la petición que recibimos -->
                <entry key="decryptionPropFile" value="props/server-key-store.properties" />
                <entry key="passwordCallbackClass" value="es.org.cxf.ws.sec.PasswordCallback" />
            </map>
        </constructor-arg>
    </bean>

    <bean id="firmaRespuesta" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <entry key="action" value="Timestamp Signature Encrypt" />
                <!-- Ajustamos el firmado de las respuestas -->
                <entry key="signaturePropFile" value="props/server-key-store.properties" />
                <entry key="user" value="desarrollo-internet" />
                <!-- Ajustamos el encriptado de las respuestas -->
                <entry key="encryptionPropFile" value="props/server-trust-store.properties" />
                <entry key="encryptionUser" value="edugarci" />
                <entry key="signatureKeyIdentifier" value="DirectReference" />
                <entry key="passwordCallbackClass" value="es.org.cxf.ws.sec.PasswordCallback" />
                <entry key="signatureParts" value="{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body" />
                <entry key="encryptionParts" value="{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body" />
                <entry key="encryptionKeyTransportAlgorithm" value="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
                <entry key="encryptionSymAlgorithm" value="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
            </map>
        </constructor-arg>
    </bean>

    <jaxws:endpoint xmlns:tns="http://impl.ws.cxf.org.es/"
        id="calculadora" implementor="es.org.cxf.ws.impl.CalculadoraImpl"
        wsdlLocation="wsdl/calculadoraimpl.wsdl" endpointName="tns:CalculadoraImplPort"
        serviceName="tns:CalculadoraImplService" address="/CalculadoraImplPort">

        <jaxws:outInterceptors>
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
            <ref bean="firmaRespuesta" />
        </jaxws:outInterceptors>
        <jaxws:inInterceptors>
            <ref bean="firmaPeticion" />
            <bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
        </jaxws:inInterceptors>
       
    </jaxws:endpoint>
</beans>


7) El log4j.properties sería:

log4j.rootCategory=INFO,CONSOLA

log4j.logger.es.org.cxf.ws=INFO

org.apache.cxf.interceptor.LoggingInInterceptor.level=INFO
org.apache.cxf.interceptor.LoggingOutInterceptor.level=INFO
org.apache.cxf.ws.rm.security.wss4j.WSS4JInInterceptor.level=INFO
org.apache.cxf.ws.rm.security.wss4j.WSS4JOutInterceptor.level=INFO

log4j.appender.CONSOLA=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLA.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLA.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.FICHERO=org.apache.log4j.RollingFileAppender
log4j.appender.FICHERO.file=${log4j.debug.ruta_fichero}/cxf-web.log
log4j.appender.FICHERO.append=true
log4j.appender.FICHERO.MaxFileSize=${log4j.debug.MaxFileSize}
log4j.appender.FICHERO.MaxBackupIndex=${log4j.debug.MaxBackupIndex}
log4j.appender.FICHERO.layout=org.apache.log4j.PatternLayout
log4j.appender.FICHERO.layout.ConversionPattern=[%d{dd-MM-yyyy HH\:mm\:ss,SSS}] thread\:[%t] nivelTraza\:[%p] lugarTraza\:[%c] mensajeTraza[%m]%n


8) El web.xml sería:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>test-cxf-web</display-name>
  <servlet>
    <description>Apache CXF Endpoint</description>
    <display-name>cxf</display-name>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>60</session-timeout>
  </session-config>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>


9) El keystore sería un fichero llamado server-keystore.jks donde se ha generado un certificado llamado "desarrollo-internet". Dejamos el fichero en la carpeta src/main/resources/keystore/server-keystore.jks

10) Precisamos de un fichero server-key-store.properties donde alojar información sobre el server-keystore.jks. Este fichero estará alojado en src/main/resources/props/server-key-store.properties. El contenido del mismo sería:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=password
org.apache.ws.security.crypto.merlin.keystore.alias=desarrollo-internet
org.apache.ws.security.crypto.merlin.keystore.file=keystore/server-keystore.jks


11) El truststore sería un fichero llamado server-truststore.jks donde se ha almacenado la clave pública del cliente que nos enviará peticiones firmadas. La clave pública de este certificado la llamamos "edugarci". Dejamos el fichero en la carpeta src/main/resources/keystore/server-truststore.jks

12) Precisamos de un fichero server-trust-store.properties donde alojar información sobre el server-truststore.jks. Este fichero estará alojado en src/main/resources/props/server-trust-store.properties. El contenido del mismo sería:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=password
org.apache.ws.security.crypto.merlin.keystore.alias=edugarci
org.apache.ws.security.crypto.merlin.keystore.file=keystore/server-truststore.jks




Módulo jar cxf-client

1) El fichero pom.xml de este módulo sería:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 

    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>cxf</artifactId>
        <groupId>es.org.sgtic</groupId>
        <version>1.0</version>
    </parent>
    <groupId>es.org.sgtic</groupId>
    <artifactId>cxf-client</artifactId>
    <version>1.0</version>
    <dependencies>
        <dependency>
            <groupId>org.apache.ws.security</groupId>
            <artifactId>wss4j</artifactId>
            <version>1.6.7</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-rm</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-security</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-addr</artifactId>
            <version>2.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-ws-policy</artifactId>
            <version>2.6.3</version>
        </dependency>
    </dependencies>
</project>


2) El wsdl del servicio web a atacar estará publicado en la URL:

http://localhost:8080/cxf-web/services/CalculadoraImplPort

3) El contenido del wsdl sería:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="CalculadoraImplService" targetNamespace="http://impl.ws.cxf.org.es/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://impl.ws.cxf.
org.es/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
  <wsdl:types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://impl.ws.cxf.org.es/" schemaLocation="calculadoraimpl_schema1.xsd"/>
</schema>
  </wsdl:types>
  <wsdl:message name="restaResponse">
    <wsdl:part name="parameters" element="tns:restaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="multiplica">
    <wsdl:part name="parameters" element="tns:multiplica">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="suma">
    <wsdl:part name="parameters" element="tns:suma">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="resta">
    <wsdl:part name="parameters" element="tns:resta">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="divide">
    <wsdl:part name="parameters" element="tns:divide">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="multiplicaResponse">
    <wsdl:part name="parameters" element="tns:multiplicaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="sumaResponse">
    <wsdl:part name="parameters" element="tns:sumaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="divideResponse">
    <wsdl:part name="parameters" element="tns:divideResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="Calculadora">
    <wsdl:operation name="resta">
      <wsdl:input name="resta" message="tns:resta">
    </wsdl:input>
      <wsdl:output name="restaResponse" message="tns:restaResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="divide">
      <wsdl:input name="divide" message="tns:divide">
    </wsdl:input>
      <wsdl:output name="divideResponse" message="tns:divideResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="suma">
      <wsdl:input name="suma" message="tns:suma">
    </wsdl:input>
      <wsdl:output name="sumaResponse" message="tns:sumaResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="multiplica">
      <wsdl:input name="multiplica" message="tns:multiplica">
    </wsdl:input>
      <wsdl:output name="multiplicaResponse" message="tns:multiplicaResponse">
    </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="CalculadoraImplServiceSoapBinding" type="tns:Calculadora">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="resta">
      <soap:operation soapAction="urn:Resta" style="document"/>
      <wsdl:input name="resta">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="restaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="divide">
      <soap:operation soapAction="urn:Divide" style="document"/>
      <wsdl:input name="divide">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="divideResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="suma">
      <soap:operation soapAction="urn:Suma" style="document"/>
      <wsdl:input name="suma">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="sumaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="multiplica">
      <soap:operation soapAction="urn:Multiplica" style="document"/>
      <wsdl:input name="multiplica">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="multiplicaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="CalculadoraImplService">
    <wsdl:port name="CalculadoraImplPort" binding="tns:CalculadoraImplServiceSoapBinding">
      <soap:address location="http://localhost:8080/cxf-web/services/CalculadoraImplPort"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>


4) Las clases del cliente se generarían a partir del wsdl mediante el asistente incorporado en Eclipse Helios. Hay una peculiaridad y es que para que el asistente genere las clases necesita trabajar sobre un proyecto web, así pues montamos un proyecto web (servlets 2.5) vacío, copiamos el fichero wsdl a este proyecto y haciendo clic sobre el con el botón derecho del ratón seleccionamos la opción "Web Services/Generate Client". En el asistente que se lanza definimos que el "server runtime" sea Tomcat y el "webservice runtime" sea Apache CXF.

5) Las clases generadas las moveremos al proyecto cxf-client y ya podríamos borrar el proyecto web comodín. El contenido de estas clases excede el objetivo del blog, pero la lista de clases sería:

CalculadoraImpl.java
Multiplica.java 
RestaResponse.java
CalculadoraImplService.java

MultiplicaResponse.java
Suma.java
Calculadora.java

ObjectFactory.java
SumaResponse.java
Divide.java

package-info.java
DivideResponse.java

Resta.java

6) La clase que devolverá la password de acceso al certificado usado por el cliente para firmar los envíos al ws y para desencriptar las respuestas recibidas desde el ws sería:

package es.org.cxf.ws.sec;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class PasswordCallback implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        //Password del certificado "edugarci" de "client-keystore.jks" se se
        //usa para firmar los envíos del cliente y desencriptar los mensajes
        //que le llegan cifrados desde el servidor con la clave publica de "edugarci"
        final WSPasswordCallback pc = (WSPasswordCallback)callbacks[0];
        pc.setPassword("password");
    }
}

7) La clase ejemplo donde se implementa mediante el API todo el tema de cifrado y firmado del lado del cliente sería:

package es.ins.sgtic.app;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

import es.org.cxf.ws.impl.Calculadora;
import es.org.cxf.ws.impl.CalculadoraImplService;

public class AppTest {

    private static final QName SERVICE_NAME = new QName("http://impl.ws.cxf.org.es/", "CalculadoraImplService");
   
    public static void main(String[] args) {

        //Definimos el servicio web a usar
        final URL wsdlURL = CalculadoraImplService.WSDL_LOCATION;
        final CalculadoraImplService calcService = new CalculadoraImplService(wsdlURL, SERVICE_NAME);
        final Calculadora calculadora = calcService.getCalculadoraImplPort();
               
        //Obtenemos la referencia al endpoint del ws
        final Client client = ClientProxy.getClient(calculadora);
        final Endpoint cxfEndpoint = client.getEndpoint();
       
        //Configuramos el interceptor de cifrado/firmado para los envíos
        final Map<String,Object> outProps = new HashMap<String,Object>();
        outProps.put("action", "Timestamp Signature Encrypt");
        outProps.put("user", "edugarci");
        outProps.put("signaturePropFile", "props/client-key-store.properties");
        outProps.put("signatureKeyIdentifier", "DirectReference");
        outProps.put("passwordCallbackClass", "es.org.cxf.ws.sec.PasswordCallback");
        outProps.put("signatureParts", "{Element}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body");
        outProps.put("encryptionPropFile", "props/client-trust-store.properties");
        outProps.put("encryptionUser", "desarrollo-internet");
        outProps.put("encryptionParts", "{Element}{http://www.w3.org/2000/09/xmldsig#}Signature;{Content}{http://schemas.xmlsoap.org/soap/envelope/}Body");
        outProps.put("encryptionSymAlgorithm", "http://www.w3.org/2001/04/xmlenc#tripledes-cbc");
        outProps.put("encryptionKeyTransportAlgorithm", "http://www.w3.org/2001/04/xmlenc#rsa-1_5");
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps); //request
        cxfEndpoint.getOutInterceptors().add(wssOut);
        cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());
       
        //Configuramos el interceptor de descifrado/verificacion-firma para las recepciones
        Map<String,Object> inProps= new HashMap<String,Object>();
        inProps.put("action", "Timestamp Signature Encrypt");
        inProps.put("signaturePropFile", "props/client-trust-store.properties");
        inProps.put("passwordCallbackClass", "es.org.cxf.ws.sec.PasswordCallback");
        inProps.put("decryptionPropFile", "props/client-key-store.properties");
        WSS4JInInterceptor wssIn = new WSS4JInInterceptor(inProps); //response
        cxfEndpoint.getInInterceptors().add(wssIn);
        cxfEndpoint.getInInterceptors().add(new SAAJInInterceptor());
       
        //Lanzamos unas peticiones de ejemplo
        for (int c=0;c<100;c++){
            System.out.println(String.format("port.suma(15, 3) --> %d",calculadora.suma(15, 3)));
            System.out.println(String.format("port.resta(15, 3) --> %d",calculadora.resta(15, 3)));
            System.out.println(String.format("port.multiplica(15, 3) --> %d",calculadora.multiplica(15, 3)));
            System.out.println(String.format("port.divide(15, 3) --> %d",calculadora.divide(15, 3)));
        }
    }
}










martes, 9 de octubre de 2012

Utilización de Jetty 6 en Eclipse Helios

Los pasos dados para la integración de Jetty en Eclipse Helios han sido:

1) Descargar Jetty 6.1.26 desde la URL:

http://dist.codehaus.org/jetty/jetty-6.1.26/jetty-6.1.26.zip

2) Descomprimirlo en el disco duro. Como usualmente se manejarán varias versiones de distintos servidores (jBoss, Tomcat, Jetty, ...) es conveniente descomprimirlo en una carpeta paralela a las de los otros servirores que utilicemos.

3) Si ya tenemos instalado el plugin para Jetty, podemos seguir en el punto 6), si no lo tenemos hay que instalarlo.

4) En el asistente de instalación deberemos añadir el plugin para Jetty (si no lo teníamos añadido antes), para ello pulsamos en enlace "Download additional server adapters". Si tenemos adecuadamente configurada la conexión a internet se cargará una lista de plugin, deberemos seleccionar el plugin "Jetty Generic Server Adaptor", pulsamos Next, aceptamos la licencia y pulsamos finalizar. Con esto se inicia el proceso de instalación del plugin. Tras la instalación del mismo nos pide reiniciar Eclipse.

5) Tras reiniciar Eclipse Helios, vamos a definir un Jetty en nuestro workspace, para ello deberemos ir de nuevo a la ventana Servers y pulsar son el botón derecho New/Server/Jetty/Jetty 6, nos pedirá que indiquemos al Eclipse la ruta de instalación del Jetty en nuestro sistema, esto es la ruta del directorio donde está instalado el fichero start.jar, si la instalación es la de por defecto debería estar en /usr/share/jetty. Pulsamos Next, Next y Finish
 
6) Abrimos la vista de servidores ("Windows/Show View/Other/Servers").

7) Sobre la ventana de servidores, hacemos clic con el botón derecho del ratón, pulsamos New/Server y seleccionamos la opción Jetty 6. Lo único especial que se nos solicitará el path del directorio de instalación. Algo así:

/home/xxxxxxx/jetty/jetty-6.1.26/

8) Para ver que todo está bien, desde Eclipse levantamos el servidor, y desde un navegador accederemos a la URL:

http://localhost:8080

9) Montamos un proyecto con uno de nuestros arquetipos, por ejemplo el de JSF2MojarraHibernate, y lo desplegamos sin problemas.

domingo, 16 de septiembre de 2012

Primeros pasos con Gnome Shell

Gnome Shell  es el gestor de ventanas de la versión 3 de Gnome.

Tras instalar Ubuntu 12.04 te encuentras con Unity, que nunca me ha gustado, por lo que tras volver a la versión 2 de Gnome (Gnome Panel), he decidido probar Gnome Shell.

Por el momento no tiene mala pinta, pero he tenido un par de dificultades que, si bien he resuelto, parece que indican que este gestor de ventanas tampoco va a ser intuitivo.

Ajuste de la resolución en Gnome Shell

Tras cerrar cierto juego a pantalla completa con una resolución distinta de la configurada por defecto en el gestor de ventanas, me he encontrado con una resolución de 800x600, por lo que no podía acceder a la configuración del monitor (en la configuración del sistema). Googleando he localizado un comando que permite ajustar la resolución sin problemas:

xrandr -s 1280x1024

Añadir a favoritos un ejecutable en Gnome Shell (dificil)

Para añadir al panel de favoritos una aplicación, ésta debe tener previamente un lanzador, dado que sin lanzador, la aplicación no aparece en la lista de aplicaciones y no he visto ninguna forma de buscar nuevas aplicaciones en esta lista. La forma más cómoda de definir un lanzador es instalar alacarte, que es el asistente de configuración del menú en Gnome 2:

sudo apt-get install alacarte

Después se ejecuta tecleando alacarte y se configura el lanzador en la categoría deseada.

Añadir a favoritos un ejecutable en Gnome Shell (fácil)

Otra forma mucho más simple de añadir aplicaciones al panel de favoritos es hacer permanente el icono "temporal" de la aplicación que aparece en el panel mientras esté abierta la aplicación. Para hacerlo debemos hacer clic con el botón derecho del ratón y seleccionar la opción "Añadir a los favoritos".

Eliminar acceso directo de la barra de favoritos en Gnome Shell

Para eliminar los accesos directos de la barra de tareas de Gnome3 (lo instale en Ubuntu 12.04 porque no me gustaba Unity) tenemos que pulsar con el botón derecho del ratón y, en el menú contextual, seleccionar la opción "Quitar de los favoritos".

Apagado rápido

En el menú de la derecha, donde está la configuración del sistema, tenemos la opción de  cerrar sesión y la de Suspender. La cuestión es que se echa de menos la opción de Apagar. Siempre habrá quien argumente que al fin y al cabo, suspendiendo el sistema tenemos un apagado mejorado que permite reiniciar el ordenador con las aplicaciones que ya teníamos abiertas, pero si queremos apagar completamente no hay una la opción directa. La solución es pulsar la tecla ALT mientras tenemos desplegado el menú, con lo que Suspender pasa a ser Apagar.

Abrir una segunda instancia de una aplicación

Cuando queremos abrir una segunda instancia de una aplicación, deberemos hacer clic con el botón derecho del ratón sobre el icono de la aplicación en la barra de favoritos y, en el menú contextual que aparece, seleccionar la opción "Ventana nueva". Un atajo es mantener pulsada la tecla CTRL mientras se hace clic con el botón izquierdo del ratón.

Minimizar todas las ventanas

Para minimizar todas las ventanas hay un atajo de teclado: CTRL+ALT+D.
Otra opción es: CTRL+ALT+SUPER (siendo SUPER la tecla con el icono de Windows). De todos modos todos éstos atajos son configurables en el panel de control/teclado/atajos.

Reajustar los atajos de teclado

Para reajustar los atajos de teclado deberemos acceder a la pantalla de configuración del sistema, llegamos a ella haciendo clic con el botón derecho del ratón sobre nuestro nombre de usuario en la parte superior derecha de la pantalla, en la ventana de configuración del sistema hacemos clic sobre la opción Teclado y en la pestaña Atajos tenemos todas las opciones.

Ajustar los botones minimizar, maximizar y cerrar en las ventanas

Para reajustar la ubicación de los botones minimizar, maximizar y cerrar en las ventanas, deberemos usar gconf-editor.
  1. Para instalarlo, si es que no está ya instalado: sudo apt-get install gconf-editor
  2. Para lanzarlo: sudo gconf-editor
  3. Si por ejemplo queremos ajustar la ubicación de los botones minimizar, maximizar y cerrar en las ventanas buscamos la variable: apps->metacity->general->button layout y ajustamos su valor a: ":minimize,maximize,close" (sin las comillas y con los dos puntos a la izquierda).

martes, 11 de septiembre de 2012

java.lang.OutOfMemoryError: PermGen space failure

Memoria en la máquina virtual de Java

La memoria de la Máquina Virtual se divide en varias regiones. Una de estas regiones es el PermGen, el área de memoria utilizada para, entre otras cosas, guardar el metadata de las clases como los atributos y sus tipos de datos, métodos etc. Esta memoria es de tipo non-heap. Las instancias de las clases se cargan en la memoria de tipo heap, a la que se van añadiendo y eliminando las instancias de las clases según se van utilizando y eliminándose por el recolector de basura (Garbage Collector, en adelante GC).

No vamos a entrar en el mecanismo de la carga y eliminación de clases en java porque excede este artículo, pero sí lo comentaremos brevemente para poder entender el porqué de la excepción OutOfMemoryException: para que una clase pueda ser eliminada por el recolector de basura (GC), es necesario que no esté referenciada por ninguna otra clase. Para que se pueda eliminar la memoria ocupada por una clase en el espacio PermGen, es necesario además que se elimine el classloader que cargó la clase.

El valor por defecto del espacio PermGen es de 64 Mb en la máquina virtual (VM) de Sun. Este valor es suficiente normalmente para las aplicaciones que corren de forma independiente. En el caso de una aplicación que corra en un servidor web tipo Tomcat o un servidor de aplicaciones, hay casos en los que este valor no es suficiente.

En primer lugar, es posible que la aplicación necesite más espacio por su propia arquitectura. Las librerías Spring e Hibernate son librerías grandes, que cargan muchas clases y además hacen uso de proxies y carga dinámica de clases que hacen uso del espacio PermGen por lo que puede que los 64Mb no sean suficientes. Este caso se da cuando la excepción con el error se produce nada mas arrancar el servidor web o de aplicaciones o al acceder a la aplicación. Para solventar este problema bastará con aumentar el tamaño máximo de la memoria de tipo PermGen según veremos más adelante.

En segundo lugar, la causa más probable de una excepción java.lang.OutOfMemoryError: PermGen space failure se produce cuando la aplicación se reinstala en el servidor sin reiniciar el servidor. La forma de reinstalar las aplicaciones es eliminando el classloader que cargó la aplicación por primera vez y creando un nuevo classloader para la nueva instancia de la aplicación. En teoría, esto proporciona una forma limpia de reinstalar aplicaciones, pero si el servidor web o de aplicaciones guarda una referencia al classloader antiguo, se produce una fuga de memoria (memory leak) y el resultado es que tendremos las clases de la aplicación cargadas dos veces en memoria. Es cuestión del número de recargas y de la memoria gastada por la aplicación el que se produzca la excepción  java.lang.OutOfMemoryError: PermGen space failure. En este caso no basta con aumentar el tamaño máximo de la memoria, ya que esto sólo retrasaría el problema a unas cuantas reinstalaciones más. En este caso la única solución consiste en averiguar la causa de la fuga de memoria. Esto también excede el contenido de este artículo, aunque lo intentarmos tratar con profundidad en otra ocasión. Por el momento sólo proporcionamos un enlace muy interesante, aunque en inglés, acerca de posibles causas de fugas de memoria en java: http://opensource.atlassian.com/confluence/spring/pages/viewpage.action?pageId=2669

Ajuste de la memoria de la máquina virtual Java

En Java, hay varios parámetros para controlar el tamaño inicial y el tamaño máximo que puede tomar la máquina virtual.

Además, hay que tener en cuenta el tipo de memoria sobre el que se quiere actuar: heap, non-heap (PermGen). Los valores por defecto que se dan corresponden a máquina virtuales de Sun, y son orientativos, porque pueden cambiar entre versiones de la máquina virtual. Los parámetros son los siguientes, fijándose a 128Mb.
  • -Xms128m: Tamaño de inicio de la máquina virtual de Java a 128Mb. El valor por defecto son 64Mb. Si se aumenta este valor, se elimina el tiempo que se tardaría en aumentar el tamaño en memoria de la máquina virtual si se llegara el caso de que se necesitara más memoria, por lo que aumentaría el rendimiento en los casos que la aplicación haga uso intensivo de la memoria.
  • -Xmx128m: Tamaño máximo de la máquina virtual de Java a 128Mb.  El valor por defecto son 128Mb. Si la aplicación supera el tamaño máximo de memoria que marca este parámetro, se lanza la excepción java.lang.OutOfMemoryError.  No conviene asignar a este parámetro el máximo de la memoria de la máquina porque si ya no queda memoria física disponible (por la que usa el sistema operativo u otras aplicaciones) se pueden producir escrituras en memoria asignada a otros programas y provocar un auténtico lío.
  • -XX:PermSize=128m: Tamaño de inicio de la memoria de tipo PermGen a 128Mb. Arrancar la máquina virtual con un valor superior al por defecto agiliza la carga de aplicaciones, sobre todo en el caso de aplicaciones que hagan uso intensivo de este tipo de memoria (Spring, Hibernate…)
  • -XX:MaxPermSize=128m: Tamaño máximo de la memoria de tipo PermGen a 128Mb. El valor por defecto son 64Mb. Si la aplicación supera el tamaño máximo de memoria para este tipo que marca este parámetro, se lanza la excepción java.lang.OutOfMemoryError: PermGen space. El valor necesario para este parámetro siempre suele ser menor que el de la memoria de tipo heap.
Si se quiere especificar un valor distinto de 128Mb, que se utiliza para todos los parámetros como ejemplo para simplificar, bastaría con sustituir el valor 128 del parámetro con el que se desee, siempre que sean múltiplos de 2 (64, 128, 256, 512, 768, 1024, 2048…)

Conocidos los tipos de memoria y los parámetros que los controlan, ahora viene la pregunta, ¿cómo especifico estos valores para mi aplicación? La respuesta es la misma para todos los casos: Los valores se especifican como parámetros en el arranque de la máquina virtual que ejecutará la aplicación. La diferencia estribará en cómo se arranca la aplicación: con un script, desde línea de comandos, mediante ant…

Artículos originales en: http://www.programacionenjava.com/blog/tag/outofmemory/