lunes, 19 de septiembre de 2011

Tutorial Web Services (II): Implementando un web service Apache CXF

Instalando Eclipse

Para desarrollar el web service vamos a utilizar Eclipse, uno de los IDEs más empleados en el mundo del desarrollo de aplicaciones web. Eclipse puede descargarse gratuitamente en http://www.eclipse.org. Necesitamos descargar la versión para Java EE Developers. A día de hoy, la versión más actual es Eclipse Helios (anteriormente Galileo y Ganymede).

Simplemente debes descargar el archivo .zip, descomprimirlo en tu ordenador y arrancar el ejecutable eclipse.exe

Utilizando una librería para web services

Cuando un cliente invoque el web service, mandará un mensaje de entrada SOAP. Para facilitar el trabajo necesitamos un conversor de mensaje SOAP a clase Java, y que esta clase Java invoque los métodos de otra clase Java que tú proveas. Hay librerías que pueden actuar como este conversor, las más populares Apache CXF, Apache Axis2 y Metro de Sun Microsystems.

 

Instalando Apache CXF


Para usar Apache CXF necesitas descargar varias librerías que dependen unas de otras. Para facilitar este proceso vamos a utilizar Maven en Eclipse. Aquí puedes ver unas sencillas instrucciones para instalar Maven en Eclipse.

Creamos un proyecto Java en Eclipse. Le llamaremos SimpleService. Después click con botón derecho sobre el proyecto y escogemos Maven | Enable Dependency Management. Vemos:


Aceptamos los valores por defecto y pulsamos Finish.

Ahora necesitamos una conexión a internet. Indicamos a Maven las librerías CXF que necesitamos y de dónde nos las descargamos. Para ello modificamos el pom.xml del proyecto:

<project ...>
      <modelVersion>4.0.0</modelVersion>
      <groupId>SimpleService</groupId>
      <artifactId>SimpleService</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <dependencies>
            <dependency>
                  <groupId>org.apache.cxf</groupId>
                  <artifactId>cxf-bundle</artifactId>
                  <version>2.2.5</version>
            </dependency>
      </dependencies>
      <repositories>
            <repository>
                  <id>apache-incubating</id>
                  <name>Apache Incubating Repository</name>
                  <url>http://people.apache.org/repo/m2-incubating-repository/</url>
            </repository>
      </repositories>
</project>

WSDL del web service

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
      xmlns:tns="http://ttdev.com/ss"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="SimpleService"
      targetNamespace="http://ttdev.com/ss">
      <wsdl:types>
            <xsd:schema
                  targetNamespace="http://ttdev.com/ss"
                  xmlns:tns="http://ttdev.com/ss">
                  <xsd:element name="concatRequest">
                        <xsd:complexType>
                              <xsd:sequence>
                                    <xsd:element name="s1" type="xsd:string"/>
                                    <xsd:element name="s2" type="xsd:string"/>
                              </xsd:sequence>
                        </xsd:complexType>
                 </xsd:element>
                 <xsd:element name="concatResponse" type="xsd:string"/>
            </xsd:schema>
      </wsdl:types>
      <wsdl:message name="concatRequest">
            <wsdl:part name="concatRequest" element="tns:concatRequest" />
      </wsdl:message>
      <wsdl:message name="concatResponse">
            <wsdl:part name="concatResponse" element="tns:concatResponse" />
      </wsdl:message>
      <wsdl:portType name="SimpleService">
            <wsdl:operation name="concat">
                  <wsdl:input message="tns:concatRequest" />
                  <wsdl:output message="tns:concatResponse" />
            </wsdl:operation>
      </wsdl:portType>
      <wsdl:binding name="SimpleServiceSOAP" type="tns:SimpleService">
            <soap:binding style="document"                                                                                             transport="http://schemas.xmlsoap.org/soap/http" />
                  <wsdl:operation name="concat">
                        <soap:operation soapAction="http://ttdev.com/ss/concat" />
                              <wsdl:input>
                                    <soap:body parts="concatRequest" use="literal" />
                              </wsdl:input>
                              <wsdl:output>
                                    <soap:body parts="concatResponse" use="literal" />
                              </wsdl:output>
                  </wsdl:operation>
      </wsdl:binding>
      <wsdl:service name="SimpleService">
            <wsdl:port binding="tns:SimpleServiceSOAP" name="p1">
                  <soap:address location="http://localhost:8080/ss/p1" />
            </wsdl:port>
      </wsdl:service>
</wsdl:definitions>

Creando el fichero WSDL visualmente

Aquí podemos seguir las instrucciones para crear el WSDL visualmente con Eclipse, ya que la creación del WSDL a mano es propensa a errores.

Validando el fichero WSDL

Sobre SimpleService.wsdl hacemos click con botón derecho --> Validate

Generando el código del servicio

Aquí usamos Apache CXF para que genere el código del conversor de mensaje SOAP a objeto Java. Este objeto se pasará como argumento a un método. Creamos las carpetas src/main/java. Después click botón derecho sobre la raíz del proyecto y Maven | Update Project Configuration para habilitar la configuración Maven, que lo que hace es convertir la carpeta java en una carpeta de código fuente por lo que los ficheros Java .class serán compilados.

En la carpeta src/main/java creamos la siguiente clase:

package com.ttdev;

import org.apache.cxf.tools.wsdlto.WSDLToJava;

