Primeros pasos con Apache CXF

2
53759

Primeros pasos con Apache CXF

0. Índice de contenidos.

1. Entorno

Este tutorial está desarrollado usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 2Ghz Intel Core i7 (4 núcleos) 8Gb de RAM
  • Sistema Operativo: Mac OS X 10.7.2 (Lion)
  • Eclipse Indigo (Revisar tutorial de Alex para su instalación)
  • Apache Tomcat 7 (instalado e integrado en eclipse)
  • Versión de java SDK 6 instalada en el sistema

2. Introducción

En este tutorial veremos como realizar servicios con Apache CXF basados en JAX-WS (http://en.wikipedia.org/wiki/JAX-WS) utilizando las WST de eclipse.

Apache CXF es un framework de Web Services que nace de la combinación de dos proyectos open source Celtix y XFire. Sus principales caracteristicas son (sacado de la wikipedia):

  • Web Services Standards
    • SOAP
    • WS-Addressing
    • WS-Policy
    • WS-ReliableMessaging
    • WS-SecureConversation
    • WS-Security
    • WS-SecurityPolicy
  • JAX-WS API for Web service development
    • Java first support
    • WSDL first tooling
  • JAX-RS (JSR 311 1.1) API for RESTful Web service development
  • JavaScript programming model for service and client development
  • Maven tooling
  • CORBA support
  • HTTP and JMS transport layers
  • Embeddable Deployment:
    • ServiceMix or other JBI containers
    • Geronimo or other Java EE containers
    • Tomcat or other servlet containers
    • OSGi
  • Reference OSGi Remote Services implementation

3. Configuración de eclipse

Primero de todo debemos configurar eclipse con la versión de CXF que vayamos a utilizar:

Configuración de eclipse

Configuración de eclipse

Configuración de eclipse

Configuración de eclipse

Una vez configurado podremos utilizarlo en el asistente de creación de webservices.

4. Aproximaciones. Code First

En esta primera aproximación partimos de una clase que expondrá los métodos que le especifiquemos explicitamente en el webservice. Para ello creamos un nuevo proyecto java y codificamos una interfaz (a la que llamaremos SEI) con su implementación, y una entidad Persona. Para la persistencia utlizaremos un HashMap que almacenará las personas:

Creación de proyecto java

Creación de proyecto java

Creación de proyecto java

Creación de proyecto java

Entidad:

package com.autentia.cxf;

import java.io.Serializable;

/**
 * @author cleon
 *
 */
public class Persona implements Serializable{
	private static final long serialVersionUID = 1677577045608086848L;
	private String nombre;
	private String apellidos;
	public Persona() {
	}
	/**
	 * @return the nombre
	 */
	public String getNombre() {
		return nombre;
	}
	/**
	 * @param nombre the nombre to set
	 */
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	/**
	 * @return the apellidos
	 */
	public String getApellidos() {
		return apellidos;
	}
	/**
	 * @param apellidos the apellidos to set
	 */
	public void setApellidos(String apellidos) {
		this.apellidos = apellidos;
	}
	
}

Singleton que mantiene la persistencia de las personas:

package com.autentia.cxf;

import java.util.HashMap;
import java.util.Map;

/**
 * @author cleon
 *
 */
public class Singleton {
	private static final Singleton instance = new Singleton();
	private Map<String,Persona> cuentas = new HashMap<String, Persona>();
	private Singleton(){}
	public static Singleton getInstance() {
        return instance;
	}
	public Map<String,Persona> getPersonas(){
		return cuentas;
	}
}

Interfaz:

package com.autentia.cxf;

import java.util.List;
/**
 * @author cleon
 *
 */
public interface PersonasSEI {
	/**
	 * Añade una persona al listado
	 * @param persona
	 */
	void addPersona(Persona persona);
	/**
	 * Devuelve una persona y en caso de no encontrarla lanza una excepcion
	 * @param nombre
	 * @return
	 * @throws Exception
	 */
	Persona getPersona(String nombre) throws Exception;
	/**
	 * Obtiene el listado de personas almacenado
	 * @return
	 */
	List<Persona> getPersonas();
	/**
	 * Elimina una persona del listado y si no existe lanza una excepcion
	 * @param nombre
	 * @throws Exception
	 */
	void removePersona(String nombre) throws Exception;
}

Implementación:

package com.autentia.cxf;

import java.util.ArrayList;
import java.util.List;

/**
 * @author cleon
 *
 */
public class PersonasServiceImpl implements PersonasSEI {

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#addPersona(com.autentia.cxf.Persona)
	 */
	@Override
	public void addPersona(Persona persona) {
		Singleton.getInstance().getPersonas().put(persona.getNombre(), persona);
	}

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#getPersona(java.lang.String)
	 */
	@Override
	public Persona getPersona(String nombre) throws Exception {
		Persona persona = Singleton.getInstance().getPersonas().get(nombre);
		if (persona==null){
			throw new Exception("La persona buscada no existe");
		}
		return persona;
	}

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#getPersonas()
	 */
	@Override
	public List<Persona> getPersonas() {
		return new ArrayList<Persona>(Singleton.getInstance().getPersonas().values());
	}

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#removePersona(java.lang.String)
	 */
	@Override
	public void removePersona(String nombre) throws Exception {
		Persona persona = Singleton.getInstance().getPersonas().get(nombre);
		if (persona==null){
			throw new Exception("La persona buscada no existe");
		}else{
			Singleton.getInstance().getPersonas().remove(nombre);
		}

	}

}

Una vez definidas las clases, hacemos click derecho sobre la implementación y webservices –> createwebservice y seguimos los siguientes pasos:

Creación webservice

Creación webservice

Creación webservice

Creación webservice

Creación webservice

Creación webservice

Creación webservice

Creación webservice

Lo primero que vemos es que nos ha anotado la clase que implementaba el servicio con las anotaciones de JAX-WS, y adicionalmente se han creado los tipos de request-response para cada una de las operaciones:

package com.autentia.cxf;

import java.util.ArrayList;
import java.util.List;

import javax.jws.WebService;

/**
 * @author cleon
 *
 */
@WebService(targetNamespace = "http://cxf.autentia.com/", portName = "PersonasServiceImplPort", serviceName = "PersonasServiceImplService")
public class PersonasServiceImpl implements PersonasSEI {

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#addPersona(com.autentia.cxf.Persona)
	 */
	@Override
	public void addPersona(Persona persona) {
		Singleton.getInstance().getPersonas().put(persona.getNombre(), persona);
	}

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#getPersona(java.lang.String)
	 */
	@Override
	public Persona getPersona(String nombre) throws Exception {
		Persona persona = Singleton.getInstance().getPersonas().get(nombre);
		if (persona==null){
			throw new Exception("La persona buscada no existe");
		}
		return persona;
	}

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#getPersonas()
	 */
	@Override
	public List<Persona> getPersonas() {
		return new ArrayList<Persona>(Singleton.getInstance().getPersonas().values());
	}

	/* (non-Javadoc)
	 * @see com.autentia.cxf.PersonaSEI#removePersona(java.lang.String)
	 */
	@Override
	public void removePersona(String nombre) throws Exception {
		Persona persona = Singleton.getInstance().getPersonas().get(nombre);
		if (persona==null){
			throw new Exception("La persona buscada no existe");
		}else{
			Singleton.getInstance().getPersonas().remove(nombre);
		}

	}

}
Clases que componen el mensaje de request-response

Despues se ha creado un proyecto web en el que lo mas relevante es que se ha generado el wsdl y adicionalmente se ha creado un xsd que contiene la definición de los tipos. Además se ha configurado el contenedor web y la configuración de Spring:

Proyecto web generado

personasserviceimpl_schema1.xsd

<?xml version="1.0" encoding="utf-8"?><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://cxf.autentia.com/" attributeFormDefault="unqualified" elementFormDefault="unqualified" targetNamespace="http://cxf.autentia.com/">
  <xs:element name="addPersona" type="tns:addPersona"/>
  <xs:element name="addPersonaResponse" type="tns:addPersonaResponse"/>
  <xs:element name="getPersona" type="tns:getPersona"/>
  <xs:element name="getPersonaResponse" type="tns:getPersonaResponse"/>
  <xs:element name="getPersonas" type="tns:getPersonas"/>
  <xs:element name="getPersonasResponse" type="tns:getPersonasResponse"/>
  <xs:element name="removePersona" type="tns:removePersona"/>
  <xs:element name="removePersonaResponse" type="tns:removePersonaResponse"/>
  <xs:complexType name="addPersona">
    <xs:sequence>
      <xs:element minOccurs="0" name="arg0" type="tns:persona"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="persona">
    <xs:sequence>
      <xs:element minOccurs="0" name="apellidos" type="xs:string"/>
      <xs:element minOccurs="0" name="nombre" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="addPersonaResponse">
    <xs:sequence/>
  </xs:complexType>
  <xs:complexType name="getPersonas">
    <xs:sequence/>
  </xs:complexType>
  <xs:complexType name="getPersonasResponse">
    <xs:sequence>
      <xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:persona"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="removePersona">
    <xs:sequence>
      <xs:element minOccurs="0" name="arg0" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="removePersonaResponse">
    <xs:sequence/>
  </xs:complexType>
  <xs:complexType name="getPersona">
    <xs:sequence>
      <xs:element minOccurs="0" name="arg0" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="getPersonaResponse">
    <xs:sequence>
      <xs:element minOccurs="0" name="return" type="tns:persona"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="Exception" type="tns:Exception"/>
  <xs:complexType name="Exception">
    <xs:sequence>
      <xs:element minOccurs="0" name="message" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

personasserviceimpl.wsdl

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="PersonasServiceImplService" targetNamespace="http://cxf.autentia.com/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://cxf.autentia.com/" 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://cxf.autentia.com/" schemaLocation="personasserviceimpl_schema1.xsd"/>
</schema>
  </wsdl:types>
  <wsdl:message name="addPersonaResponse">
    <wsdl:part name="parameters" element="tns:addPersonaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getPersonas">
    <wsdl:part name="parameters" element="tns:getPersonas">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="addPersona">
    <wsdl:part name="parameters" element="tns:addPersona">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getPersonasResponse">
    <wsdl:part name="parameters" element="tns:getPersonasResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="removePersonaResponse">
    <wsdl:part name="parameters" element="tns:removePersonaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="removePersona">
    <wsdl:part name="parameters" element="tns:removePersona">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getPersonaResponse">
    <wsdl:part name="parameters" element="tns:getPersonaResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getPersona">
    <wsdl:part name="parameters" element="tns:getPersona">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="Exception">
    <wsdl:part name="Exception" element="tns:Exception">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="PersonasServiceImpl">
    <wsdl:operation name="addPersona">
      <wsdl:input name="addPersona" message="tns:addPersona">
    </wsdl:input>
      <wsdl:output name="addPersonaResponse" message="tns:addPersonaResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="getPersonas">
      <wsdl:input name="getPersonas" message="tns:getPersonas">
    </wsdl:input>
      <wsdl:output name="getPersonasResponse" message="tns:getPersonasResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="removePersona">
      <wsdl:input name="removePersona" message="tns:removePersona">
    </wsdl:input>
      <wsdl:output name="removePersonaResponse" message="tns:removePersonaResponse">
    </wsdl:output>
      <wsdl:fault name="Exception" message="tns:Exception">
    </wsdl:fault>
    </wsdl:operation>
    <wsdl:operation name="getPersona">
      <wsdl:input name="getPersona" message="tns:getPersona">
    </wsdl:input>
      <wsdl:output name="getPersonaResponse" message="tns:getPersonaResponse">
    </wsdl:output>
      <wsdl:fault name="Exception" message="tns:Exception">
    </wsdl:fault>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="PersonasServiceImplServiceSoapBinding" type="tns:PersonasServiceImpl">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="addPersona">
      <soap:operation soapAction="" style="document"/>
      <wsdl:input name="addPersona">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="addPersonaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="getPersonas">
      <soap:operation soapAction="" style="document"/>
      <wsdl:input name="getPersonas">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getPersonasResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="getPersona">
      <soap:operation soapAction="" style="document"/>
      <wsdl:input name="getPersona">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getPersonaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
      <wsdl:fault name="Exception">
        <soap:fault name="Exception" use="literal"/>
      </wsdl:fault>
    </wsdl:operation>
    <wsdl:operation name="removePersona">
      <soap:operation soapAction="" style="document"/>
      <wsdl:input name="removePersona">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="removePersonaResponse">
        <soap:body use="literal"/>
      </wsdl:output>
      <wsdl:fault name="Exception">
        <soap:fault name="Exception" use="literal"/>
      </wsdl:fault>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="PersonasServiceImplService">
    <wsdl:port name="PersonasServiceImplPort" binding="tns:PersonasServiceImplServiceSoapBinding">
      <soap:address location="http://localhost:8080/PersonasWebService/services/PersonasServiceImplPort"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

beans.xml

<?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" />
	<jaxws:endpoint xmlns:tns="http://cxf.autentia.com/" id="personasservice"
		implementor="com.autentia.cxf.PersonasServiceImpl" wsdlLocation="wsdl/personasserviceimpl.wsdl"
		endpointName="tns:PersonasServiceImplPort" serviceName="tns:PersonasServiceImplService"
		address="/PersonasServiceImplPort">
		<jaxws:features>
			<bean class="org.apache.cxf.feature.LoggingFeature" />
		</jaxws:features>
	</jaxws:endpoint>
</beans>

web.xml

<?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_3_0.xsd" version="3.0">
  <display-name>PersonasWebService</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <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>WEB-INF/beans.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

Prueba del Servicio. Si hacemos click derecho sobre el wsdl seleccionando webservices–>Test with webservices explorer y realizamos una petición de addPersona

Invocación webservice

Invocación webservice

Invocación webservice

Si revisamos los logs del servidor, podemos ver todos los datos de la petición y la respuesta

INFO: Inbound Message
----------------------------
ID: 1
Address: http://localhost:8080/PersonasWebService/services/PersonasServiceImplPort
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=utf-8
Headers: {Accept=[application/soap+xml, application/dime, multipart/related, text/*], cache-control=[no-cache], connection=[close], Content-Length=[404], content-type=[text/xml; charset=utf-8], host=[localhost:8080], pragma=[no-cache], SOAPAction=[""], user-agent=[IBM Web Services Explorer]}
Payload: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:q0="http://cxf.autentia.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
    <q0:addPersona>
      <arg0>
        <apellidos>Carlos</apellidos>
        <nombre>León</nombre>
      </arg0>
    </q0:addPersona>
  </soapenv:Body>
</soapenv:Envelope>

--------------------------------------
19-abr-2012 13:30:23 org.apache.cxf.interceptor.AbstractLoggingInterceptor log
INFO: Outbound Message
---------------------------
ID: 1
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:addPersonaResponse xmlns:ns2="http://cxf.autentia.com/"/></soap:Body></soap:Envelope>
--------------------------------------

Podeis invocar a las demás operaciones para probar que todo está ok.

5. Aproximaciones. Contract First

La segunda aproximación es partiendo del contrato, es decir, el wsdl. Para ello utilizaremos el wsdl generado en el punto anterior. Si realizamos un click derecho sobre el WSDL y después realizamos los siguientes pasos:

Web Service skeleton

Web Service skeleton

Web Service skeleton

Web Service skeleton

Web Service skeleton

Web Service skeleton

Generaremos el esqueleto del webservice, en el cual tan solo deberemos modificar la clase PersonasServiceImplImpl con nuestra implementación, ya que al partir del contrato, no tenemos la lógica de negocio del servicio.

Web Service skeleton

6. Conclusiones

Como hemos podido comprobar, es bastante sencillo realizar webservices con estas herramientas, ya que las WST nos proveen una forma cómoda de trabajar con los comandos wsdl2java y java2wsdl que implementa la distribución de Apache CXF.
Aunque es una forma muy rápida de implementar los webservices, os recomiendo que lo hagais simplemente para construir el esqueleto de vuestro servicio para después pasarlo a un proyecto maven.

Los siguientes tutoriales sobre apache cxf serán construidos con maven basandonos en la arquitectura que hemos visto.

Como nota significativa y adicional a la generación del webservice desde una clase java, es que la implementación del WSDL es siempre document-literal, ya que los webservices son mucho mas interoperables trabajando en este formato (revisar http://www.ibm.com/developerworks/webservices/library/ws-whichwsdl/).
Si partimos de la generación del webservice desde el WSDL y el formato es rpc-encoded, apache cxf obtendremos un error (ya que no cumple la WS-I Compliance, mas información en http://www.ws-i.org/), ya que se está quedando obsoleto el formato RPC.
Para terminar, recomiendo el uso de apache cxf para implementar WebServices, dado que cubre casi todas las necesidades y está bastante documentado, además de ser motor de webservices por defecto en muchas plataformas ESB opensource y de pago.

Cualquier duda o sugerencia podeis comentarlo.

Saludos.

2 COMENTARIOS

  1. Buenos días Carlos,

    Gracias por tu tutorial.
    Quisiera saber si si teneis algún tutorial equivalente para generar las clases java a partir del WSDL con Apache CXF u otra herramienta JAX-WS RI.
    Por mas que he buscado no he encontrado nada.

    Muchas gracias,

  2. Buen dia Alvaro, yo estoy en la misma que vos y un amigo me paso el dato que me resulto!
    Aca va:

    Bajate esta herramienta:
    http://cxf.apache.org/docs/wsdl-to-java.html este es la herramienta que te genera un cliente de un webservice

    Este el comando que tenes que ejecutar en una consola

    wsdl2java -verbose -autoNameResolution http://10.241.100.99:9800/wsdl/servicios/integracion/cliente-v1?wsdl

    te va a generar una carpetita con las clases clientes pero, necesitas las lib de cfx en el cliente para consumir el webservice.

    Saludos!!!

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad