Archive for the 'howto' Category

Particularidades de Java2WSDL en Axis

Hace un par de semana me estuve peleando con los scripts de Axis que permiten crear documento WSDL a partir de una clase Java, Java2WSDL y viceversa, generar el skeleton o stub Java a partir de un documento WSDL, WSDL2Java. Particularmente la herramienta java2wsdl tiene ciertos detalles que pueden llegar a ser molestos si no se sabe como abordarlos. A continuación os cuento los problemas que tuve y las soluciones que adopté.

 

1. WSDL es un estándar web.
La afirmación anterior implica que la información que puede contener este documento debe ser independiente de cualquier plataforma, arquitectura, sistema operativo o lenguaje de programación, por tanto, en la sección donde se define los tipos no puede haber referencia a ningún tipo específico Java, tal como List, ArrayList, Map, ….

Solución:
Cualquier método que quiera ser publicado a través de un servicio web debe tener como parámetros de entrada y/o salida, tipos estándares, esto es en Java, tipos primitivos, arrays u objetos propios serializables que contengan tipos estándares.

Ejemplo
Supongamos que tenemos una clase Foo que queremos publicar como servicio web y que contiene un tipo no estándar, por ejemplo una Lista.

public class Foo {
  private List<String> lstStrings;

  public List<String> getLstStrings() {
    return this.lstStrings;
  }

  public void setLstStrings(List<String> lstStrings){
    this.lstStrings = lstStrings;
  }
  ...
}

Bien, pues si queremos que la herramienta java2wsdl genere un documento WSDL correcto debemos transformar el código anterior en el siguiente:

public class Foo {
  private List<String> lstStrings;
  private String[] arrayStrings;     // Según la versión de axis, es necesario declararlo o no

  public List<String> getLstStrings() {
    return this.lstStrings;
  }

  public void setLstStrings(List<String> lstStrings) {
    this.lstStrings = lstStrings;
  } 

  public String[] getArrayStrings() {
    if ( getLstStrings() != null ) {
      return getLstStrings().toArray(new String[getLstStrings().size()]);
    }
    return new String[0];
  }

  public void setArrayStrings(String[] arrayStrings) {
    this.setArrayStrings(Arrays.asList(arrayStrings));
  }
  ...
}

 

2. No están soportados los modelos de datos con clases internas.
Actualmente el script no genera correctamente el conjunto de tipos del documento WSDL si el método de la clase a publicar contiene parametros cuyos tipos sean clases internas.

Este error es debido a un fallo en el jar annogen-0.1.0; existe un parche oficioso que genera el WSDL correctamente pero si usamos wsdl2java para generar el cliente, la invocación al servicio dará un fallo.

Solución:

  1. Sacar las clases internas de la clase del servicio.
  2. Utilizar el jar oficioso de Deepal Jayasinghe y crear el stub sin utilizar WSDL2Java.

 

3. Nuestro servicio lanza una excepción.
Siempre que nuestro servicio lanza una Exception la herramienta java2wsdl intenta mapear el tipo de la excepción a un tipo WSDL. Esto siempre produce el código :

...
<xs:complexType name="MyException">
  <xs:complexContent>
    <xs:extension base="xs:Exception" />
    <!-- o <xs:extension base="xs:RuntimeException" /> -->
    <xs:sequence />
  </xs:complexContent>
</xs:complexType>
...

Al final java2wsdl busca la clase padre de la que extiende nuestra exception para mapearla según la semántica que permite WSDL, sin embargo, ni xs:Exception ni xs:RuntimeException existen como tipo estándares de XML-Schema lo que provoca la existerncia de warnings/errores de los editores XML que validan los documentos.

Solución: La única solución que he encontrado es hacer el siguiente cambio :

...
<xs:complexType name="MyException">
  <xs:complexContent>
    <xs:extension base="xs:anyType" />
    <!-- o <xs:extension base="xs:anyType" /> -->
    <xs:sequence />
  </xs:complexContent>
</xs:complexType>
...

Eso si, si se genera un cliente a partir de este documento WSDL se deberán modificar las clases asociadas a las excepciones para que extiendan de RuntimeException o Exception o cualquier otra.

 

4. <soap12:fault />.
Cuando se crea un documento WSDL para que pueda ser utilizado por las dos versiones de SOAP existentes, 1.1 y 1.2, la parte del binding de SOAP 1.2 es incorrecta.

La herramienta java2wsdl genera un código tal como este :