public class CodeGenerator {
      public static void main(String[] args) {
            WSDLToJava.main(new String[] {
                  "-server",
                  "-d", "src/main/java",
                  "src/main/resources/SimpleService.wsdl" });
            System.out.println("Done!");
      }
}

La ejecutamos como Java Application. Si obtienes un error tipo java.lang.AbstractMethodError es que las librerías que ha descargado Maven están desfasadas. Para arreglarlo, añade la siguiente dependencia al pom.xml:

            <dependency>
                  <groupId>xerces</groupId>
                  <artifactId>xercesImpl</artifactId>
                  <version>2.9.1</version>
            </dependency>

Después de terminar de ejecutarlo con éxito, hacemos un Refresh (F5) del proyecto. Se ha creado el package com.ttdev.ss con algunos ficheros en él. Aparecen errores porque al actualizar la configuración del proyecto con Maven, el plugin de Eclipse fija la versión Java 1.4 por defecto, que no soporta anotaciones. Para arreglarlo modificamos el pom.xml:

<project ...>
      ...
      <dependencies>
            ...
      </dependencies>
      <repositories>
            ...
      </repositories>
      <build>
            <plugins>
                  <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <configuration>
                              <source>1.6</source>
                              <target>1.6</target>
                        </configuration>
                  </plugin>
            </plugins>
      </build>
</project> 

Para que esto funcione debes tener Java versión 6 (java -version en una shell para ver la versión de Java que tienes instalada).

Actualiza la configuración del proyecto y los errores deben desaparecer. Todos menos un error en el fichero SimpleService_P1_Server:


Creamos la clase SimpleServiceImpl en el package com.ttdev.ss, clase donde implementamos la funcionalidad de nuestro web service:

package com.ttdev.ss;

import javax.jws.WebService;

@WebService(endpointInterface = "com.ttdev.ss.SimpleService")
public class SimpleServiceImpl implements SimpleService {
      @Override
      public String concat(ConcatRequest parameters) {
            return parameters.getS1() + parameters.getS2();
      }
}

Con la anotación @WebService marcamos que la clase implementa un web service en el SEI(port type) com.ttdev.ss.SimpleService. Además la clase implementa la interface SimpleService proporcionada por Apache CXF (WSDLToJava):

package com.ttdev.ss;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;

@WebService(targetNamespace = "http://ttdev.com/ss", name = "SimpleService")
@XmlSeeAlso( { ObjectFactory.class })
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface SimpleService {
      @WebResult(name = "concatResponse",
            targetNamespace = "http://ttdev.com/ss",
            partName = "parameters")
      @WebMethod(action = "http://ttdev.com/ss/NewOperation")
      public java.lang.String concat(
            @WebParam(partName = "parameters",
                  name = "concatRequest",
                  targetNamespace = "http://ttdev.com/ss") ConcatRequest parameters);
}

También WSDLToJava nos proporciona la clase ConcatRequest:

package com.ttdev.ss;

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;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "s1", "s2" })
@XmlRootElement(name = "concatRequest")
public class ConcatRequest {
      @XmlElement(required = true)
      protected String s1;
      @XmlElement(required = true)
      protected String s2;
      public String getS1() {
            return s1;
      }
      public void setS1(String value) {
            this.s1 = value;
      }
      public String getS2() {
            return s2;
      }
      public void setS2(String value) {
            this.s2 = value;
      }
}

Ejecutamos la clase SimpleService_P1_Server como Java Application. Finalmente debe mostrarse un mensaje de servidor disponible "Server ready":


Para comprobar que el servidor está funcionando vamos a un navegador y pegamos la siguiente dirección: http://localhost:8080/ss/p1?wsdl Debe devolver el fichero WSDL:


Para finalizar el servicio, espera 5 minutos o pulsa el botón rojo en Eclipse en la ventana "console"


Creando el Cliente

Apache CXF también puede generar un conversor en el lado del cliente (service stub). Cuando invocas un método en el stub, los objetos Java se convierten al formato correcto (XML) y se mandan al web service real. Cuando obtiene la respuesta, los vuelva a convertir de XML a Java.

Copiamos el proyecto SimpleService y lo pegamos como SimpleServiceClient. Modificamos la clase CodeGenerator para que convierta del fichero WSDL a código Java para el cliente:

package com.ttdev;

import org.apache.cxf.tools.wsdlto.WSDLToJava;

public class CodeGenerator {
      public static void main(String[] args) {
            WSDLToJava.main(new String[] {
                  "-client",
                  "-d", "src/main/java",
                  "src/main/resources/SimpleService.wsdl" });
            System.out.println("Done!");
      }
}

Borramos el package com.ttdev.ss y ejecutamos CodeGenerator como una Java Application. Refrescamos el proyecto para ver el nuevo código generado. En particular, nos interesa el cliente SimpleService_P1_Client:


Necesitamos modificar el código generado para crear un objeto ConcatRequest

...
public static void main(String args[]) throws Exception {
...
      {
            System.out.println("Invoking concat...");
            com.ttdev.ss.ConcatRequest _concat_parameters = new ConcatRequest();
            _concat_parameters.setS1("abc");
            _concat_parameters.setS2("123");
            java.lang.String _concat__return = port.concat(_concat_parameters);
            System.out.println("concat.result=" + _concat__return);
      }
      System.exit(0);
}

Finalmente ejecuta en primer lugar el servidor y después el cliente. La siguiente salida debe mostrarse por la consola:

...
Invoking concat...
concat.result=abc123

No hay comentarios:

Publicar un comentario