Apr 25

Jugando con Web Services e Hibernate. Método setter de las Collection.

 En un rato de entretenimiento me he dedicado a generar las clases java a partir de unos WSDL y XSD de Web Service. Para ello he usado wsdl2java de Apache CXF. Para que el entretenimiento fuera mayor, decidí hacer persistentes estas clases generadas usando Hibernate. Como no eran muchas clases y quiero aprender un poco de Hibernate, mi gran asignatura pendiente, me decido a hacer los ficheros hbm.xml a mano.

Me pongo a ello, hago mis primeras pruebas y tras corregir los errores evidentes, llega el que me ha llamado la atención y es motivo de este post.

Caused by: org.hibernate.PropertyNotFoundException: Could not find a setter for property poligonPositions in class

 

El error parece claro, en la propiedad PoligonPositions (un List dentro de la clase AreaGeografica), Hibernate no puede encontrar un método set de acceso a dicha propiedad, es decir, un setPoligonPositions(List). Miro la clase en cuestión y efectivamente, no hay método set. ¡Qué raro!, parece que el wsdl2java no ha generado el método set.

Venga a mirar la herramienta wsdl2java a ver si tiene alguna configuración o algo y no, no la he encontrado. Rebuscando en internet me encuentro que la especificación jax-ws dice que las colecciones no deben tener método set(). La clase generada, en el atributo, tiene añadido este comentario

