Servicios Web SOAP con JAX-WS, Spring y CXF (I) : Servidor

Última actualización: 13/02/2016

logo java

Java API for XML Web Services (JAX-WS), es una especificación para la creación de servicios web basados en XML-SOAP que vino a suponer la evolución de la especificación JAX-RPC y que forma parte de JEE 5 y Java SE 6. Está basada en anotaciones y permite la creación tanto de servicios como de clientes de servicios web SOAP directamente con clases Java POJO sin tener que lidiar con XML lo que simplifica enormemente trabajar con esta tecnología. A pesar del auge que han tenido en los últimos años los servicios REST(y que en Java cuentan con la especificación JAX-RS), los servicios web SOAP se siguen usando ampliamente y son una opción perfectamente válida para la publicación de APIs y la interoperabilidad de aplicaciones. En este enlace hay una interesante comparativa entre ambas tecnologías.

JAX-WS cuenta con una implementación de referencia llamada Metro, aunque también existen otros productos que implementan la especificación como CXF (el antigüo XFire) o Axis 2. En este tutorial vamos a partir de la base de que ya tenemos una aplicación web que utiliza Spring (al menos su contenedor IoC) y queremos publicar los métodos de un bean como un servicio web SOAP estándar para su consumo por parte de terceras aplicaciones. El módulo Spring WS permite la creación de servios web Contract-first y un soporte (limitado) del estándar JAX-WS pero lo que nosotros queremos es generar el servicio web automáticamente a partir de nuestro código Java sin definir el WSDL.