...
<wsdl:binding...>
  <soap12:opertation ... />
  <wsdl:operation ...>
    ...
    <wsdl:fault ...>
      <soap12:fault use="literal" name="MyException" />
    </wsdl:fault>
  </wsdl:operation>
</wsdl:binding>
...

pues el código anterior falla, el elemento <soap12:fault use="literal" name="MyException" /> no es correcto y, por ejemplo, la herramient wsdl2java no puede crear un cliente correcto a partir del WSDL

Solución: Cambiar <soap12:fault use="literal" name="MyException" /> por <soap:fault use="literal" name="MyException" />

 

Si habéis tenido problemas parecidos y los habéis resuelto de una forma distinta a la mía, por favor, no dudéís es compartirlo para que todos salgamos ganando.

 

Javadoc con Diagramas de Clases usando Graphviz + UMLGraph + Maven

Una de las herramientas más importantes con las que cuenta la plataforma Java es, sin duda, el javadoc. A través de ella es posible crear una referencia completa y homogénea de todo el conjunto de clases definidas es nuestro proyecto.

Toda página de una clase del javadoc está dividida en distintas zonas, la primera de ellas muestra la situación jerárquica de la clase respecto a la clase padre Object. Bajo la situación jerárquica aparece la lista de interfaces que implementa si existe alguna y, por último, la lista de clases que la extienden. Para las interfaces aparece la lista de superinterfaz en primer lugar y la lista de subinterfaces en segundo.

Aunque esta información pueda ser suficiente en muchos casos, no estaría de más completarla con diagramas de clases que permitan una rápida visualización de la jerarquía de clases de nuestro proyecto. Ese es el objetivo de este post, añadir diagramas de clases UML a nuestro javadoc.

Diagrama UML generado con UMLGraph

Para ello necesitaremos dos cosas básicamente :

  • Graphviz, librería que permite la generación de gráficos.
  • UMLGraph plugin para maven que sobreescribe el doclet que genera el javadoc para insertar los gráficos generados por Graphviz.
  • Maven

A continuación vamos a ver como instalar Graphviz y como configurar el pom.xml del proyecto maven para que use el plugin UMLGraph.

Instalación de Graphviz

Hay varias formas de hacerlo, bajando el fuente y compilando, haciendo un checkout del repositorio CVS o dejando que el sistema de paquetes de tu sistema operativo lo haga todo por tí ;-). Esto último es lo que haremos pues la finalidad de éste post es otra.

sudo apt-get update
sudo apt-get install graphviz
man dot

La última entrada es sólo para comprobar que el paquete ha sido instalado correctamente.

Modificación del fichero pom.xml del proyecto

Simplemente se debe añadir al fichero las siguientes líneas de código

...
<reporting>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-javadoc-plugin</artifactId>
      <configuration>
        <doclet>gr.spinellis.umlgraph.doclet.UmlGraphDoc</doclet>
        <docletArtifact>
          <groupId>gr.spinellis</groupId>
          <artifactId>UmlGraph</artifactId>
          <version>4.6</version>
        </docletArtifact>
        <additionalparam>
          -inferrel -inferdep -quiet -hide java.* -collpackages java.util.* -qualify
          -postfixpackage -nodefontsize 9
          -nodefontpackagesize 7
        </additionalparam>
      </configuration>
    </plugin>
  </plugins>
</reporting>
...

Para generar el javadoc se ejecutará el comando mvn javadoc:javadoc dentro del directorio
donde se encuentra el fichero pom.xml.

Javadoc de la clase Nivel 1

Configurar Axis 2 + Spring 2 + Weblogic 9.2

Aunque existen en la red muchos posts acerca de este tipo de configuración, no hay ninguno, que yo haya leído, que explique todo el proceso de forma sencilla. Este es el propósito de este post, describir de una manera cercana la configuración de Axis 2 para que use el contexto de Spring todo ello sobre el servidor Weblogic 9.2.

Antes de empezar a describir todos los detalles de la configuración vamos a intentar explicar el porqué de ésta, particularmente la interacción entre Axis y Spring.

Sin Spring

Supongamos que en nuestra aplicación no usamos Spring, el fichero service.xml de un servicio de Axis sería como sigue:

<service name="ServicioWebSinSpring">
  <parameter name="ServiceClass">
    com.jmaguilar.springaxisweblogic.ws.services.ServicioWebSinSpring
  </parameter>
  <operation name="saluda">
    <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
  </operation>
</service>

En el código anterior se indica que el servicio web tiene el nombre de ServicioWebSinSpring, está asociado a la clase com.jmaguilar.springaxisweblogic.ws.services.ServicioWebSinSpring y tiene publicado el método saluda. Simple.

