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 16

google-gson para manejar json desde java

jsongoogle-gson es una pequeña librería java que nos permite convertir una clase java en un String con formato JSON y viceversa. Es útil si hacemos una aplicación web con java en el lado del servidor y usamos el formato JSON para enviar y recibir datos al navegador.

Un pequeño ejemplo, hagamos un par de clases java sencillitas, dos bean sin necesidad de métodos set y get, pero con un constructor sin parámetros. Una de ellas tiene como atributo, entre otros, a la otra clase.

class Dato {
   private double numero;
   private int array[];
   private String texto;
   private LinkedList<Boolean> lista;
   private DatoInterno dato;

   public String toString() {
      return texto + " " + numero + " " + Arrays.toString(array) + " "
        + Arrays.toString(lista.toArray()) + " " + dato;
   }
}

class DatoInterno {
   private float valor;

   public String toString() {
      return "" + valor;
   }
}
 

Le hemos puesto unos métodos toString() para ver por pantalla fácilmente su contenido

Una vez instalada la librería en nuestro proyecto (es un solo jar), podemos hacer un código como este

Gson gson = new Gson();

Dato dato = gson.fromJson("{numero:1.4,texto:dos,array:[1,2,3,4],lista:[true,false],dato:{valor:11.22}}",
        Dato.class);
System.out.println(dato.toString());

 

que da de resultado algo como esto

dos 1.4 [1, 2, 3, 4] [true, false] 11.22

es decir, la salida de nuestro System.out.println() en el que hemos obtenido una clase dato rellena con los datos del texto json. Por otro lado, también tenemos la posibilidad inversa. Si una vez relleno el dato llamamos a

System.out.println(gson.toJson(dato));

obtendremos la siguiente salida

{"numero":1.4,"array":[1,2,3,4],"texto":"dos","lista":[true,false],"dato":{"valor":11.22}}

es decir, el texto JSON correspondiente a lo que tenemos dentro del dato, pero perfectamente formateado y no la "guarrería" que había puesto yo inicialmente en el código.

En fin, sencilla de usar y bastante potente.

 

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 17

Constantes en los beans y JSTL

A veces, en una aplicación web con JSTL nos vendría bien tener una constante definida en uno de los beans de java, estilo

public class UnaClase {
   public static final String UNA_CONSTANTE = "algo";
   …
}

y luego poder llamarla desde JSTL con algo como

<jsp:useBean id="bean" class="com.chuidiang.UnaClase"></jsp:useBean>

<c:out value="${bean.UNA_CONSTANTE}" />

Desgraciadamente, eso no funciona. JSTL presupone que UNA_CONSTANTE es un atributo del bean y va a convertir ese ${bean.UNA_CONSTANTE} en una llamada al método bean.getUNA_CONSTANTE() que no existe.

La solución es fácil, basta con crear un método getUNA_CONSTANTE() en el bean que devuelva el valor de la constante. Unicamente tenemos que tener en cuenta que ese método no puede ser static, puesto que si no JSTL tampoco lo llamará (por debajo, probablemente usa la introspección de java y no busca métodos estáticos). El bean quedaría así

public class UnaClase {
   public static final String UNA_CONSTANTE = "algo";

   public String getUNA_CONSTANTE () {
      return UnaClase.UNA_CONSTANTE;
   }

}

que sí funciona como esperamos.

Nov 26

Abrir conexiones java contra un servidor https

Me ha tocado abrir conexiones desde código java contra  un servidor https que requiere certificado de cliente. Me he puesto a ello y he aprendido algunas cosas.

Cuando hacemos un cliente java contra un servidor https, java verifica que el certificado que presenta ese servidor es válido. Por ello, si no los tenemos, necesitamos obtener dichos certificados y meterlos en un almacén de certificados que luego le diremos a java que es el almacén de certificados en los que confiamos. Una forma de obtener el certificado es visitar la página con el navegador y pinchar el el candadito que sale junto a la URL. Ahí podemos ver los datos del certificado y guardarlo en un fichero.

Con la herramienta keytool que viene con java podemos meter esos certificados en un fichero único, de nombre genérico "KeyStore". De hecho, el fichero por defecto que crea/usa esta herramienta keytool suele ser $HOME/.keystore. La herramienta keytool nos pedirá una clave cuando cree el fichero para protegerlo y nos pedirá la misma clave cada vez que queramos ver, añadir o borrar certificados de ese fichero. El formato de este fichero se conoce como "JKS" y es propio de java.

