HolaMundo CXF Spring

Revisando las estadísticas de las entradas he visto que una de las que más visitas registra es una sobre la creación de un webservice con CXF. Como quiera que es una entrada muy mejorable y voy a reescribirla y publicar el proyecto.
La idea final es crear un servicio para la aplicación con jsf que desarrollo en otras entradas de este blog que se encargue inicialmente de "almacenar" alguna entidad. En esta fase inicial almacenará en memoria y externamente implementará las operaciones de crear, recuperar y borrar.
Maven, eclipse Indigo, jdk 1.6 y tomcat7 configuran el entorno de trabajo.
Crearé el proyecto con maven desde linea de comandos con:
mvn archetype:generate
Esto me lista todos los arquetipos, si escribo cxf me filtra los apropiados.
de entre todos los filtrados el mejor candidato parece ser el 3:

Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 225: cxf
Choose archetype:
1: remote -> org.apache.cxf:cxf-http-basic (-)
2: remote -> org.apache.cxf.archetype:cxf-jaxrs-service (Simple CXF JAX-RS webapp service using Spring configuration)
3: remote -> org.apache.cxf.archetype:cxf-jaxws-javafirst (Creates a project for developing a Web service starting from
Java code)
4: remote -> org.apache.servicemix.tooling:servicemix-cxf-bc-service-unit (-)

...
Aquí me lista 55, selecciono el último.Tampoco importa mucho cual, al final lo voy a ajustar bastante a mis necesidades.

Y termindo de dar valores al proyecto:
Define value for property 'groupId': : es.sinjava
Define value for property 'artifactId': : crudcxf
Define value for property 'version':  1.0-SNAPSHOT: : 1.0
Define value for property 'package':  es.sinjava: :
Confirm properties configuration:
groupId: es.sinjava
artifactId: crudcxf
version: 1.0
package: es.sinjava
 Y: : Y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: cxf-jaxws-jav
....

------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ....

-------------------------------------

Vamos a importarlo desde el eclipse para empezar a editarlo comodamente.
Import-> Maven -> Existing Maven Proyect.

Sin hacer nada más ya nos lo debería desplegar si vamos a "Run Configurations..." y en el apartado Maven Build ponemos un goal "tomcat:run-war"

Voy a colgar este proyecto para tenerlo como referencia aquí. Por si has tenido algún problema para generarlo con maven..
Momento de comentario personal:  Maven es una herramienta genial para no tener que abordar un proyecto partiendo de una "hoja en blanco". Tener un proyecto con sus carpetas, las dependencias básicas solucionadas y los archivos de configuraciíon cada uno en su sitio ahorra muchas frustraciones en el momento de empezar con un nuevo framework- Pero maven no tiene 8.000 de arquetipos  incluido el proyecto que nosotros estamos buscando y el dia que lo tenga haran/haremos falta muchos menos programadores.

En esta ocasión al contrario que el anterior post vamos a "tunearlo" un poco.
Vamos a empezar por definir unas paquetes para cada capa: es.sinjava.ws y es.sinjava.ws.impl que contendrán las interfaces y las implementaciones y un paquete es.sinjava.model que contendrá el modelo. De momento en el modelo sólo vamos a meter una clase: User.java.

package es.sinjava.model;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private long id;
    private String name;
    private String surname;


//Getter y Setter

Veamos el interfaz del ws:Crud.java.

package es.sinjava.ws;

import java.util.List;
import javax.jws.WebService;
import es.sinjava.model.User;

@WebService
public interface Crud {
   
    long createUser(User current);
    User deleteUser(long idUser);
    User getUser (long idUser);
    List<User> getUsers();
}


 Lo único interesante es la anotación "@WebService" del paquete
javax.jws.WebService. En la paquete "es.sinjava.ws.impl" creamos una clase que implemente los método de este interface. Luego volveremos a esta clase.

Vamos a crear también una capa que tenga la lógica de negocio "es.sinjava.bl" con una clase manager que debería conectar ya con los Dao, pero que de momento, por no complicar más el desarrollo, mantendrá en memoria objetos de tipo User y nos hará las funciones de dao.

La clase en cuestión es esta:

package es.sinjava.bl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import es.sinjava.model.User;

@Component
@Scope ("singleton")
public class ManagerCrud implements ManagerCrudInterface  {
   
    private Map<Long, User> currentBD;
    private final Logger LOG= Logger.getLogger(ManagerCrud.class);
    private Long idUser;


    public ManagerCrud() {
        LOG.trace("Creado el bean del Manager");
        currentBD = new HashMap<Long, User>(0);
        idUser = 0L;
    }

    public Long createUser(User current) {
        idUser++;
        current.setId(idUser);
        currentBD.put(idUser, current);
        return current.getId();
    }

    public User deleteUser(Long idUser) {
        User user = currentBD.remove(idUser);
        return user;
    }

    public User getUser(Long idUser) {
        User user = currentBD.get(idUser);
        return user;
    }


    public List<User> getUsers() {
        List<User> list = new ArrayList<User>(currentBD.values());
        return list;
    }

}

A destacar que la definimos explicitamente como singleton, en vez de declarara la variable como estática.