/**
* Gets the value of the poligonPositions property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the poligonPositions property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getPoligonPositions().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Position }
*
*
*/
public List<Position> getPoligonPositions() {
 

 

que básicamente quiere decir que no se pone el set y que debemos llamar a getPoligonPositions().add(newItem).

Bueno, parece que por el lado de los Web Services no hay solución a esto, poner el set() a mano tampoco parece adecuado en una clase que ha sido generada automáticamente y que se puede "machacar" en cualquier momento.

Afortunadamente, en la parte de Hibernate si tenemos solución. En el fichero hbm.xml de la clase se puede poner algo como

<hiberante-mapping>
   <class …..>
      ….
     <list name="poligonPositions" … access="field">
         …
    </list>
     …
   </class>
</hibernate-mapping>

El atributo access="field" hace que hiberante, en vez de usar el método set(), vaya directamente al atributo.

 

Apr 10

CXF: Timeout en los clientes de web services

Cuando hacemos un cliente de web service con apache CXF, podemos fijar el tiempo de timeout que el cliente espera por la conexión y por la respuesta de la siguiente forma

// Creación del cliente web service de una de las muchas maneras posibles a partir de los wsdl en línea y una interfaz
// Calculadora.class.
URL wsdlURL = new URL("http://localhost:8080/ejemplo_cxf/Calculadora?wsdl");
QName SERVICE_NAME = new QName("http://cxf.ejemplos.chuidiang.com/", "CalculadoraImplService");
Service service = Service.create(wsdlURL, SERVICE_NAME);
Calculadora calculadora = service.getPort(Calculadora.class);
……

// Y el cambio del timeout de conexion y respuesta
org.apache.cxf.endpoint.Client client = ClientProxy .getClient(calculadora);

HTTPConduit http = (HTTPConduit) client.getConduit();
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(0);
httpClientPolicy.setReceiveTimeout(0);

http.setClient(httpClientPolicy);

 

El tiempo de timeout en milisegundos, 0 siginifica espera infinita. Los tiempos por defecto son 30 segundos para conexión y 1 minuto para respuesta.

 

Mar 27

Maven y CXF: Generar el ciente de web service a partir del WSDL, eligiendo el nombre del paquete

A partir de los WSDL y con CXF podemos generar el código de nuestro cliente de web service. Por defecto, la herramienta wsdl2java de CXF pondrá a estas clases cliente un paquete elegido a partir del namespace que aparece en el wsdl

Puede no interesarnos esto, sino que quizás queramos poner nuestro propio paquete para esas clases. Desde maven y CXF podemos hacerlo de la siguiente forma

<build>
   <plugins>
      <plugin>
         <groupId>org.apache.cxf</groupId>
         <artifactId>cxf-codegen-plugin</artifactId>
         <version>2.4.2</version>
         <executions>
            <execution>
               <id>generate-sources</id>
               <phase>generate-sources</phase>
               <configuration>
                 <sourceRoot>${basedir}/src/main/java</sourceRoot>
                 <wsdlRoot>${basedir}/src/main/resources/wsdl</wsdlRoot>
                 <wsdlOptions>
                    <wsdlOption>
                       <wsdl>${basedir}/src/main/resources/wsdl/UnFichero.wsdl</wsdl>
                       <wsdlLocation>classpath:/wsdl/UnFichero.wsdl</wsdlLocation>
                       <extraargs>
                          <extraarg>-p</extraarg>
                          <extraarg>mi.propio.paquete</extraarg>
                      </extraargs>
                   </wsdlOption>
 ….

 

Más o menos es lo estándar de maven y CXF para generar el cliente. Suponemos que el wsdl estará dentro del jar, por eso lo hemos puesto con wsdlRoot src/main/resources y con wsdlLocation en el classpath.

El "truco" para conseguir el paquete propio son los <extraargs>, que son -p y el nombre del paquete que queremos. Estas son opciones del comando wsdl2java que viene con apache CXF. Esto es válido para cualquier parámetro que admita el comando wsdl2java y que no esté soportado directamente por el plugin de maven
 

 

Mar 17

Web Services CXF: Errores de versión con jaxb-api

Si tenemos java 6 y estamos haciendo web services, con apache CXF por ejemplo, y obtenemos errores extraños con la versión de Jaxb-api, como estos

Exception in thread "main" java.lang.LinkageError: JAXB 2.0 API is being loaded
from the bootstrap classloader, but this RI (from jar:file:/D:/work/jaxws-ri/lib/jaxb-impl.jar!
/com/sun/xml/bind/v2/model/impl/ModelBuilder.class) needs 2.1 API.
Use the endorsed directory mechanism to place jaxb-api.jar

 

es que estamos teniendo problemas con la versión de determinadas librerías. El JDK tiene incluidas la versión 2.0 de las librerías JAXB 2.0 y JAX-WS 2.0, mientras que nuestra versión de Web Services (apache CXF por ejemplo), necesita la 2.1 (según la excepción). La forma de solucionar esto es poner las librerías JAX-WS 2.1 y JAXB 2.1 en algún sitio de forma que se encuentren antes que las de defecto del JDK.

Si nuestra aplicación es una aplicación de escritorio, debemos colocarlas en los directorios JDK_HOME/lib/endorsed o JDK_HOME/jre/lib/endorsed, según qué estemos usando.

Si nuestra aplicación corre en un contenedor web de aplicaciones, como Apache Tomcat, debemos colocarla en el sitio que nuestro servidor web tenga indicado para ello. En el caso de Tomcat, sería en CATALINA_HOME/endorsed (debemos crear el directorio si no lo está).

Existe también forma de indicarle a java dónde están los directorios "endorsed" en vez de usar los de defecto, de forma que no tendremos que tocar los directorios de instalación de Java. Podremos así meter esos dos jar en un directorio cualquiera en nuestro ordenador e indicarle a Java en el momento de arrancar la máquina virtual, que ese directorio hace de "endorsed"

java -Djava.endorsed.dirs=UN_DIRECTORIO_CUALQUIERA …

 

que sería válido tanto para aplicaciones de escritorio como para Tomcat.

 Referencias:

http://www.java.net/external?url=http://java.sun.com/j2se/1.5.0/docs/guide/standards/

http://weblogs.java.net/blog/ramapulavarthi/archive/2007/01/problems_using.html

Mar 12

Apache CXF: Mostrar mensajes SOAP en el log del cliente

 Si con Apache CXF generamos, a partir de un WSDL, las clases de cliente, nos saldrá una clase UnWebServiceImpl que podemos instanciar. Esta clase tiene un método getUnWebServicePort() para obtener una interfaz que es con la que podemos hacer realmente las llamadas al Web Service

UnWebServiceImpl cliente = new UnWebServiceImpl();
UnWebService interfaz = cliente.getUnWebServicePort();
interfaz.unMetodo(unosParametros);

Nos puede interesar en un momento dado ver exactamente el contenido XML de los mensajes que van y vienen. Esto está contemplado en Apache CXF y se hace fácilmente

org.apache.cxf.endpoint.Client client = ClientProxy.getClient(cliente);

LoggingInInterceptor logIn = new LoggingInInterceptor();
logIn.setPrettyLogging(true);
client.getInInterceptors().add(logIn);

LoggingOutInterceptor logOut = new LoggingOutInterceptor();
logOut.setPrettyLogging(true);
client.getOutInterceptors().add(logOut);
 

Y listo, obtenemos por un lado los de entrada, por otro los de salida. La llamada a setPretty() es para que el XML salga formateado, si no, saldrá todo seguido en una única línea.

 

Jan 14

El WebappClassLoader y ContextClassLoader en Tomcat/Liferay

En el desarrollo nos hemos encontrado con un problema curioso. Estamos desarrollando un portlet con Liferay y necesitamos desde él hacer llamadas AJAX que actualicen parte del portlet sin refrescar la página. Se nos ha ocurrido, en la parte del servidor, hacer un jsp que sería el que reciba estas llamadas desde la parte de javascript, digamos un funcion.jsp. En esa funcion.jsp usamos clases de nuestra parte del servidor del portlet, en concreto, usamos los jar que hay en el WEB-INF/lib de ese portlet.

Pues bien, el problema que se nos ha presentado es que aunque todo parece ir bien tanto en el funcion.jsp como en las clases que se van llamando, hay un sitio concreto en el que hay un problema. Esas clases crean un cliente de web service con Apache CXF para conseguir datos en otros servidores y en la creación de ese cliente nos salta el problema, una excepción fea

ClassCastException: com.sun.xml.ws.client.sei.SEIStub cannot be cast to org.apache.cxf.frontend.ClientProxy [java] at org.apache.cxf.frontend.ClientProxy.

A poco que se hurge con google, encontramos que la versión 6 de java viene con un proveedor por defecto para web services. Java proporciona mecanismos para cambiar ese proveedor por defecto por el que queramos, en concreto y entre otras opciones, basta con poner en el classpath el fichero META-INF/services/javax.xml.ws.spi con una única línea de texto que sería la clase proveedor de web service que queramos uar,  en mi caso, la de apache cxf. La excepción surge si cuando java busca el proveedor no lo encuentra, poniendo entonces el suyo por defecto, que no es compatible con Apache CXF.

Normalmente no debemos preocuparnos de esto, el jar cxf-rt-frontend-jaxws.jar de apache cxf contiene ese fichero debidamente configurado, por lo que si este jar está en el classpath, no debería haber problemas.

¿Por qué entonces surge el problema cuando llamo al funcion.jsp si este jar está en el WEB-INF/lib?. Pues tras varios días de pruebas, google y más pruebas, encontramos que nuestras clases usan por defecto el WebappClassLoader que les proporciona tomcat, por lo que todos los jar en WEB-INF/lib de nuestro portlet están disponibles, pero java, cuando busca el proveedor de servicios, usa Thread.currentThread().getContextClassLoader() que NO le devuelve el WebappClassLoader, sino el de la propia aplicación de Tomcat, por lo que las clases de nuestra aplicación no están disponibles.

Si experimentamos y hurgamos un poco más, vemos que Tomcat, cuando va a llamar a algo de nuestra aplicación web, nos cambia ese ContextClassLoader haciendo algo como

Thread.currentThread.setContextClassLoader(elWebappContextClassLoaderCorrespondiente);

y luego, cuando nuestra aplicación termina de hacer lo que tenga que hacer, Tomcat restaura el ContextClassLoader por el de defecto.

El problema que tenemos entonces es que cuando se llama a nuestra funcion.jsp, no se está cambiando ese ClassLoader. Ahora, sabiendo el problema, la solución debería ser fácil.

La primera y más inmediata es que nosotros mismos, antes de crear el cliente web service, cambiamos ese ContextClassLoader y luego lo restauramos, algo similar a lo que hace Tomcat. Esta opción no me gusta porque no me gusta jugar con los ClassLoader sin saber a ciencia cierta qué hago, y sobre todo porque Tomcat debería cambiarnos el ClassLoader y no lo hace, lo que implica que algo tenemos mal configurado.

Como estamos con liferay y llamamos a funcion.jsp con una URL directa, no a través del portlet liferay, imagino que ahí vienen los problemas. Así que toca "investigar" un poco a ver cómo conseguir que liferay/tomcat consideren funcion.jsp como parte del webapp o portlet y le cambien el classloader al Thread dichoso.

 

Nov 10

Pequeño problema con cliente CXF de Web Service

Estoy haciendo un cliente de Web Service con CXF que luego ira en una página web, de forma que un usuario a través de la página pueda consultar los web services. Y ha surgido un pequeño incordio.

Cojo el wsdl y con la herramienta wsdl2java de CXF genero las clases correspondientes al ciente, todo bien. Hago mi software y todo maravilloso, en local, incluso desplegado en mi propio Tomcat, todo va de perlas.

Lo subo al servidor real….. y error. No encuentra los wsdl. Efectivamente, wsdl2java genera el código del cliente y pone en ese código java la ubicación del wsdl. Luego, cuando usas el cliente, el código java de CXF busca no sé para qué ese wsdl y si no lo encuentra, da error y no funciona nada más.

Yo, en mi ignorancia, pensé que ese wsdl no servía para nada una vez generado el código java, así que no lo incluí como parte de mi proyecto ni en el war. Bueno, la solución parece sencilla, basta meterlo en el war y modifiar las clases de cliente generadas por CXF para que lo busquen dentro del webapp en algún sitio. Pues no, no es tan sencillo. Desgraciadamente el código java dentro de un war tiene muy dificil, si no imposible, saber cual es el directorio en el que está la aplicación web. El directorio por defecto suele ser el del ejecutable tomcat y no el de la aplicación web, por lo que una de las soluciones más socorrida en poner los ficheros que necesitas en el classpath, bien dentro de un jar, bien en el directorio WEB-INF/classes. Pero CXF no entiende de una URL que empiece por "classpath:…."

Por internet buscas y sí, hay cosas, pero normalmente suelen ser si usas spring y en los ficheros de XML de configuración de Spring pones cosas y tal. No es el caso, sólo uso Tomcat.

Total, que muy a mi pesar y por no perder el tiempo, opté por poner los wsdl en un directorio fijo y conocido en el servidor y acceder a ellos con path absoluto. En fin, un asquito y una cosa que se me queda en la lista de tareas para revisar "algún día".

ACTUALIZACIÓN: serhii se ha cargado el problema (lo ha solucionado y ya no es un problema), ver comentario número 7. Está claro que algunos debemos dedicarnos a otra cosa 😉  Muchas gracias, serhii.

Oct 29

Certificados digitales y web services

Algo con lo que también ando liado últimamente. Ofrecemos Web Services sobre https y requerimos que el cliente que intente usarlos presente un certificado digital para poder acceder a ellos, pero no usando WS-Security, sino sobre el mismo protocolo https.

El montaje y la serie de artículos a lo que todo este trabajo ha dado lugar es el siguiente.

Por un lado, montar un Apache http server y un Apache Tomcat. El segundo es el que contiene el war con nuestros web services. El primero, el apache http server, únicamente redirige las entradas de los clientes hacia el tomcat, y también es el que tiene su certificado y acepta o rechaza los certificados de los clientes.

Una vez hecho este montaje, viene la parte de hacer el código de cliente del web service. Para ello uso la librería Apache CXF.y este es el código para conseguir que un cliente CXF pueda presentar su propio certificado de cliente al servidor y a su vez acepte o rechace el certificado del servidor. Y bueno, este ya es problema particular mío, pero también tuve que conseguir que el cliente CXF saliera a través de un proxy corporativo.

Y aparte de todo esto, un pequeño conjunto de artículos más básicos que han ido saliendo sobre web services.

En fin, cogidas por los pelos, pero he aprendido un montón de cosas.

Oct 10

Sigo jugando con los web Service

 El proyecto en el que ando metido es un proyecto de lo más extraño. Son seis paises europeos, cada uno tiene que hacer un portal web y todos estos portales web deben compartir datos entre ellos. Debe ser distribuido, por lo que ninguno de los servidores es más importante que los demás ni tiene centralizada la información. Así que la forma que se ha pensado es que cada portal web vaya a su bola con su propia base de datos y sus propios usuarios, pero que todos ellos ofrezcan unos web services de forma que puedan consultarse datos entre ellos.

¿Y qué es lo extraño?. Pues nada especial, sólo lo de siempre. El portal es relativamente sencillo, no es nada que un "friky" que sepa no pueda hacer en su casa en uno o dos meses de trabajo … pero la coordinación de todo esto es un infierno. Seis cliente de seis países distintos cada uno con su propia empresa contratada. Cualquier decisión por pequeña que sea que afecte a la interfaz de intercambio requiere una lista interminable de correos o incluso esperar a ser decidida en alguna de las reuniones internacionales que se hacen de vez en cuando. Al final ya veo por qué muchos proyectos no son rentables, se paga excesiva gestión, excesivos gestores y demasiada reunión cara. Al final la historia del remero y los supervisores va a ser cierta.

Y así estamos, desarrollando nuestra parte del portal, pero medio bloqueados/jugando con la parte de los web services en espera de interfaz definitiva, protocolos de seguridad definitivos, etc.

Así que en eso es en lo que estoy ahora, jugando con los web services y distintos temas de seguridad: WS-Security, http basic authentication, SAML, etc. Y a resultas de eso y como me gusta escribir todos los "hola mundos" de prueba que hago, salen una serie de tutoriales de web services en la chuwiki.

Por cierto, jugando con metro y con CXF, al final me quedo con CXF. ¿Por qué?. Metro me ha dado un par de problemas extraños en un par de ocasiones. Uno por incompatibilidad de librerías que tiran de librerías que tiran de librerías y el otro no lo tengo muy claro por qué me ha dado el error, pero el mismo código/wsdl funciona sin problemas con CXF. Aparte, me gusta mucho más la documentación de CXF que la de metro.

Y la culpa es del remero.

Sep 20

jax-ws, metro y cxf

No hace mucho que empecé a trabajar con web services. Buscando, buscando, vi como opciones axis y jax-ws. Al final, por sencillez, me decidí por jax-ws … pero hoy he descubierto un pequeño detalle que no sabía.

jax-ws no es más que una especificación y hay varias posibles implementaciones. Las más conocidas son metro y cxf. Yo he estado usando metro, confundiéndolo con jax-ws. Ahora que he descubierto que hay dos, he investigado un poco en internet a ver ventajas de una y otra. No he hecho pruebas ni una búsqueda exhaustiva, simplemente comento aquí algunas cosas que he visto.

Por un lado, metro obliga a poner las anotaciones sobre clases, mientras que cxf admite que se pongan en interfaces. No es algo demasiado importante, pero siempre es más elegante tener definido el web service sobre una interfaz.

Metro sólo soporta SOAP, mientras que cxf soporta también otros protocolos. De hecho, una misma interfaz puede llevar simultáneamente anotaciones de SOAP y de REST, de forma que la clase que lo implementa no necesita saber qué tipo de web service habrá detrás.Por su parte, Metro no soporta REST. La implementación de esta gente para REST está en un proyecto separado, Jersey.

Tanto Metro como CXF tienen plugin de maven para generar el WSDL a partir de la clase java o para generar las clases java a partir del WSDL.

Una cosa que he leído pero no he acabado de entender es que parece ser que hay diferencias a la hora de tratar/generar código en los parámetros de un WebMethod si estos parámetros tienen cierta complejidad, como ser colecciones. Intentaré echarle un ojo con más calma a ver.