Con Spring

Ahora la cosa cambia, Axis no debería poder instanciar la clase del servicio que va a usar, sino que debe solicitarla a Spring para que le proporcione la instancia ya creada y configurada previamente, de forma declarativa, a través del applicationContext-manager.xml.

En primer lugar vamos a ver el fichero de configuración de Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <bean id="applicationContext" class="org.apache.axis2.extensions.spring.receivers.ApplicationContextHolder" />
  <bean id="servicioWebConSpring" class=" com.jmaguilar.springaxisweblogic.ws.services.ServicioWebConSpring" />
</beans>

El fichero define dos beans, applicationContext y servicioWebConSpring. El primero de ellos será utilizado por el motor de Axis para recuperar el applicationContext y, a partir de él, extraer cualquier bean ahí definido. Si hemos leido con atención el código nos habrá llamado la atención la clase asociada a este bean, ApplicationContextHolder se encuentra en el jar axis2-spring-x.y, que viene en el fuente de Axis. El segundo bean, servicioWebConSpring, es nuestro servicio.

Para indicar al motor de Axis que debe recuperar el bean servicioWebConSpring se debe incluir el siguiente código en el fichero service.xml del nuestro servicio web.

<service name="ServicioWebConSpring">
  <parameter name="ServiceObjectSupplier" locked="false">org.apache.axis2.extensions.spring.receivers.SpringAppContextAwareObjectSupplier</parameter>
  <parameter name="SpringBeanName" locked="false">servicioWebConSpring</parameter>
  <operation name="saluda">
    <messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
  </operation>
</service>

En este caso, dentro del fichero service.xml se indica mediante el parámetro ServiceObjectSupplier la clase que proporcionará los objetos de servicio, SpringAppContextAwareObjectSupplier que también está dentro del jar axis2-spring-x.y. A través del parámetro SpringBeanName se especifica el bean que actuará como servicio, ojo se debe especifica el nombre del bean, no su clase. Y, por último, se indican las operaciones disponibles.

Weblogic 9.2

Sólo faltan por ultimar los detalles para que el despliegue en Weblogic sea correcto.

web.xml

<!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><!-- indica los ficheros que contienen los beans a inyectar -->
      classpath*:applicationContext-manager.xml
    </param-value>
  </context-param>

  <listener><!-- permite la inyeccion de los beans definidos ficheros indicados anteriormente -->
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- AXIS 2 Configuration -->
  <servlet>
    <servlet-name>AxisServlet</servlet-name>
    <servlet-class>org.apache.axis2.transport.http.AxisServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>AxisServlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>

  ...

</web-app>

Debido a que el servidor de BEA tiene una implementación propia del pull-parser StAX es necesario, para el correcto funcionamiento de Axis, tirar de los jars que vienen incluidos en la distribución de Axis. Para ello es necesario indicar a Weblogic que la preferencia en el classpath es para los jars contenidos en el directorio WEB-INF/lib, ésto se hace a través del fichero weblogic.xml que debe situarse bajo WEB-INF.

<?xml version="1.0" encoding="ISO-8859-1"?>
<weblogic-web-app xmlns="http://www.bea.com/ns/weblogic/90">
  <container-descriptor>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
  </container-descriptor>
</weblogic-web-app>

Configurar proxy en Subversion y Maven

Hoy he empezado a trabajar en las oficinas del cliente del proyecto que me ocupará los próximos meses y he tenido que configurar todo mi sistema para que se conecte a Internet a través de un proxy. Aunque esta configuración es trivial, voy a comentar los ficheros que hay modificar para que tanto Maven como Subversion pueden acceder a repositorios externos a través de un proxy.

Maven

En Maven el fichero que tiene que ser modificado se llama settings.xml y está situado bajo el directorio $MAVEN_HOME/config, siendo $MAVEN_HOME el directorio base de instalación de Maven.

Dentro de este fichero será necesario buscar el siguiente bloque de código :

<proxies>
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net,some.host.com</nonProxyHosts>
</proxy>
</proxies>

Simplemente debéis establecer los parámetros de la configuración de acceso al proxy en los elementos correspondientes, por ejemplo :

<proxy>
<!-- Parámetro opcional -->
<id>Proxy oficina cliente</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyUser</username>
<password>proxyPassword</password>
<host>10.23.22.43</host>
<port>80</port>
<!-- Direcciones a excluir de pasar por el proxy -->
<nonProxyHosts>localhost, 10.23.23.44</nonProxyHosts>
</proxy>
</proxies>