La implementación del ws será la siguiente:

package es.sinjava.ws.impl;

import java.util.List;

import org.apache.log4j.Logger;

import es.sinjava.bl.ManagerCrudInterface;
import es.sinjava.model.User;
import es.sinjava.ws.Crud;

public class CrudImpl implements Crud {
   
    private ManagerCrudInterface manager;

    private final Logger LOG = Logger.getLogger(CrudImpl.class);

    public CrudImpl() {
        LOG.trace("Creado el WS");
    }

    public Long createUser(User current) {
        LOG.trace("Begin createUser");
        LOG.debug("Voy a crear el usuario" +current.getName()+ " - "+  current.getSurname());
       Long soy= manager.createUser(current);
        return soy;
    }

    public User deleteUser(Long idUser) {
        LOG.trace("Begin deleteUser");
        return manager.deleteUser(idUser);
    }

    public User getUser(Long idUser) {
        LOG.trace("Begin getUser");
        return manager.getUser(idUser);
    }

    public List<User> getUsers() {
        LOG.trace("Begin GetUsers");
        return manager.getUsers();
    }

  .....
}

Que no tiene mucho misterio. Conforme le llega el parámetro lo lanza contra la capa del manager.
Queda por último configurar el web services. En el cual vamos a cambiar el interfaz jaxws por simple:server. Dicho cambio lo vamos a hacer sobre el beans.xml que nos queda 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:context="http://www.springframework.org/schema/context"
    xmlns:simple="http://cxf.apache.org/simple" xmlns:soap="http://cxf.apache.org/bindings/soap"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd
http://cxf.apache.org/simple http://cxf.apache.org/schemas/simple.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <import resource="classpath:META-INF/cxf/cxf.xml" />
    <context:component-scan base-package="es.sinjava.bl" />
    <context:annotation-config />

    <simple:server id="server" serviceClass="es.sinjava.ws.Crud"
        address="/crud/services">
        <simple:serviceBean>
            <bean class="es.sinjava.ws.impl.CrudImpl" autowire="byType" />
        </simple:serviceBean>
    </simple:server>
   
</beans>

Resñar que al definir el serviceBean lo hemos puesto la propiedad autowire a "byType" de forma que nos haga las inyecciones de dependencia buscando un "bean" que se ajuste al tipo que necesite. Puesto que lo hacemos a nivel de bean intentará hacer esto con todas las propiedades que tenga, por tanto es preciso realizar esto con sentido común.

El web.xml apenas ha tenido cambios:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/beans.xml</param-value>
    </context-param>

    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath:/log4j.properties</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <display-name>CXF Servlet</display-name>
        <servlet-class>
            org.apache.cxf.transport.servlet.CXFServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>


Hemos añadido un listener de log4j para tener unos logs y el log4j.properties con la configuración deseada es este:
og4j.rootLogger = DEBUG, stdout, logfile
log4j.category.es.sinjava= DEBUG
log4j.category.org.apache= WARN
log4j.category.org.springframework=WARN


log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Threshold = DEBUG
log4j.appender.stdout.Target   = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %5p | %c | %M | %L | %m%n

log4j.appender.logfile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File = /logs/crudws.log
log4j.appender.logfile.append = true
log4j.appender.logfile.DatePattern = '.'yyyy-MM-dd'.log'
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %5p | %d{yyyy-MM-dd HH:mm:ss} | %c | %M | %L | %m%n


Sin más cambios este proyecto debería compilar y desplegar ofreciéndonos unos métodos apropiados.
El proyecto completo lo tienes en este enlace.

En el pom he realizado algunos cambios, he añadido una dependecia:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.4</version>
        </dependency>


Y he configurado el plugin del tomcat para que despliegue en mi tomcat.

       <groupId>org.codehaus.mojo</groupId>
       <artifactId>tomcat-maven-plugin</artifactId>
       <configuration>
             <server>winxpwork</server>
             <username>tomcat</username>
             <password>tomcat</password>
             <url>http://192.168.2.55:8080/manager/html</url>
       </configuration>


Al igual que en el ejemplo anterior puede ser más que recomendable utilizar una herramienta como soapUi, para comprobar que el ws funciona correctamente. Es bastante trivial su empleo: crear un proyecto e indicarle la url del wsdl en nuestro caso: http://localhost:8080/crudcxf/crud/services?wsdlAutomáticamente nos creará unas consultas apropiadas para probar los cuatro métodos.

En cualquiera de los casos una vez desplegada al aplicación, en http://localhost:8080/crudcxf/ deberíamos ver algo como esto en el navegador:

Available SOAP services:
CrudPortType
  • createUser
  • deleteUser
  • getUsers
  • getUser
Endpoint address: http://localhost:8080/crudcxf/crud/services
WSDL : {http://ws.sinjava.es/}Crud
Target namespace: http://ws.sinjava.es/

Available RESTful services:


En proxima entregas engancharemos con este ws desde el proyecto jsf, meteremos JPA para crear una capa de persistencia como es debido y sobre todo complementaré este proyecto con las omisiones que se detecten.






Comentarios