Jun 16

Comunicación Consola Servidor : Hazelcast, Vert.x, REST

Logo HazelcastEn el mismo proyecto del post anterior, estaba replanteandome como hacer que la interfaz de usuario (consola, aplicación java de escritorio) se conecte con el servidor (ejecutable java que no es servidor web ni de aplicaciones). Habitualmente abrimos un socket TCP/IP y definimos la mensajería. Tenemos librerías ya preparadas para esto, es eficiente y lleva muchos años funcionando. Pero a todos nos gusta jugar. Para este proyecto, me estoy planteando usar algún otro tipo de comunicación.

En otros proyectos que he visto (uno de los cuales lo he heredado), usan servicios web REST para todo lo que sea petición/respuesta desde la consola al servidor. Cuando el servidor quiere enviar algo de motu propio a todas las consolas, se usa ActiveMQ como cola de mensajes. Como el proyecto está con Spring Framework, hay soporte para las dos cosas haciendo fácil su codificación. Este mecanismo también está funcionando sin problemas.

Pero lo dicho, quiero seguir jugando.

Lo del servicio REST … ¿podemos eliminarlo usando sólo colas de mensajes o algo parecido? ¿Y en vez de una cola de mensajes ActiveMQ podemos usar algo más sencillo y ligero?

Pues me plantee hacerlo primero todo con Hazelcast. Si pongo modelos compartidos entre consola y servidor y con el mecanismo que tiene Hazelcast de publicación/suscripción a base de topics, quizás me bastaría sólo Hazelcast para reemplazar los web services REST y a ActiveMQ.

Pues no. Por un lado, Hazelcast no ofrece un mecanismo de petición/respuesta. Si uso un topic como petición de forma que el servidor se suscribe a ese topic de petición y cuando la consola quiere algo, publica en ese topic qué quiere, el servidor no tiene forma de responder. Habría que crear un topic temporal de respuesta, de forma que la consola que ha hecho la petición se suscribe a ese topic temporal y recoge la respuesta. Esto lo ofrece ActiveMQ de forma transparente, pero con Hazelcast habría que currárselo.

Y me surgió un segundo tema. Si en base de datos tenemos guardadas una serie de cosas y la consola las quiere, lo normal es pedírselas al servidor y el servidor en respuesta las devuelve. ¿Cómo lo hace en el caso de Hazelcast? Tendría que meterlo en algún tipo de colección de Hazelcast en memoria. ¿Y qué pasa sin son muchos datos?. Pues que hay que cargarlos todos en memoria, o inventar un mecanismo de cargas parciales, con paginados, etc, etc.

Así que descarté el uso exclusivo de Hazelcast. Lo he dejado, pero principalmente porque me vale para el modelo “vivo” de datos. En la aplicación se verá un mapa con barcos moviéndose. Estos barcos moviéndose en lo que llamo modelo “vivo” de datos, los datos que actualmente se presentan en todas las consolas y que el servidor se encarga de ir actualizando para todas. Esto es una ventaja frente a lo que teníamos. Sin Hazelcast (o un equivalente), el servidor tenía que enviar todos los cambios de ese modelo “vivo” a través de eventos por socket TCP/IP o ActiveMQ y las consolas debías actualizar su copia de dicho modelo en base a esos eventos. Con Hazelcast, el servidor toca la colección compartida y la consola ve dicha colección actualizada en todo momento. Es Hazelcast el que se “come” por debajo todo el tema de eventos de cambios del modelo y la actualización en el lado de la consola. La consola puede suscribirse a cambios en esa colección compartida, hazelcast ofrece esa posibilidad, pero para hacer otras cosas que necesite, como borrar el icono del barco en el mapa o desplazarlo, pero no para actualizar esa misma colección de barcos que ya está actualizada

Bien, no me vale Hazelcast para todo. ¿Qué tal Vert.x?. Tiene un EventBus, más sencillo de manejar que ActiveMQ, contempla la petición/respuesta, usa (o puede usar) Hazelcast por debajo por lo que encaja con el Hazelcast que me he planteado usar. Así que eso me planteé. Dejo Hazelcast para el modelo vivo y usar EventBus de Vert.x para peticion/respuesta y publicación/suscripción con Topics.

Me pongo a hacer mis pruebas y … ¡¡Sorpresa!!. Vert.x no garantiza el orden en la entrega de los eventos. Si garantiza que si un publicador envía eventos, un suscriptor concreto los recibe en el orden que el publicador los ha enviado. Pero no garantiza nada más. Si un publicador publica en varios topic y cada topic tiene un suscriptor distinto, los suscriptores pueden recibir los eventos en cualquier orden, no necesariamente en el orden en que el publicador los ha enviado. Y eso me hizo polvo. Para petición/respuesta a veces se necesita que las cosas vayan en orden, que no me contesten antes a lo que he pedido después. Así que que para petición/respuesta descarté Vert.x, seguro que puedo hacer apaños para garantizar el orden, pero va en contra de la filosofía de Vert.x y me parece una fuente importante de bugs si los desarrolladores se despistan.

¿Y para publicación/suscripción con topics, sin respuesta?. Bien, tengo Hazelcast que también lo hace. No voy a meter un framework por encima sólo para eso. Lo dejo aparcado por si en un momento dado Hazelcast se me queda cojo en este asunto y Vert.x me lo solucionara, pero lo dicho, queda aparcado.

¿Cómo hago al final lo de petición/respuesta?. Pues al final he dejado los web services REST.

¿Qué creo que he ganado con todo este cambio aparte de jugar y aprender? Principalmente el tema del modelo “vivo”. La colección compartida de Hazelcast entre consola y servidor me ahorra enviar eventos de cambios en ese modelo en el servidor, recogerlos en la consola y reconstruir en consola con código el mismo modelo que tiene el servidor.

En cuanto a topics, AciveMQ frente a Hazelcast, desde luego es mucho más potente y con más posibilidades ActiveMQ, pero es más complejo y no necesito todas esas posibilidades, tiro de momento por la opción más simple de Hazelcast.

 

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.