Este será el primero de una serie de artículos dedicados a JAX-WS con CXF y Spring.

  1. Servidor
  2. Clientes
  3. Securización TLS + BASIC
  4. Handlers
    1. Nota: Aquí hay una interesante comparativa en inglés entre los puntos fuertes y débiles de Axis 2, CXF y Spring WS, recomendándose este último.

      Requisitos: Conocimientos básicos de Spring IoC, aplicaciones web Java, Maven y servicios SOAP

      Resumen

      Los pasos a seguir y que detalla el artículo son los siguientes

      1. Incluir las dependencias de Spring y CXF
      2. Configurar en el web.xml los servlets de Spring y CXF
      3. Si utilizamos log4j, configurar la redirección de los logs de CXF
      4. Anotar con @WebService la interfaz o clase cuyos métodos públicos se publicarán con SOAP
      5. En el applicationContext definir el endpoint para la clase/interfaz
      6. El WS queda disponible dentro de la aplicación en la ruta / [url-pattern de CXF en el web.xml] / [endpoint del WS en el applicationContext]

      Proyecto para pruebas

      Vamos a crear un proyecto web Java estándar con Maven que, tal y como veremos más adelante, consistirá únicamente en un bean de Spring que expondremos como un servicio web. El pom.xml incluye la configuración para poder generar un proyecto para Eclipse WTP 2.0 que es el entorno de trabajo que siempre utilizo ( se puede usar cualquier otro o incluso ninguno, es una de las ventajas de usar Maven para configurar y construir los proyectos).

      <?xml version="1.0" encoding="UTF-8" ?>
      
      <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>
      	<groupId>com.danielme.demo</groupId>
      	<artifactId>spring-cxf-ws</artifactId>
      	<version>1.0</version>
      	<packaging>war</packaging>
      	<description>Maven + JAX-WS + Spring + CXF web project just for playing around with SOAP web services
      	</description>
      	<inceptionYear>2013</inceptionYear>
      
      
      	<licenses>
      		<license>
      			<name>GNU GENERAL PUBLIC LICENSE, Version 3.0</name>
      			<url>http://www.gnu.org/licenses/gpl.html</url>
      		</license>
      	</licenses>
      
      	<developers>
      		<developer>
      			<id>danielme.com</id>
      			<name>Daniel Medina</name>
      		</developer>
      	</developers>
      
      	<properties>	
          	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      		<cxf.version>2.7.18</cxf.version>
      		<spring.version>3.2.16.RELEASE</spring.version>
      		<log4j.version>1.2.12</log4j.version>
      		<java.version>1.6</java.version>
      	</properties>
      
      
      	<build>
      
      		<pluginManagement>
      
      			<plugins>
      
      				<plugin>
      					<groupId>org.apache.maven.plugins</groupId>
      					<artifactId>maven-compiler-plugin</artifactId>
      					<version>3.0</version>
      					<configuration>
      						<source>${java.version}</source>
      						<target>${java.version}</target>
      						<encoding>${project.build.sourceEncoding}</encoding>
      					</configuration>
      				</plugin>
      
      
      				<plugin>
      					<groupId>org.apache.maven.plugins</groupId>
      					<artifactId>maven-war-plugin</artifactId>
      					<version>2.3</version>
      					<configuration>
      						<archive>
      							<manifestEntries>
      								<Implementation-Version>${project.version}
      								</Implementation-Version>
      								<Implementation-Title>${artifactId}</Implementation-Title>
      								<Extension-Name>${artifactId}</Extension-Name>
      								<Built-By>Daniel Medina - danielme.com</Built-By>
      							</manifestEntries>
      						</archive>
      					</configuration>
      				</plugin>
      
      				<!-- mvn eclipse:eclipse -Dwtpversion=2.0 -->
      				<plugin>
      					<groupId>org.apache.maven.plugins</groupId>
      					<artifactId>maven-eclipse-plugin</artifactId>
      					<configuration>
      						<wtpapplicationxml>true</wtpapplicationxml>
      						<wtpversion>2.0</wtpversion>
      					</configuration>
      				</plugin>
      
      			</plugins>
      
      		</pluginManagement>
      
      	</build>
      
      	<dependencies>
      
      
      		<dependency>
      			<groupId>org.springframework</groupId>
      			<artifactId>spring-web</artifactId>
      			<version>${spring.version}</version>
      		</dependency>
      
      		<dependency>
      			<groupId>org.apache.cxf</groupId>
      			<artifactId>cxf-api</artifactId>
      			<version>${cxf.version}</version>
      		</dependency>
      
      		<dependency>
      			<groupId>org.apache.cxf</groupId>
      			<artifactId>cxf-rt-frontend-jaxws</artifactId>
      			<version>${cxf.version}</version>
      		</dependency>
      
      		<dependency>
      			<groupId>org.apache.cxf</groupId>
      			<artifactId>cxf-rt-transports-http</artifactId>
      			<version>${cxf.version}</version>
      		</dependency>
      
      		<dependency>
      			<groupId>log4j</groupId>
      			<artifactId>log4j</artifactId>
      			<version>${log4j.version}</version>
      		</dependency>
      
      
      	</dependencies>
      
      </project>
      

      En el web.xml, vamos a configurar los tres frameworks:

      • Log4j: simplemente me gusta tener la configuración de logs en el WEB-INF.
      • Spring: Se inicia el contexto de Spring como en cualquier proyecto web con Spring IoC indicando los xml de configuración.
      • CXF: Iniciamos su servlet, indicando la ruta relativa desde la que “colgaremos” nuestros servicios CXF (SOAP o REST). Se ha usado /ws
      <?xml version="1.0" encoding="UTF-8"?>
      
      <web-app id="spring-cxf-ws" version="2.5"
          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">
              
          <display-name>spring-cxf-ws</display-name>
          
        <!-- ==============Log4j============= --> 
      
           <context-param>
              <param-name>
                  log4jConfigLocation
              </param-name>
      
              <param-value>
                  /WEB-INF/log4j.xml
              </param-value>
          </context-param>
          
          <listener>
              <listener-class>
                  org.springframework.web.util.Log4jConfigListener
              </listener-class>
          </listener>
          
          <!-- ==============Spring============= -->
       
          <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>
                  /WEB-INF/applicationContext.xml
              </param-value>
          </context-param>
          
           <listener>
              <listener-class>
                  org.springframework.web.context.ContextLoaderListener
              </listener-class>
          </listener>
      
      
         <!-- ==============CXF============= -->
           
          <servlet>
          
      		<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>/ws/*</url-pattern>
      		</servlet-mapping>
         
      </web-app>
      

      El fichero log4j.xml tendrá una configuración estándar en modo DEBUG para salidad por consola y fichero:

      <?xml version="1.0" encoding="UTF-8" ?>
      
      <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
      
      <!-- http://wiki.apache.org/logging-log4j/Log4jXmlFormat -->
      
      <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
      
      	<appender name="console" class="org.apache.log4j.ConsoleAppender">
      		<param name="Target" value="System.out" />
      		<layout class="org.apache.log4j.PatternLayout">
      			<param name="ConversionPattern" value="%d{MM-dd-yyyy hh:mm:ss,SSS a} %5p %c{1}:%L - %m%n" />
      		</layout>
      	</appender>
      
      	<appender name="file" class="org.apache.log4j.RollingFileAppender">
      		<param name="file" value="logs/logger.log" />
      		<param name="MaxFileSize" value="5MB" />
      		<param name="MaxBackupIndex" value="10" />
      		<layout class="org.apache.log4j.PatternLayout">
      			<param name="ConversionPattern" value="%d{MM-dd-yyyy hh:mm:ss,SSS a} %5p %c{1}:%L - %m%n" />
      		</layout>
      
      	</appender>
      
      	<root>
      		<priority value="debug" />
      		<appender-ref ref="console" />
      		<!--<appender-ref ref="file" />-->
      	</root>
      
      </log4j:configuration>
      

      Para redireccionar los logs de CXF a log4j se creará el fichero de texto /src/main/resources/META-INF/org.apache.cxf.Logger que contendrá la siguiente línea: org.apache.cxf.common.logging.Log4jLogger

      org.apache.cxf.common.logging.Log4jLogger

      El applicationContext.xml de momento lo dejamos “vacío” pero con lo que necesitaremos.

      <?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:jaxws="http://cxf.apache.org/jaxws"
      	xsi:schemaLocation="http://www.springframework.org/schema/beans  
      		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
      		http://www.springframework.org/schema/context  
      		http://www.springframework.org/schema/context/spring-context-3.2.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-xml.xml"/>
      		<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
      
      	
      </beans>
      

      Desplegando en Tomcat, podemos comprobar si nuestra aplicación está operativa llamando a la url en la que se encuentran los servicios CXF, que en nuestro caso devolverá lo siguiente:

      no services have been found

      Creación del servicio web

      El servicio web SOAP que se va a crear permitirá realizar ciertas operaciones con los jugadores de un equipo de fútbol. Este servicio web será en realidad un bean definido en Spring que implementará una interfaz, aunque ninguno de estos requisitos son obligatorios (podríamos publicar cualquier clase). La clase para modelar los jugadores es la siguiente:

      package com.danielme.demo.jaxws.cxf.model;
      
      /**
       * 
       * @author danielme.com
       *
       */
      public class Player 
      {
      	private String name;
      	
      	private int age;
      	
      	private int number;
      	
      	public Player()
      	{
      		super();
      	}
      	
      	public Player(int number, String name, int age)
      	{
      		super();
      		this.name = name;
      		this.age = age;
      		this.number = number;
      	}
      
      
      	public String getName() 
      	{
      		return name;
      	}
      
      	public void setName(String name)
      	{
      		this.name = name;
      	}
      
      
      	public int getAge() 
      	{
      		return age;
      	}
      
      	public void setAge(int age) 
      	{
      		this.age = age;
      	}
      
      	public int getNumber() 
      	{
      		return number;
      	}
      
      	public void setNumber(int number) 
      	{
      		this.number = number;
      	}
      	
      	@Override
      	public String toString() 
      	{
      	   return number + " - " + name + " (" + age + ")";	
      	}
      
      }
      
      

      Y ahora vamos con la interfaz que define las operaciones sobre los jugadores

      package com.danielme.demo.jaxws.cxf.ws;
      
      import java.util.List;
      
      import javax.jws.WebService;
      
      import com.danielme.demo.jaxws.cxf.model.Player;
      
      /**
       * 
       * @author danielme.com
       *
       */
      @WebService
      public interface ITeamService 
      {	
      	List<Player> getTeam();
      	
      	List<Player> getPlayers(int... numbers);
      	
      	boolean updatePlayerByNumber(int number, Player player);
      	
      	boolean deletePlayer(int number);
      	
      	void foo();
      }
      

      Obsérvese que para convertirla en la definición de un servicio web simplemente se ha anotado con @WebService. La configuración por defecto es suficiente, pero se pueden modificar ciertos parámetros a la hora de generar el WSDL y los mensajes SOAP correspondientes tal y como podemos ver en la documentación. El método foo() no hará nada, y luego veremos por qué se ha definido.

      Por omisión, todos los métodos de la interfaz serán publicados como servicio web. Usaremos la configuración por defecto, pero se pueden personalizar un poco los métodos y sus parámetros, veamos un ejemplo:

      @WebMethod(operationName="actualizar")
      @WebResult(name="booleano", targetNamespace="http://com.danielme.demo.resultado")
      	boolean updatePlayerByNumber(int number,  @WebParam(header = true, name = "jugador", targetNamespace = "http://com.danielme.demo.jugador")Player player);
      
      • @WebMethod: personaliza la representación del método en el SOAP. En nuestro caso, establecemos el nombre de wsdl:operation
      • @WebResult: permite personalizar el valor wsdl:part correspondiente con el valor devuelto por el método. En nuestro ejemplo, le hemos puesto el nombre booleano y un namespace propio
      • @WebParam: permite personalizar el valor wsdl:part correspondiente a los parámetros que debe recibir el método. En nuestro ejemplo, le hemos definido el nombre, el namespace e indicamos que el valor “viajará” en el header del documento SOAP

      Ahora vamos con la implementación de esta interfaz. Es un bean de Spring definido mediante anotación que simplemente implementa la interfaz al que opcionalmente indicamos que vamos a utilizar el portocolo SOAP 1.2.

      .

      package com.danielme.demo.jaxws.cxf.ws;
      
      
      import java.util.LinkedList;
      import java.util.List;
      import java.util.ListIterator;
      
      
      import org.apache.log4j.Logger;
      import org.springframework.stereotype.Service;
      import javax.xml.ws.BindingType;
      
      import com.danielme.demo.jaxws.cxf.model.Player;
      
      @Service(value="teamService")
      @BindingType(javax.xml.ws.soap.SOAPBinding.SOAP12HTTP_BINDING)
      public class TeamServiceImpl implements ITeamService 
      {
      	
      	public static final Logger logger = Logger.getLogger(TeamServiceImpl.class);
      	 
      	private List<Player> team = null;	
      	
      
      	public TeamServiceImpl() 
      	{
      	    team = new LinkedList<Player>();
      	    team.add(new Player(1, "David De Gea", 22));
      	    team.add(new Player(2, "Rafael", 22));
      	    team.add(new Player(3, "Patrice Evra", 31));
      	    team.add(new Player(4, "Phil Jones", 21));
      	    team.add(new Player(5, "Rio Ferdinand", 34));
      	    team.add(new Player(7, "Antonio Valencia", 27));
      	    team.add(new Player(8, "Anderson", 24));
      	    team.add(new Player(10, "Wayne Rooney", 27));	    
      	}
      	
      	@Override
      	public List<Player> getTeam() 
      	{
      		logger.info("team requested");
      		return team;
      	}
      
      	@Override
      	public List<Player> getPlayers(int...numbers) 
      	{
      		List<Player> result = new LinkedList<Player>();
      		
      		if (numbers != null)
      		{
      			Player player = null;
      			for (int i =0; i< numbers.length;i++)
      			{
      				player = findById(numbers[i]);
      				if (player != null)
      				{
      					result.add(player);
      				}
      			}
      		}
      		logger.info("returning " + result.size() + " players");
      		
      		return result;
      	}
      
      	@Override
      	public boolean updatePlayerByNumber(int number, Player player)
      	{
      		logger.info("updating player " + number);
      
      		Player playerOld = findById(number);
      		boolean result = false;
      		if (playerOld != null)
      		{
      			playerOld.setAge(player.getAge());
      			playerOld.setNumber(player.getNumber());
      			playerOld.setName(player.getName());
      			result = true;
      		}
      		else
      		{
      			logger.warn("player " + number + " not found");
      		}
      		return result;
      
      	}
      
      	@Override
      	public boolean deletePlayer(int number) 
      	{
      		logger.info("deleting player " + number);
      
      		Player player = findById(number);
      		boolean result = false;
      		
      		if (player != null)
      		{
      			team.remove(player);
      			result = true;
      		}
      		else
      		{
      			logger.warn("player " + number + " not found");
      		}
      		return result;
      	}
      
      	@Override
      	public void foo() 
      	{
      		logger.info("Hello world!!!");
      	}
      	
      	private Player findById(int number)
      	{
      		Player player = null;
      		boolean found = false;
      		ListIterator<Player> listIterator = team.listIterator();
      		
      		while (listIterator.hasNext() && !found)
      		{
      			player = listIterator.next();
      			if (player.getNumber() == number)
      			{
      				found = true;
      			}
      		}
      		if (!found)
      		{
      			player = null;
      		}
      		
      		return player;
      	}
      
      }
      
      

      Ya sólo resta configurar el servicio en el applicationContext.xml. He añadido comentada cómo habría que hacer la configuración si queremos exponer como web service una clase cualquiera que no fuera un bean de Spring (si esta clase no implementa interfaz alguna obviamente las anotaciones de JAX-WS irían directamente en la misma). El resultado es el siguiente (no olvidar tampoco “activar” las anotaciones para la inyección de dependencias), obsérvese que al haber definido al menos un servicio podemos prescindir de los import de configuración de CXF del proyecto inicial:

      <?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:jaxws="http://cxf.apache.org/jaxws"
      	xsi:schemaLocation="http://www.springframework.org/schema/beans  
      		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
      		http://www.springframework.org/schema/context  
      		http://www.springframework.org/schema/context/spring-context-3.2.xsd  
      		http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
      
      	<!-- activates annotations (@Service, @Component, @Autowired) -->
      	<context:component-scan base-package="com.danielme.demo" />
      
          <!-- publish the web service -->
      	<!--  <jaxws:endpoint id="teamService" implementor="com.danielme.demo.jaxws.cxf.ws.TeamServiceImpl" address="/v/1/teamService" />-->
      
      	<!-- references a spring bean  -->
      	<!--  <bean id="teamService" class="com.danielme.demo.jaxws.cxf.ws.TeamService"/>--> 
      	<jaxws:endpoint id="teamServiceWS" implementor="#teamService" address="/v/1/teamService"/>
      	
      </beans>
      

      Lo que hemos hecho es indicar el bean de Spring que hay que exponer mediante SOAP y su ruta en el servidor que será relativa a la ruta en la que se ha configurado CXF en el web.xml. Con esto ya lo tenemos todo listo, y sólo resta desplegar la aplicación y comprobar que nuestro servicio está operativo. Si accedemos a la url en la que “escucha” el servlet de CXF veremos un listado de todos los servicios desplegados.

      SOAP 1

      Nota: se pueden ocultar los servicios mostrados en el listado.

      <jaxws:endpoint id="teamServiceWS" implementor="#teamService" address="/v/1/teamService">
      	<jaxws:properties><entry key="org.apache.cxf.endpoint.private" value="true"/></jaxws:properties>
      </jaxws:endpoint>
      

      El descriptor de nuestro servicio que CXF ha generado “mágicamente” lo encontramos en la url

       http://localhost:8080/spring-cxf-ws/ws/v/1/teamService?wsdl

      .

      <wsdl:definitions name="TeamServiceImplService"
      	targetNamespace="http://ws.cxf.jaxws.demo.danielme.com/">
      	<wsdl:types>
      		<xs:schema elementFormDefault="unqualified"
      			targetNamespace="http://ws.cxf.jaxws.demo.danielme.com/" version="1.0">
      			<xs:element name="deletePlayer" type="tns:deletePlayer" />
      			<xs:element name="deletePlayerResponse" type="tns:deletePlayerResponse" />
      			<xs:element name="foo" type="tns:foo" />
      			<xs:element name="fooResponse" type="tns:fooResponse" />
      			<xs:element name="getPlayers" type="tns:getPlayers" />
      			<xs:element name="getPlayersResponse" type="tns:getPlayersResponse" />
      			<xs:element name="getTeam" type="tns:getTeam" />
      			<xs:element name="getTeamResponse" type="tns:getTeamResponse" />
      			<xs:element name="updatePlayerByNumber" type="tns:updatePlayerByNumber" />
      			<xs:element name="updatePlayerByNumberResponse" type="tns:updatePlayerByNumberResponse" />
      			<xs:complexType name="deletePlayer">
      				<xs:sequence>
      					<xs:element name="arg0" type="xs:int" />
      				</xs:sequence>
      			</xs:complexType>
      			<xs:complexType name="deletePlayerResponse">
      				<xs:sequence>
      					<xs:element name="return" type="xs:boolean" />
      				</xs:sequence>
      			</xs:complexType>
      			<xs:complexType name="getPlayers">
      				<xs:sequence>
      					<xs:element maxOccurs="unbounded" minOccurs="0" name="arg0"
      						type="xs:int" />
      				</xs:sequence>
      			</xs:complexType>
      			<xs:complexType name="getPlayersResponse">
      				<xs:sequence>
      					<xs:element maxOccurs="unbounded" minOccurs="0" name="return"
      						type="tns:player" />
      				</xs:sequence>
      			</xs:complexType>
      			<xs:complexType name="player">
      				<xs:sequence>
      					<xs:element name="age" type="xs:int" />
      					<xs:element minOccurs="0" name="name" type="xs:string" />
      					<xs:element name="number" type="xs:int" />
      				</xs:sequence>
      			</xs:complexType>
      			<xs:complexType name="foo">
      				<xs:sequence />
      			</xs:complexType>
      			<xs:complexType name="fooResponse">
      				<xs:sequence />
      			</xs:complexType>
      			<xs:complexType name="updatePlayerByNumber">
      				<xs:sequence>
      					<xs:element name="arg0" type="xs:int" />
      					<xs:element minOccurs="0" name="arg1" type="tns:player" />
      				</xs:sequence>
      			</xs:complexType>
      			<xs:complexType name="updatePlayerByNumberResponse">
      				<xs:sequence>
      					<xs:element name="return" type="xs:boolean" />
      				</xs:sequence>
      			</xs:complexType>
      			<xs:complexType name="getTeam">
      				<xs:sequence />
      			</xs:complexType>
      			<xs:complexType name="getTeamResponse">
      				<xs:sequence>
      					<xs:element maxOccurs="unbounded" minOccurs="0" name="return"
      						type="tns:player" />
      				</xs:sequence>
      			</xs:complexType>
      		</xs:schema>
      	</wsdl:types>
      	<wsdl:message name="getPlayers">
      		<wsdl:part element="tns:getPlayers" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="fooResponse">
      		<wsdl:part element="tns:fooResponse" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="getTeamResponse">
      		<wsdl:part element="tns:getTeamResponse" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="updatePlayerByNumber">
      		<wsdl:part element="tns:updatePlayerByNumber" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="foo">
      		<wsdl:part element="tns:foo" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="getPlayersResponse">
      		<wsdl:part element="tns:getPlayersResponse" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="updatePlayerByNumberResponse">
      		<wsdl:part element="tns:updatePlayerByNumberResponse" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="deletePlayer">
      		<wsdl:part element="tns:deletePlayer" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="getTeam">
      		<wsdl:part element="tns:getTeam" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:message name="deletePlayerResponse">
      		<wsdl:part element="tns:deletePlayerResponse" name="parameters">
      		</wsdl:part>
      	</wsdl:message>
      	<wsdl:portType name="ITeamService">
      		<wsdl:operation name="deletePlayer">
      			<wsdl:input message="tns:deletePlayer" name="deletePlayer">
      			</wsdl:input>
      			<wsdl:output message="tns:deletePlayerResponse" name="deletePlayerResponse">
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="getPlayers">
      			<wsdl:input message="tns:getPlayers" name="getPlayers">
      			</wsdl:input>
      			<wsdl:output message="tns:getPlayersResponse" name="getPlayersResponse">
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="foo">
      			<wsdl:input message="tns:foo" name="foo">
      			</wsdl:input>
      			<wsdl:output message="tns:fooResponse" name="fooResponse">
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="updatePlayerByNumber">
      			<wsdl:input message="tns:updatePlayerByNumber" name="updatePlayerByNumber">
      			</wsdl:input>
      			<wsdl:output message="tns:updatePlayerByNumberResponse"
      				name="updatePlayerByNumberResponse">
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="getTeam">
      			<wsdl:input message="tns:getTeam" name="getTeam">
      			</wsdl:input>
      			<wsdl:output message="tns:getTeamResponse" name="getTeamResponse">
      			</wsdl:output>
      		</wsdl:operation>
      	</wsdl:portType>
      	<wsdl:binding name="TeamServiceImplServiceSoapBinding"
      		type="tns:ITeamService">
      		<soap:binding style="document"
      			transport="http://schemas.xmlsoap.org/soap/http" />
      		<wsdl:operation name="deletePlayer">
      			<soap:operation soapAction="" style="document" />
      			<wsdl:input name="deletePlayer">
      				<soap:body use="literal" />
      			</wsdl:input>
      			<wsdl:output name="deletePlayerResponse">
      				<soap:body use="literal" />
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="getPlayers">
      			<soap:operation soapAction="" style="document" />
      			<wsdl:input name="getPlayers">
      				<soap:body use="literal" />
      			</wsdl:input>
      			<wsdl:output name="getPlayersResponse">
      				<soap:body use="literal" />
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="foo">
      			<soap:operation soapAction="" style="document" />
      			<wsdl:input name="foo">
      				<soap:body use="literal" />
      			</wsdl:input>
      			<wsdl:output name="fooResponse">
      				<soap:body use="literal" />
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="updatePlayerByNumber">
      			<soap:operation soapAction="" style="document" />
      			<wsdl:input name="updatePlayerByNumber">
      				<soap:body use="literal" />
      			</wsdl:input>
      			<wsdl:output name="updatePlayerByNumberResponse">
      				<soap:body use="literal" />
      			</wsdl:output>
      		</wsdl:operation>
      		<wsdl:operation name="getTeam">
      			<soap:operation soapAction="" style="document" />
      			<wsdl:input name="getTeam">
      				<soap:body use="literal" />
      			</wsdl:input>
      			<wsdl:output name="getTeamResponse">
      				<soap:body use="literal" />
      			</wsdl:output>
      		</wsdl:operation>
      	</wsdl:binding>
      	<wsdl:service name="TeamServiceImplService">
      		<wsdl:port binding="tns:TeamServiceImplServiceSoapBinding"
      			name="TeamServiceImplPort">
      			<soap:address
      				location="http://localhost:8080/spring-cxf-ws/ws/v/1/teamService" />
      		</wsdl:port>
      	</wsdl:service>
      </wsdl:definitions>
      

      Tal y como vemos todos los métodos de la interfaz se han publicado dentro del servicio web, ¿pero qué pasaría si no queremos exponer algún método? Puede ser el caso de que nuestro servicio de Spring sólo deba publicar al exterior un subconjunto de funcionalidades. Pues bien, vamos a evitar que se publique el método foo, y para ello anotamos el método de la siguiente forma siempre en la implementación y no en la interfaz:

      @Override
      	@WebMethod(exclude=true)
      	public void foo() 
      	{
      		logger.info("Hello world!!!");
      	}
      

      Si redesplegamos la aplicación, podemos comprobar cómo ahora el método foo ya no forma parte de nuestro servicio web

      available soap services

      Ya tenemos listo nuestro servicio de Spring para ser consumido por otras aplicaciones. En la segunda parte del artículo veremos lo fácil que resulta hacerlo con JAX-WS, CXF y, opcionalmente, Spring.

      Algunas limitaciones

      La “magia” de JAX-WS está limitada por la tecnología subyacente, esto es, los mensajes XML SOAP. Esto nos obliga a tener cuidado con los servicios que publicamos, por ejemplo:

      • Los parámetros utilizados no deben tener referencias cruzadas, esto es, constituir una estructura de datos recursiva. Esto es debido a que el mensaje de respuesta del WS es un XML en el que no se pueden representar estos datos. Si incurrimos en este error, se producirá un excepción en el servidor que recibiremos en el cliente con un mensaje que, afortunadamente, es bastante descriptivo y nos ayudará a identificar exactamente cual es el error producido. Este mensaje comenzará más o menos así:
        org.apache.cxf.interceptor.Fault: Marshalling Error: A cycle is detected in the object graph. This will cause infinitely deep XML
      • Si queremos que el servicio sea totalmente estándar para usarlo con cualquier cliente/lenguaje, debemos utilizar como estructura de datos únicamente Arrays (o en su defecto List que son correctamente mapeados por CXF) en lugar de tipos propios de Java como HashMap o similares.

      Código completo

      El proyecto completo se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.

One Response to Servicios Web SOAP con JAX-WS, Spring y CXF (I) : Servidor

  1. sergio dice:

    Realmente muy buen aporte y esta super claro !!!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: