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.