Web Service con Axis 2

De Dos Ideas.
Saltar a: navegación, buscar

Axis 2 es un framework para el desarrollo y consumo de Webservices en Java, e intenta solucionar algunos temas pendientes de su predecesor Axis 1.

A continuación haremos un proyecto prototipo, partiendo de un WSDL de un servicio sencillo de calculadora.

Pasos:

Crear un proyecto Web con Netbeans

Utilizaremos el nombre DosIdeas-Axis2 para el proyecto.

Agregar la librería Axis 2 al proyecto

Como puede ser que no este instalada, deberemos bajarla del sitio Web (http://ws.apache.org/axis2/download/1_4_1/download.cgi). Utilizaremos la versión 1.4.1 que es la última versión compatible con Java 1.4. En la versión 1.5 de Axis 2 cambia bastante la forma de construir Web Services, pero no será visto en este tutorial.

El proyecto viene con muchas mas librerías de las que son estrictamente necesarias para el ejemplo. No obstante, para simplificar incorporaremos todas.

Agregar Spring 2.5 o posterior

Si bien no es imprescindible para el funcionamiento, permite una clara separación de la lógica de negocio de la implementación del servicio Web.

Copiar WSDL de ejemplo al proyecto

Para este proyecto, tenemos un WSDL de un servicio Calculadora, que tiene 2 operaciones:

  • dividir (división entera)
  • sumar

La operación dividir necesita 2 parámetros, dividendo y divisor y devuelve 2 valores, cociente y resto. Además debe devolver un mensaje de error en caso que el divisor sea 0.

Generar código java a partir de un documento WSDL

Axis 2 viene con un generador de código que ahorra al usuario la construcción de todas clases necesarias para la implementación de WebServices. En este caso utilizaremos una tarea Ant para construir dichas clases. En caso de trabajar con Eclipse, también tendremos la posibilidad de utilizar el plugin de Web Services, que viene con la distribución JEE del IDE.

   <property name="axis2.home" value="D:/development/axis2-1.4.1" />
   <property name="output.wsdl2java.dir" value="build/codegen" />
   <property name="src.dir" value="src/java" />
   <property name="wsdl.uri" value="resources/wsdl/CalculadoraService.wsdl" />
   <property name="services.folder" value="web/WEB-INF/services" />
   
   <property name="service.name" value="CalculadoraService" />
   <property name="service.wsdl.uri" value="resources/wsdl/${service.name}.wsdl" />
   <property name="service.folder" value="${services.folder}/${service.name}/META-INF" />
   
       
   <path id="axis2.classpath">
       <fileset dir="${axis2.home}/lib"> 
           <include name="**/*.jar" />
       </fileset>
   </path>
       
   <target name="run-wsdl2java-server">
       <mkdir dir="${output.wsdl2java.dir}" />
       <delete dir="${output.wsdl2java.dir}" includeemptydirs="true" />
       <java classname="org.apache.axis2.wsdl.WSDL2Java" fork="true">
         <classpath refid="axis2.classpath"/> 
         <arg value="-uri"/><arg file="${wsdl.uri}"/>
         <arg value="-ss"/>
         <arg value="-g"/>
         <arg value="-sd"/>
         <arg value="-o"/><arg file="${output.wsdl2java.dir}"/>
       </java>
       <copy todir="${src.dir}" overwrite="true">
           <fileset dir="${output.wsdl2java.dir}/src">
               <include name="**/*.java"/>
           </fileset>
       </copy>
       <mkdir dir="${service.folder}" />
       <delete dir="${service.folder}" includeemptydirs="true" />
       
       <mkdir dir="${service.folder}/service" />
       
       <copy todir="${service.folder}/service" overwrite="true">
           <fileset dir="${output.wsdl2java.dir}/resources">
               <include name="**/*"/>
           </fileset>
       </copy>
   </target>

Esta tarea Ant generará todas las clases involucradas en el Web Service y una serie de archivos, incluyendo la clase que contendrá la lógica de negocio, la cual vendrá con implementaciones dummy. Luego se copiaran los clases java al directorio de fuentes y por último se copiará el archivo de configuración y el wsdl al directorio de servicio predeterminado de Axis.

Implementar lógica de negocio

De las clases generadas, la única que tendremos que implementar es la que tiene el sufijo Skeleton. En caso que implementaramos la lógica aquí, sería poco reutilizable, por lo que haremos en un paso siguiente una clase con la lógica de negocio y en el Skeleton tan solo invocaremos dicha clase.

   public class CalculadoraServiceSkeleton {
       
       protected CalculadoraBo calculadoraBo = null;
       
       public CalculadoraBo getCalculadoraBo() {
           return calculadoraBo;
       }
       
       public void setCalculadoraBo(CalculadoraBo calculadoraBo) {
           this.calculadoraBo = calculadoraBo;
       }
       
       /**
        * Auto generated method signature
        * 
        * @param divisionSolicitud
        * @throws DivisionPorCeroError : 
        */
       public com.dosideas.www.cursos.calculadora.DivisionRespuesta dividir(
               com.dosideas.www.cursos.calculadora.DivisionSolicitud divisionSolicitud)
               throws DivisionPorCeroError {
           int divisor = divisionSolicitud.getDivisor();
           int dividendo = divisionSolicitud.getDividendo();
           try {
               int cociente = calculadoraBo.divide(dividendo, divisor);
               int resto = calculadoraBo.modulo(dividendo, divisor);
               DivisionRespuesta rta = new DivisionRespuesta();
               rta.setCociente(cociente);
               rta.setResto(resto);
               return rta;
           } catch (ArithmeticException e) {
               DivisionPorCeroException error = new DivisionPorCeroException();
               // En caso que no se setee el objeto descripcion, tira un error genérico AxisFault
               DivisionPorCero div = new DivisionPorCero();
               // Si no se llenan todos los campos obligatorios, devuelve error 500 de http
               div.setDivisionPorCero("Descripcion del error");
               error.setFaultMessage(div);
               throw error;
           }
       }
       
       /**
        * Auto generated method signature
        * 
        * @param sumaSolicitud
        */
       public com.dosideas.www.cursos.calculadora.SumaRespuesta sumar(
               com.dosideas.www.cursos.calculadora.SumaSolicitud sumaSolicitud) {
           SumaRespuesta rta = new SumaRespuesta();
           rta.setNumero(calculadoraBo.sum(sumaSolicitud.getNumero1(), sumaSolicitud.getNumero2()));    
           return rta;
       }
   }

Se recomienda prestar especial atención al tratamiento de los errores custom. Cada error de negocio cuenta con un tipo interno, que contiene la descripción del error. En caso de no llenar dicha descripción, se lanzará un error genérico AxisFault. Además hay que tener especial cuidado al llenar dicha descripción. En caso que no se cumpla con el formato, Axis devuelve un error http 500, en vez de un error descriptivo. Luego se crea la clase con la lógica de negocio, la cual después se inyectará en la clase Skeleton via Spring

   public class CalculadoraBo {
       public int sum(int n1, int n2) {
           return n1 + n2;
       }
       
       public int divide(int n1, int n2){
           return n1 / n2;
       }
       
       public int modulo(int n1, int n2){
           return n1 % n2;
       }
   }

Configurar descriptores de Spring

   <bean id="applicationContext" 
       class="org.apache.axis2.extensions.spring.receivers.ApplicationContextHolder" />
       
   <bean id="calculadoraBo" class="com.dosideas.business.CalculadoraBo">
       
   </bean>
   <bean id="calculadoraService" class="com.dosideas.www.cursos.calculadora.CalculadoraServiceSkeleton">
       <property name="calculadoraBo" ref="calculadoraBo" />
   </bean>

Configurar web.xml

   <?xml version="1.0" encoding="UTF-8"?>
   <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
       <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>
       
       <servlet>
           <display-name>Apache-Axis Servlet</display-name>
           <servlet-name>AxisServlet</servlet-name>
           <servlet-class>org.apache.axis2.transport.http.AxisServlet</servlet-class>
       </servlet>
       
       <servlet-mapping>
           <servlet-name>AxisServlet</servlet-name>
           <url-pattern>/services/*</url-pattern>
       </servlet-mapping>
       
       <welcome-file-list>
           <welcome-file>index.jsp</welcome-file>
       </welcome-file-list>
   </web-app>

Modificar services.xml para que use Spring

       <parameter name="ServiceObjectSupplier" locked="false">
       org.apache.axis2.extensions.spring.receivers.SpringAppContextAwareObjectSupplier</parameter>
       <parameter name="SpringBeanName" locked="false">calculadoraService</parameter>

La configuración descrita aquí funciona tanto para Tomcat como para Glassfish. En caso que se desee hacer un Web Service para Weblogic o para Websphere, son necesarios algunos pasos adicionales. Se sugiere leer la siguiente nota: Web Service con Axis 2 para Weblogic y Websphere

Sugerencias para la construcción del WSDL

  • No utilizar las palabras Error o Exception al final de los tipos o de los mensajes Fault. Estas palabras son utilizadas por el generador de código, por lo que luego el código generado será incorrecto.
  • Si se usa la versión 1.4 de Axis 2, no nombrar de la misma forma un mensaje de error que el tipo que contiene. Genera un error extraño en el generador de código, por el que genera 2 excepciones con el nombre del mensaje. Ej: Con el tipo DivisionPorCero, generó DivisionPorCeroException0 y DivisionPorCeroException1.

Recursos

WSDL de Calculadora Service

  <?xml version="1.0" encoding="UTF-8"?>
  <wsdl:definitions name="CalculadoraServiceDefinitions" targetNamespace="http://www.dosideas.com/cursos/calculadora" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:calc="http://www.dosideas.com/cursos/calculadora" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
      <wsdl:types>
          <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.dosideas.com/cursos/calculadora">
              <xs:element name="divisionSolicitud">
                  <xs:complexType>
                      <xs:sequence>
                          <xs:element name="dividendo" type="xs:int"/>
                          <xs:element name="divisor" type="xs:int"/>
                      </xs:sequence>
                  </xs:complexType>
              </xs:element>
              <xs:element name="divisionRespuesta">
                  <xs:complexType>
                      <xs:sequence>
                          <xs:element name="cociente" type="xs:int"/>
                          <xs:element name="resto" type="xs:int"/>
                      </xs:sequence>
                  </xs:complexType>
              </xs:element>
              <xs:element name="divisionPorCero" type="xs:string"/>
              <xs:element name="sumaSolicitud">
                  <xs:complexType>
                      <xs:sequence>
                          <xs:element name="numero1" type="xs:int"/>
                          <xs:element name="numero2" type="xs:int"/>
                      </xs:sequence>
                  </xs:complexType>
              </xs:element>
              <xs:element name="sumaRespuesta">
                  <xs:complexType>
                      <xs:sequence>
                          <xs:element name="numero" type="xs:int"/>
                      </xs:sequence>
                  </xs:complexType>
              </xs:element>
          </xs:schema>
      </wsdl:types>
      <wsdl:message name="divisionSolicitud">
          <wsdl:part name="parameters" element="calc:divisionSolicitud">
          </wsdl:part>
      </wsdl:message>
      <wsdl:message name="divisionRespuesta">
          <wsdl:part name="parameters" element="calc:divisionRespuesta">
          </wsdl:part>
      </wsdl:message>
      <wsdl:message name="divisionPorCero">
          <wsdl:part name="divisionPorCero" element="calc:divisionPorCero">
          </wsdl:part>
      </wsdl:message>
      <wsdl:message name="sumarResponse">
          <wsdl:part name="parameters" element="calc:sumaRespuesta">
          </wsdl:part>
      </wsdl:message>
      <wsdl:message name="sumarRequest">
          <wsdl:part name="parameters" element="calc:sumaSolicitud">
          </wsdl:part>
      </wsdl:message>
      <wsdl:portType name="Calculadora">
          <wsdl:operation name="dividir" parameterOrder="parameters">
              <wsdl:input message="calc:divisionSolicitud">
              </wsdl:input>
              <wsdl:output message="calc:divisionRespuesta">
              </wsdl:output>
              <wsdl:fault name="divisionPorCero" message="calc:divisionPorCero">
              </wsdl:fault>
          </wsdl:operation>
          <wsdl:operation name="sumar">
              <wsdl:input message="calc:sumarRequest">
              </wsdl:input>
              <wsdl:output message="calc:sumarResponse">
              </wsdl:output>
          </wsdl:operation>
      </wsdl:portType>
      <wsdl:binding name="CalculadoraServiceSoapBinding" type="calc:Calculadora">
          <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
          <wsdl:operation name="dividir">
              <soap:operation soapAction="" style="document"/>
              <wsdl:input>
                  <soap:body parts="parameters" use="literal"/>
              </wsdl:input>
              <wsdl:output>
                  <soap:body parts="parameters" use="literal"/>
              </wsdl:output>
              <wsdl:fault name="divisionPorCero">
                  <soap:fault name="divisionPorCero" use="literal"/>
              </wsdl:fault>
          </wsdl:operation>
          <wsdl:operation name="sumar">
              <soap:operation soapAction="http://www.dosideas.com/cursos/calculadora/sumar"/>
              <wsdl:input>
                  <soap:body use="literal"/>
              </wsdl:input>
              <wsdl:output>
                  <soap:body use="literal"/>
              </wsdl:output>
          </wsdl:operation>
      </wsdl:binding>
      <wsdl:service name="CalculadoraService">
          <wsdl:port name="CalculadoraSoapPort" binding="calc:CalculadoraServiceSoapBinding">
              <soap:address location="http://localhost:8080/Axis2-15/services/CalculadoraService"/>
          </wsdl:port>
      </wsdl:service>
  </wsdl:definitions>

Proyectos de ejemplo para descargar

Aquí que te podés descargar 2 versiones del proyecto

Ver también