Subversion

La configuración de Subversion es muy parecida, aunque permite mayor flexibilidad. En este caso el fichero a modificar se encuentra en la ruta /etc/subversion/servers. La flexibilidad que aporta Subversion a la hora de configurar el acceso a un repositorio via proxy está reflejada en la posibilidad de configurar un proxy global para todos los repositorios o la posibilidad de establecer la configuración de un proxy para grupo de repositorios.

El ficheros de configuración base es el siguiente :

### This file specifies server-specific protocol parameters,
### including HTTP proxy information, and HTTP timeout settings.
###
### The currently defined server options are:
###   http-proxy-host            Proxy host for HTTP connection
###   http-proxy-port            Port number of proxy host service
###   http-proxy-username        Username for auth to proxy service
###   http-proxy-password        Password for auth to proxy service
###   http-proxy-exceptions      List of sites that do not use proxy
###   http-timeout               Timeout for HTTP requests in seconds
###   http-compression           Whether to compress HTTP requests
###   neon-debug-mask            Debug mask for Neon HTTP library
###   ssl-authority-files        List of files, each of a trusted CAs
###   ssl-trust-default-ca       Trust the system 'default' CAs
###   ssl-client-cert-file       PKCS#12 format client certificate file
###   ssl-client-cert-password   Client Key password, if needed.
###
### HTTP timeouts, if given, are specified in seconds.  A timeout
### of 0, i.e. zero, causes a builtin default to be used.
###
### The commented-out examples below are intended only to
### demonstrate how to use this file; any resemblance to actual
### servers, living or dead, is entirely coincidental.

### In this section, the URL of the repository you're trying to
### access is matched against the patterns on the right.  If a
### match is found, the server info is from the section with the
### corresponding name.

[groups]
# group1 = *.collab.net
# othergroup = repository.blarggitywhoomph.com
# thirdgroup = *.example.com

### Information for the first group:
# [group1]
# http-proxy-host = proxy1.some-domain-name.com
# http-proxy-port = 80
# http-proxy-username = blah
# http-proxy-password = doubleblah
# http-timeout = 60
# neon-debug-mask = 130

### Information for the second group:
# [othergroup]
# http-proxy-host = proxy2.some-domain-name.com
# http-proxy-port = 9000
# No username and password, so use the defaults below.

### You can set default parameters in the 'global' section.
### These parameters apply if no corresponding parameter is set in
### a specifically matched group as shown above.  Thus, if you go
### through the same proxy server to reach every site on the
### Internet, you probably just want to put that server's
### information in the 'global' section and not bother with
### 'groups' or any other sections.
###
### If you go through a proxy for all but a few sites, you can
### list those exceptions under 'http-proxy-exceptions'.  This only
### overrides defaults, not explicitly matched server names.
###
### 'ssl-authority-files' is a semicolon-delimited list of files,
[global]
# http-proxy-exceptions = *.exception.com, www.internal-site.org
# http-proxy-host = defaultproxy.whatever.com
# http-proxy-port = 7000
# http-proxy-username = defaultusername
# http-proxy-password = defaultpassword
# http-compression = no
# No http-timeout, so just use the builtin default.
# No neon-debug-mask, so neon debugging is disabled.
# ssl-authority-files = /path/to/CAcert.pem;/path/to/CAcert2.pem

Como puedes observar, en la primera parte del fichero se definen los grupos, groups, con la url de los repositorios asociados.

[groups]
group1 = *.collab.net
othergroup = repository.blarggitywhoomph.com
thirdgroup = *.example.com

Observad que se permite el mapeo de todos los subdominios de un dominio mediante el uso de *.

En la segunda parte del fichero, se especifica la configuración del proxy asociada a cada grupo. En primer lugar se especifica, [entre corchetes] el nombre del grupo de repositorios a configurar y posteriormente los parámetros propios de configuración del proxy, cuyos nombres son autoexplicativos.

[group1]
http-proxy-host = proxy1.some-domain-name.com
http-proxy-port = 80
http-proxy-username = blah
http-proxy-password = doubleblah
http-timeout = 60

Por último, es posible definir una configuración global de acceso a todos los repositorios, se realiza bajo la etiqueta [global]

[global]
http-proxy-exceptions = localhost, 10.23.23.44
http-proxy-host = 10.23.22.43
http-proxy-port = 80
http-proxy-username = proxyUser
http-proxy-password = proxyPassword

Para una configuración más avanzada consultar la referencia oficial.