Páginas

martes, 28 de agosto de 2012

Tracear tiempos de ejecución con AOP

En ocasiones las aplicaciones que desarrollamos deben realizar pruebas de rendimiento para determinar cómo responden y detectar los posibles errores que podemos llegar a tener en un entorno estresado. Muy común entre estas pruebas de rendimiento es que se nos pida el estudio de los tiempos de acceso a base de datos y el tiempo que nuestros métodos requieren para devolver los datos que se les pide para lo que solemos vernos obligados a introducir código en todos nuestros métodos.

Generar un log con el tiempo que un método tarda en ejecutarse es muy simple. Tan sólo necesitamos un par de líneas de código:


public List<User> getAllUsers() throws DaoException { Date d = new Date(); List<User> users= null; try { // code log.debug("method time: " + (new Date().getTime() - d.getTime()) + " ms"); return users; } catch (Exception e) { log.error("Error al insertar un organismo - " + e.toString()); throw new DaoException(e, "error getting users..."); } }

Sin embargo, ¿qué sucede si tenemos un centenar de métodos para el acceso a base de datos? ¿Debemos ir uno a uno introduciendo esas líneas de código? Fácil pero laborioso.

Para simplificarnos la vida y evitar el copy/paste utilizaremos AOP en colaboración con Spring. Con AOP podremos logar los tiempos de todos de cada uno de nuestros métodos sin necesidad de ir método a método copiando y pegando las líneas de código anteriores.

Lo primero que necesitamos es importar las librerías necesarias para ello (nos centraremos sólo en las librerías AOP dejando a un lado el resto de librerías, tales como las librerías de Spring)


<!-- AOP --> <dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.12</version> </dependency>


Seguidamente, creaemos nuestros aspecto y un "advice" de tipo "around" que será el que calcule el tiempo de ejecución de nuestros métodos:


package es.whatabout.service.db.aop; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @Aspect @Component public class Performance { private final Log log = LogFactory.getLog(Performance.class); /** * http://tomasjurman.blogspot.com.es/2010/01/spring-aop-with-annotation.html * * @param joinPoint * @return object * @throws Throwable */ @Around("execution(* es.whatabout.dao.*.* (..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch clock = new StopWatch("performance_dao"); try { clock.start(); log.debug("Comenzando metodo: " + joinPoint.getSignature().getName()); return joinPoint.proceed(); } finally { clock.stop(); log.debug("Consumo: " + clock.prettyPrint()); } } }


No olvidemos declarar nuestra clase como un Aspecto y un Componente. Destacar también, en en la declaración del punto de corte hemos indicado que queremos que el advice se ejecute para todas las clases y métodos (*.*) del paquete es.whatabout.dao sean cuales sean cuales sean su parámetros ( (...) ) o retorno (*)

Por último, sólo queda configurar Spring correctamente de la siguiente forma:


<sws:annotation-driven /> <aop:aspectj-autoproxy proxy-target-class="true" /> <!-- enable component scanning (beware that this does not enable mapper scanning!) --> <context:component-scan base-package="es.whatabout.*" /> <!-- enable autowire --> <context:annotation-config />


Limpio y rápido. Si además, mañana queremos eliminar por completo cualquier log sobre tiempos, sólo debemos cambiar la configuración.

Tras el proxy

Trabajar tras un proxy siempre es un problema. Desde mi punto de vista todos son desventajas ya que pierdo más tiempo luchando con éste que el tiempo que éste me pueda salvar evitando que entre en páginas "no autorizadas" pero está claro que a veces no hay opción.

Cuando estamos, como es en mi caso, trabajando tras un proxy y necesitamos que nuestro código se conecte a servicios que se encuentras más allá del proxy, nos vemos obligados a configurar nuestro código para entenderse con proxys. Esta tarea es bastante simple, ya que según la documentación solo es necesario definir algunas variables del sistema:

  • http.proxyHost: pombre host del servidor proxy
  • http.proxyPort: puerto, siendo su valor por defecto 80
  • http.nonProxyHosts: listado de host que deben ser accedidos directamente, saltándose el proxy.

En este punto, podemos ver que ni siquiera es necesario modificar una sóla línea de código sino sólo proporciar dichas variables en el arrache de nuestra aplicación o servidor si trabajamos con una aplicación Web.

Sin embargo, la configuración no queda ahí sino que se complica un poco para los casos en los que necesitemos autenticarnos contra el proxy. Para estos casos es necesario trabajar con la clase Authenticator que como se puede leer en la documentación, representa un objeto que sabe cómo obtener autentificación para una conexión de red. Una instancia concreta de esta clase (según nuestras necesidades) debe registrase en el sistema llamando al método setDefault(Authenticator) para qué este esté disponible.

Así pues, lo primero que debemos hacer es crear nuestra instancia extendiendo de la original:

package es.whatabout.common.security; import java.net.Authenticator; import java.net.PasswordAuthentication; /** * * @author whatabout.es * */ public class ProxyAuthenticator extends Authenticator { private String username, password; public ProxyAuthenticator(String username, String password) { this.username = username; this.password = password; } protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password.toCharArray()); } }

A continuación debemos, como dijimos antes, registrar nuestro autenticador dentro de nuestro código antes de realizar las conexiones que necesiten autenticarse:

Authenticator.setDefault(new ProxyAuthenticator("user", "pass"));


Como vemos, trabajar tras un proxy no es dependiente de la tecnología, o de framework. Pero veamos un ejemplo de cómo se configuraría con Spring IoC un proxy que requiera autenticación.

Primero, declaramos el bean:

<bean name="simpleAuthenticator" class="es.whatabout.common.security.ProxyAuthenticator" autowire-candidate="true"> <constructor-arg index="0" value="#{systemProperties['user'] == null ? 'user' : systemProperties['user']}" /> <constructor-arg index="1" value="#{systemProperties['pass'] == null ? 'pass' : systemProperties['pass']}" /> </bean>


El nombre de usuario y la contraseña podría proporcionarse directamente en la declaración pero eso no sería muy portable además de verte obligado a escribir tu usuario y contraseña en un código que puede compartirse en un repositorio. Y puesto que ya son varias las variables de sistema que necesitamos, ¿por qué no proporcionar el usuario y la contraseña como variables del sistema? Otra particularidad que se puede apreciar en la declaración del bean es que se proporciona unos valores por defecto para el usuario y contraseña. Eso es necesario ya que si nuestra aplicación, según el entorno, no se encontrara tras un proxy y no proporcionáramos unos valores a través de las variables del sistema, la instanciación del bean daría error.

Segundo, utilizamos nuestra clase (inyectada previamente):


if (proxyAuthenticator != null) Authenticator.setDefault(proxyAuthenticator);


Finalmente, ejecutemos nuestra aplicación en local utilizando Maven y el plugin disponible para Jetty

mvn jetty:run -DproxySet=true -DproxyHost=proxy.indra.es -DproxyPort=8080 -Duser=XXX -Dpass=XXX 

¿Que pasa si la aplicación se desplegara en un entrono si proxy? ¿Hay que hacer algún cambio? No. Simplemente, no serían necesarias las variables del sistema:

mvn jetty:run

Si trabajamos con Maven, como en el ejemplo, también podríamos configurar estas variables de sistema en el plugin de Jetty y definiras en un perfil de forma que no nos veamos obligados a escribir las variables de sistemas cada vez que queramos ejecutar nuestra aplicación tras un proxy.

domingo, 20 de mayo de 2012

Migración desde Jetty 6 a Jetty 8

Siempre suelo trabajar con Jetty como servidor local utilizando el plugin que éste proporciona para Maven. A pesar de que me gusta trabajar con las últimas versiones de las librerías con las que trabajo, hace poco me di cuenta de que no estaba trabajando con la última versión de Jetty, la 8. El error era debido a que desde su versión 7 Jetty pasó a formar parte de la fundación Eclipse y esto hizo que cambiaran de paquetes y que el plugin para Maven cambiara de nombre. Mientras que la versión 6 de Jetty y anteriores se encuentra bajo en artifact maven-jetty-plugin, la 7 y posteriores se encuentran bajo jetty-maven-plugin. Así, la configuración del Plugin de Maven para la nueva versión quedaría así:

<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>8.1.3.v20120416</version> <configuration> <webAppConfig> <contextPath>/</contextPath> </webAppConfig> </configuration> </plugin>
Esta sería su configuración más sencilla, sin embargo, si estamos utilizando configuración más compleja directamente sobre el plugin o a través del archivo jetty.xml también debemos tener en cuenta la repaquetización que he comentado antes.

Digamos que queremos configurar un recurso JNDI como podría ser la conexión a la base de datos. Antes, esto se hacía sobre la configuración del servidor y la clase org.mortbay.jetty.Server en el archivo jetty.xml mientras que ahora el uso del archivo jetty.xml queda para la configuración del servidor y el archivo jetty-env.xml para la configuración de nuestra aplicación Web. Así, teniendo el cuenta los nuevos paquetes, la configuración del recurso JNDI en el fichero jetty-env.xml quedaría así:
<Configure class="org.eclipse.jetty.webapp.WebAppContext"> <New id="epago" class="org.eclipse.jetty.plus.jndi.Resource"> <Arg>jdbc/epago</Arg> <Arg> <New class="org.apache.commons.dbcp.BasicDataSource"> <Set name="driverClassName">oracle.jdbc.OracleDriver</Set> <Set name="url">jdbc:oracle:thin:@172.111.111.111:1521:XE</Set> <Set name="username">user</Set> <Set name="password">pass</Set> </New> </Arg> </New> </Configure>
¿Y si queremos hacer algún cambio en la confiugraicón del servidor, como podría ser el puerto de arranque? Como hemos dicho, esta configuración sí sería propia del archivo jetty.xml y seria algo así:
<Configure class="org.eclipse.jetty.server.Server"> <Call name="addConnector"> <Arg> <New class="org.eclipse.jetty.server.nio.SelectChannelConnector"> <Set name="port">8081</Set> <Set name="maxIdleTime">3000</Set> </New> </Arg> </Call> </Configure>
Finalmente, veamos cómo quedaría la configuración del plugin:
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>8.1.3.v20120416</version> <dependencies> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.2.0</version> </dependency> </dependencies> <configuration> <webAppConfig> <contextPath>/myApp</contextPath> </webAppConfig> <systemProperties> <systemProperty> <name>javax.net.ssl.trustStore</name> <value>d:/Keystore</value> </systemProperty> </systemProperties> <jettyEnvXml>target/${project.build.finalName}/WEB-INF/jetty-env.xml</jettyEnvXml> <jettyXml>target/${project.build.finalName}/WEB-INF/jetty.xml</jettyXml> </configuration> </plugin>
En la última configuración se puede ver cómo se hacen referencia a los archivos jetty.xml y jetty-env.xml antes nombrados, cómo se añaden librerías necesarioas para el recurso JNDI definido y cómo, finalmente, se configura para que trabaje con un almecén de claves seguro y así poder acceder a recursos seguros como podría ser un servicio Web bajo HTTPS.

Algunos enlaces de interes:

jueves, 16 de febrero de 2012

Primate programming

Hace unos días leía en Mundo geek una cita de James Goldsmith que decía:
Si pagas cacahuetes, obtienes monos
Generalmente cierto. También es verdad que muchos conocen perfectamente esa relación y qué obtendrán con cacahuetes sin embargo, piensan que un grupo de monos puede llevar a cabo cualquier tarea. A esas personas, les dejo este enlace donde pueden encontrar primates con gran experiencia a unas tasas muy reducidas.

Depurar mensajes SOAP con Spring WS

Son muchos los que preguntan por la red cómo depurar (logar, tracer, ...) los mensajes SOAP cuando se utiliza Spring WS. Yo también me lo he preguntado así que aquí va la solución.

Lo primero que hay que saber es que no es lo mismo depurar los mensajes SOAP cuando estamos en el lado del cliente, es decir, estamos implementando una llamada a un servicio Web o en el lado del servidor, esto es, estamos implementando el servicio Web. La forma de depurar los mensajes SOAP es distinta para cada caso.

En el lado del servidor tenemos dos alternativas:
  • Utilizar interceptores. Concretamente debemos utilizar PayloadLoggingInterceptor SoapEnvelopeLoggingInterceptor. Podéis encontrar información más detallada en la documentación de Spring, justo aquí. Cuando os decidais por uno de los interceptores, no hay que olvidad activar el nivel de DEBUG para el paquete al que pertenece.
  • Activar el nivel DEBUG o TRACE para el paquete org.springframework.ws.server.MessageTracing. Más información aquí.
En el lado del cliente:
  • Activar el nivel de DEBUG o TRACE para unos paquetes particulares:
    • org.springframework.ws.client.MessageTracing.sent para los mensajes salientes
    • org.springframework.ws.client.MessageTracing.received para mensajes entrantes.

martes, 14 de febrero de 2012

Servicios Web y XML

Recientemente he estado trabajando con Servicio Web con Spring WS y me he encontrado algunos problemas de librerías relacionadas con Servicios Web y XML, especialmente al desplegar estos servicios en servidores de aplicaciones como Welogic o JBoss. Tras algunas tortas me ha quedado medianamente claro para qué sirven algunas de las librerías. En este post trato de aclarar algo para qué sirven todas estas librerías.

  • SAAJ (SOAP with Attachments API for Java). Proporciona una forma estándar de enviar documentos XML a través de Internet desde Java. Dicho de otra forma, SAAJ permite a producir y consumir mensajes acorde con la especificación SOAP 1.1 y SOAP con adjuntos. Otros datos:
    • Opera a un nivel inferior que JAX-RPC y JAX-WS que usan SAAJ.
    • Forma parte del JDK. Particularmente, la versión Java 6 incluye SAAJ 1.3
    • Para aplicaciones Web, la implementación es típicamente provista por el servidor de aplicaciones.
  • JAXP (Java Api for XML Processing). API Java (definido por Sun Microsystems) sirve para la manipulación y el tratamiento de archivos XML. Otros datos:
    • Es parte del JDK. En  la versión Java 6 podemos encontrar JAXP 1.4 mientras que en Java 5 encontramos la versión 1.3 que está obsoleta.
    • El JDK también proporciona una implementación (Xerces a partir de JDK5 y anteriormente Crimson).
  • JAXB (Java Architecture for XML Binding). Permite a los desarrolladores Java mapear clases Java a representaciones XML. JAXB proporciona dos características principales: la habilidad de convertir (marshal) objetos Java en XML y viceversa. En otras palabras, permite almacenar y extraer datos en memoria en cualquier formato XML sin la necesidad de tener que implementar código específico. Otros datos:
    • Es parte de JDK. Particularmente, la versión Java 6 (updated 4) viene con  JAXB 2.1
    • La implementación de referencia es parte del proyecto Glassfish
    • Aquí encontrarás una descripción más completa sobre JAXB
  • JAX-RPC (Java API XML para RPC) es una especificación para desarrolladores Java para el desarrollo de Web servicios basados en SOAP. Este API está obsoleto en favor de JAX-WS. Otros datos:
    • Tiene su propio modelo de mapeo de datos (data mapping model) que cubre el 90% de los esquemas. Los esquemas que no son cubiertos son mapeados  javax.xml.soap.SOAPElement.
    • El modelo controlador (handler model) está basado en SAAJ 1.2
    • Era el API de Servicios Web en el JDK1.4
  • Apache Xerces. Es una librería de Apache para el parseo, validación, serialización y manipulación de XML. Actualmente se encuentra en su versión 2 y aunque se puede encontrar como una librería independiente también forma parte del JDK. Otros datos:
    • Ofrece una implementación del API JAXP 1.4 entre otros APIs
    • Fue re-paquetizado bajo el paquete com.sun en el JDK5
    • La implementación es típicamente provista por el servidor de aplicaciones como se describe aquí.
  • Apache Xalan. Es un procesador XSLT para la transformación de documentos XML en documentos HTML, textos, u toros tipos de documentos XML. Otros datos:
    • Al igual que Xerces, forma parte del JDK y a partir de la versión 5 fue re-paquetizado bajo sun.com.
    • Implemente XPath entre otros.
    • La versión 1 ya no está soportada por lo que es recomendable trabajar con la versión 2.
  • JAX-WS (Java Api for XML Web Services) es el API Java que se utiliza para la creación de Web Service. JAX-WS forma parte del estándar Java EE. La implementación de referencia de JAX-WS es parte del proyecto GlassFish. En la plataforma Java EE 5, JAX-WS 2.0 reemplaza al API JAX-RPC (obsoleto). El cambio se basa en moverse hacia Web Services orientados a documentos (en vez de RPC). Otros datos:
    • Como modelo de mapeo de datos utiliza JAXB
    • El controlador de modelo (model handler) se base en SAAJ 1.3
    • Pasó a formar parte del JDK5 en su versión 2.0. La versión  JAX-WS 2.1 fue introducida en Java 6 Update 4.
    • JAX-WS 2.1 fue incluido en Java 6, particularmente en JDK 1.6.0_04 y superior.
    • JAX-RPC vs JAX-WS
Por último, dejo algunos enlaces interesantes:

martes, 31 de enero de 2012

Homogeniedad

Es un hecho el que a la hora de programar alguna aplicación nos tropecemos con muchos problemas. Muchos de estos serán repetidos, es decir, mismo problema en distinto punto de la aplicación.

¿Qué sucede cuando uno se encuentra por segunda vez con un problema que ya ha resuelto en otro punto de la aplicación/código?
  • Podemos acudir a nuestra primera solución y a ser posible no duplicar el código para que tengamos en un mismo punto la solución a distintos problemas del mismo tipo.
  • Podemos dar de nuevo solución al problema ignorando la primera solución porque, por ejemplo, se nos ha ocurrido una mejor implementación.

Suele pasar que cuando nos enfrentamos una segunda o tercera vez con el mismo problema se nos ocurra una mejor forma de implementar la solución. En este caso creo que es un error implementar una nueva solución aunque esta sea mejor que la anterior. De lo que se trata es de evitar tener dos problemas del mismo tipo con dos soluciones distintas. Si decides implementar una segunda solución tienes que modificar el código ya escrito para que todos los problemas de dicho tipo tengan la misma nueva solución (fácil si se ha centralizado es punto del código). Si este paso es muy costoso, homogeneizar la solución, olvídalo y sigue con la implementación actual por que lo que sí será costoso en un futuro será mantener un código donde cada cual y según el momento decidió implementar una solución distinta para el mismo problema.

Por otro lado, y aunque parezca raro, también se da el caso de tener varios problemas implementados con la misma solución. ¿Como se llega a esta punto? Fácil. Tras encontrarse con el segundo problema, distinto al primero se decide que aunque distinto es parecido así que se decide introducir unas pequeñas modificaciones (normalmente introduciendo cláusulas if y más parámetros en la llada) en la primera solución. Esto suele sucede en los programadores empeñados en "generalizar" código y puede acabar en un método con infinitas líneas y llenos de if. No es de extrañar luego que hagas algunas modificaciones para que el código también resuelva un nuevo problema y que la hacerlo dejen de funcionar otros dos.

Por eso, que digo yo, y esto es sólo una opinión, señores:
  • Mismo problema: misma solución
  • Distintos problemas: distintas soluciones.