El certificado de cliente habitualmente nos lo darán como un fichero, protegido por una clave y su formato suele ser "PKCS12", este es un formato estándar entendido por la mayoría de las aplicaciones no java. Su extensión suele ser .p12

Pues bien, en java tenemos dos opciones para indicar cual es nuestro KeyStore y cual es nuestro certificado de cliente. Una es por medio de propiedades de sistema. Podemos arrancar nuestro código java con propiedades de este estilo

java -Djavax.net.ssl.trustStore=c:/usuario/.keystore ….

y necesitamos hasta 6 de estas propiedades: fichero, clave del fichero y formato del fichero, para el KeyStore y para el certificado del cliente.

Y la otra opción es codificarlo en java directamente, usando clases como SSLContext, TrustStoreFactory, etc, etc. Algo más complejo, pero que nos dará más versatilidad si queremos abrir varias conexiones con juegos de certificados distintos.

Cómo no, todo esto con más detalle en la chuwiki.

 

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.

May 13

Tutorial de java ee

Con el tema de JSF, estoy leyendo poco a poco, en las idas y venidas en tren al trabajo, el tutorial de java ee de sun. No, no te asustes, no me estoy leyendo las mil y pico páginas. Sólo el capítulo de JSF.

Las primeras páginas del capítulo me parecieron más o menos bien. No tenía ni idea de JSF y leyendo me he ido enterando. Pero según avanzo en el capítulo, cada vez me gusta menos el tutorial. Me da la impresión de que van repitiendo una y otra vez lo mismo, a base de pinceladas, pero dando en cada repetición alguna pincelada más. Por llevarlo de forma más cómoda, imprimo treinta o cuarenta hojas, sin encuadernar de ninguna forma y voy leyendo. En más de una ocasión me ha dado la impresión de haberme equivocado de hoja al leer, porque lo que leo me suena haberlo leído ya palabra por palabra. Y resulta que no, simplemente vuelven a repetirlo más adelante y le añaden algo más, un poco más de detalle.

En fin, empieza a resultar repetitivo y algo pesado. Creo que voy a ir saltando a aquellas partes que más me interesen o de las que todavía no he leído nada y dedicarme más a jugar con eclipse, tomcat y compañía.

Apr 16

Jugando con javadocx

Javadocx es una librería java que permite generar documentos docx

 La gente de javadocx se puso en contacto conmigo a través de twitter. Querían que publicara una nota de prensa que anunciaba el lanzamiento de la versión 2.0. Así que me decidí primero a probar un poco la librería e incluso hacer algún pequeño tutorial en la Chuwiki si se terciaba.

La librería se baja en forma de fuentes java que necesitamos compilar con ant. Así que me descargué también ant, ya que hace tiempo que reemplacé su uso por maven y no lo tenía instalado. El compilado fue bien a la primera, salvo por unos pequeños warnings relativos a algo de unos caracteres extraños de un idioma extraño. No sé si el warning es importante, pero lo comento más abajo.

Miro la documentación en la página web de javadocx para ver cómo empezar. La documentación me remite a los ejemplos java que vienen con la librería, así que me voy a mirarlos. La verdad es que los ejemplos son muy sencillos y al ejecutarlos se genera el docx sin problemas. Casi nada más verlos y ejecutarlos, decidí que no merecía la pena hacer un tutorial en la Chuwiki. El tutorial, de hacerlo, sería prácticamente copiar y pegar esos ejemplos.

Me llevé una pequeña sorpresa al abrir el docx generado con el word. Nada más abrirlo me salen un par de errores, pero aceptando la ventana de error, el documento se abre correctamente. Comentando el problema con la gente de javadocx, me comentan que es un caso raro que pasa con algunas versiones concretas de sistema operativo/máquina virtual java y se debe a caracteres chinos y japoneses que no reconoce correctamente. Qué casualidad, justo los warnings que me dio al compilar. La verdad es que tengo un Windows Vista muy machacado (lleva casi cuatro años instalado) y yo soy un "culo inquieto", por lo que estoy continuamente instalando y desinstalando programas, librerías, herramientas y todo lo que se me ocurre.

Lo otro que he echado de menos es una documentación con las opciones que se pueden poner en los distintos métodos. Me explico. El siguiente ejemplo que viene con la librería genera un docx con un párrafo

