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 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.

 

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.

Nov 28

La maldición de las herramientas

web services internet Es bastante habitual que la gente que empieza a aprender java (o cualquier otro lenguaje de programación) coja el IDE correspondiente (eclipse, netbeans o el que sea)  y se ponga a aprender. Esos novatos van programando y con el tiempo van cogiendo ciertos conocimientos y experiencia en java. Pero desgraciadamente, el IDE les hace no aprender ciertas cosas básicas. No es raro encontrarse gente que lleva programando algún tiempo pero que sería incapaz de compilar o ejecutar un programa java desde línea de comandos, usando los comandos java, el compilador javac, o generar su jar con el comando jar.

Y esto no solo pasa con las cosas básicas ni sólo con los novatos. Cuanto más complejo sea el tema y más nos resuelva un IDE o una herramienta/framework cualquiera, menos cosas aprendemos de ese tema y más dependemos del IDE/herramienta/framework. Cuento mi caso de hace un par de días.

Llevo ya unos días trabajando con Web Services con jax-ws. Cuando empecé con ello, no me leí la documentación completa (soy un impaciente) y en seguida me puse a buscar ejemplos de aquí y de allí para ir haciendo mi propio código. Leí en la documentación que para hacer un Web Services bastaba con ponerle unas anotaciones a la clase (@WebService, @WebMethod, etc), compilarla de forma normal, pasarle la herramienta wsgen que viene con jax-ws y listo. Pues bien, eso hice, montando todo desde el principio con maven y plugins de maven. Y desde luego, pasando ese wsgen automáticamente en la fase posterior al compilado.

Hace un par de días me decidí a hacer un tutorial sobre la aprendido y quise hacerlo más de forma manual, dependiendo lo menos posible de herramientas (eclipse, maven, etc). Así que me puse a ello … y empezaron las dudas y a ponerse de manifiesto las grandes lagunas en mi conocimiento.

El problema desencadenante de todo es que hice todos los pasos para el tutorial sin usar la herramienta wsgen y el web service me funciona. Una clase con las anotaciones correspondientes, un main() que arranca un EndPoint, no se pasa el wsgen y funciona, arrancado desde línea de comandos.

Bueno, según la documentación, después de pasar el wsgen se hace el war para desplegarlo en Tomcat o similar. Será entonces que si usas EndPoint no necesitas pasar wsgen, quizás EndPoint hace todo eso que hace wsgen de forma automática. Pero lo grave de todo es que sí, se supone que hay que pasar wsgen, pero realmente no sé qué hace wsgen (sí lo sé, genera unos fuentes java que no sé exactamente para qué sirven).

Así que nada, seguiré de forma manual, intentaré montar el "hola mundo" en un tomcat y veré si ahí es necesario o no el wsgen… Y luego a pelearse con la parte del cliente, que aunque también la se hacer con herramientas, tampoco entiendo el fondo de todo.

Sep 29

jax-ws y maven

 Siguiendo con los web services y yo, he estado probando herramientas como axis2 y jax-ws. Por supuesto, nada me ha funcionado a la primera y llevo casi dos días peleándome con esto para arrancar un "hola mundo"

Con axis2 el problema es que se me ocurrió hacerme mi propia clase de ejemplo, sin copiar la de los tutoriales. Al final descubro que no soporta enumerados de java y qué casualidad, a mí se me había ocurrido poner uno de los métodos con un parámetro enumerado. Una vez conseguido arrancar un servidor y hacerme un cliente, el servidor me da error cuando intento acceder al web service desde el ciente. Ante las pocas pistas que daba el error, decidí dejar axis-2 de momento y probar con jax-ws.

Con jax-ws me cree un proyecto maven y me puse a ello. Pues bien, las dependencias maven teóricas según la documentación son estas https://jax-ws.dev.java.net/guide/Using_JAX_WS_from_Maven.html . Creo el proyecto, arranco el servidor y todo aparentemente correcto. Accedo desde un navegador a http://localhost:8080/MiServicio?WDSL con el que teóricamente debería obtener el fichero WSDL del servicio… y el servidor da error. Buscando el error por google, me encuentro con esto http://forums.java.net/jive/message.jspa?messageID=222799 Parece que la librería sjsxp de la que depende según maven el jax-ws no es correcta y hay que coger la versión 1.0.1 en vez de la 1.0. Así que me toca "tunear" el pom.xml y hacer esta ñapa

 

<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.1</version>
<exclusions>
<exclusion>
<groupId>com.sun.xml.stream</groupId>
<artifactId>sjsxp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.sun.xml.stream</groupId>
<artifactId>sjsxp</artifactId>
<version>1.0.1</version>
</dependency>
Dicho de otra forma, poner la dependencia de jaxws-rt, decirle que no me traiga su sjsxp y luego poner la dependencia del que yo quiero. Esto ha funcionado bien y ya tengo el servidor arrancado. Ahora toca pelearse con el cliente…. espero no tener demasiados problemas. Luego volveré con axis2.
 
Y aunque todavía tengo que probar más, de momento me gusta mucho más jax-ws que axis2. Los scripts de axis2 generan montones de clases que luego tampoco facilitan tanto la creación de un cliente. jax-ws también genera clases, pero parece que son menos y además quedan "ocultas", ya que genera los .class que luego se empaquetan en el war o jar.
 
Lo segundo que no me ha gustado de axis2 es que aparentemente no tiene posibilidad de hacer un main() que arranque un servidor web con los webservices, dependemos de un Tomcat ajeno con una webapp axis2 instalada sobre la que desplegamos nuestros web services. Bueno, sí tiene un servidor, pero según la documentación puede usarse sin garantías, ya que no está soportado. jax-ws permite hacerse un main y publicar el servicio de una forma tan fácil como esta
public static void main(String [] args) {
   Endpoint.publish("http//localhost:8080/MiServicio", new MiWebService());
   // Aqui puedes seguir haciendo cosas.
}
Bueno, no es nada grave si quieres publicar webservices en internet. Pero en mi caso, necesito que el servidor aparte de publicar los webservices, haga más cosas.