CreateDocx docx = new CreateDocx("docx");
String text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"
+ " sed do eiusmod tempor incididunt ut labore et dolore magna "
+ "aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
+ "ullamco laboris nisi ut aliquip ex ea commodo consequat. "
+ "Duis aute irure dolor in reprehenderit in voluptate velit "
+ "esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
+ "occaecat cupidatat non proident, sunt in culpa qui officia "
+ "deserunt mollit anim id est laborum.";
HashMap paramsText = new HashMap();
paramsText.put("b", "single");
paramsText.put("font", "Arial");

docx.addText(text, paramsText);

docx.createDocx("example_text");
 

 Como se puede ver, al addText() se le pasa un paramsText que es un conjunto de parejas clave-valor. Pues bien, no he encontrado en la documentación un listado de qué claves son posibles ni sus valores posibles. Buscándolo, me puse a mirar dentro del código de la librería y he visto que prácticamente esas claves-valor son las que se escriben directamente en el docx. Eso hace pensar que esas claves-valor son propias del formato docx y no de la librería.

Me pongo a investigar el formato docx y veo que un docx no es más que un fichero zip que podemos desempaquetar con winzip, winrar o cualquiera de esas herramientas. Una vez desempaquetado, tenemos un montón de ficheros xml y efectivamente, esas parejas clave-valor forman más o menos parte de los xml.

Resumiendo, una librería muy sencilla de usar y a tener en cuenta si queremos generar desde java este tipo de documentos. La pequeña pega es que necesitaremos conocer o tener una referencia al formato docx para saber exactamente cómo hacer algo que no aparezca tal cual en los ejemplos que vienen.

 

Mar 10

Malditas variables estáticas

En una aplicación más o menos grande y seria de java es normal leer algún fichero con propiedades de configuración para nuestro programa. Esas propiedades suelen ser múltiples y variadas, para módulos de todos los niveles que no tienen nada que ver unos con otros.

Una opción es leer ese fichero de propiedades desde un sitio cercano al main() de nuestra aplicación e ir pasando estas propiedades a los distintos módulos. Si hay módulos en varios niveles, estos deberán ir pasándose las propiedades de unos a otros, ya que el main() posiblemente no pueda acceder directamente a los módulos de bajo nivel.

Para evitar este trasiego de propiedades por todo el código, tiendo a hacer un código java de este estilo

public class FicheroPropiedades {

   private static String pathFicheroPropiedades = "path/fichero.properties";

   private static Properties propiedades = null;

   static {
      propiedades = cargaFichero();
   }

   public static String getProperty (String key) {
      return properties.getProperty(key);
   }
}

Es decir, una clase con todo estático a la que pedir los valores de las propiedades y un inicializador estático de forma que el fichero de propiedades se cargará en cuanto alguien "mencione" esta clase.

Pues bien, esto son todo problemas para los test de JUnit.

Por un lado, posiblemente tengamos o queramos un fichero de propiedades específico para nuestros test. O queramos cambiar alguna propiedad en algún test. Al leer nuestro código bajo test las propiedades de esta clase, es necesario hacerle cosas a esta clase para poder modificarla a gusto. Por ejemplo, poner public el path del fichero para poder cargar un fichero distinto en los test, o poner un cargaFichero(path), o añadir un setProperty() para modificar alguna propiedad concreta, o cualquier otra modificación que se os ocurra. Pero eso no es bonito. No es bonito tener que modificar una clase de nuestro código real para poder manejarla desde los test.

Por otro lado, si tenemos alguna herramienta que ejecuta automáticamente todos los test, es posible que esta clase sólo se inicialice en la ejecución del primer test, manteniendo sus valores para el resto de test. O que al pasar algún test que modifica propiedades, las deje modificadas para el siguiente. En fin, que podemos tener problemas en función de qué test se ejecuten primero, o si añadimos un test nuevo que modifique estas propiedades, puede alterar el resultado de los que van detrás.

Este es otro motivo, además de la mantenibilidad del código, para no usar/acceder a atributos estáticos desde todo el código  si no es realmente necesario. Los test de JUnit pueden hacerse dependientes del orden en que se ejecutan, ya que unos pueden cambiar o no los valores de estas variables estáticas según sus necesidades, incordiando a los que vienen detrás.

Aunque sea un poco rollo a la hora de codificar, suele ser mejor pasar las cosas a cada módulo a traves de métodos set() o en el constructor, aunque sea todo de golpe con un setProperties(Properties), que andar inicializando automáticamente variables estáticas y acceder a ellas desde todos sitios.

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.