Archivos del sitio javahispano
Eligiendo nuestro entorno de Integración Continua (I).
Objetivos
El objeto de este artículo es en plasmar la metodología y conclusiones obtenidas en una comparativa técnica que hemos realizado sobre algunas de las herramientas de integración continua disponibles en el mercado. Para ello se establecen unas bases para la elección de la plataforma y se realiza una comparativa completa partiendo de una selección de herramientas.
Existe un buen número de herramientas de Integración Continua en el mercado: Continuum, CruiseControl, LuntBuild, Hudson, Atlassian Bamboo, etc… Para facilitar el estudio se ha realizado una primera preselección consistente en las herramientas estrictamente Open Source más utilizadas en el mercado: Apache Continuum, LuntBuild y Hudson.
Antes de entrar en detalles, comentaremos cúales son los principales objetivos funcionales que se buscan a la hora de implantar un sistema de Integración Continua:
- Reducción del tiempo de integración. Se trata de agilizar el proceso de integración de los diversos proyectos y aplicaciones, buscando un sistema que se responsabilice de automatizar la compilación, ejecución de tests y despliegues, así como de avisar de posibles problemas a los diversos participantes en el desarrollo de un proyecto.
- Reducción de errores. Uno de los objetivos más importantes que se persiguen con este tipo de sistemas es la detección de errores y conflictos en un estado temprano.
- Testeo de la calidad de código y reglas de estilo. Para asegurar un mínimo de coherencia de los nuevos desarrollos con las políticas de calidad establecidas, se busca un sistema capaz de automatizar este tipo de tareas de manera sencilla.
- Reducción del riesgo. Permitiendo que versiones tempranas del producto estén disponibles lo antes posible, con un número de incidencias minimizado de forma automática, y con una gestión de la configuración automática que reduce errores en despliegues.
- Disponibilidad continua de la última versión del código para pruebas, agilizando los procesos de despliegue de nuevas versiones en los diferentes entornos.
Metodología del análisis
Para seleccionar el sistema de Integración Continua más adecuado, hemos procedido a analizar las diversas características funcionales y técnicas de interés. Para ello se ha procedido a instalar las tres plataformas objeto del estudio (Continuum, LuntBuild y Hudson) y se ha puesto en marcha al menos un proyecto Maven sobre ellas, tratando de evaluar una serie de variables de interés.
A continuación se indican los criterios de selección utilizados en la comparativa:
- Instalación.
Evaluaremos la facilidad de instalación y configuración de las herramientas, con parámetros como la documentación oficial de instalación, posibilidades de configuraciones avanzadas, dificultad de la instalación, etc.
- Administración, gestión de proyectos.
Partiendo del hecho de que el entorno en el que se va a realizar la implantación no tiene por qué contar con personal especializado y dedicado exclusivamente a la gestión y administración de la plataforma, será recomendable que las tareas de administración asociadas al sistema elegido sean las mínimas, y que su ejecución sea lo más sencilla posible. Se analizarán parámetros como la documentación oficial destinada a esta temática o si el sistema permite una fácil generación y restauración de copias de seguridad (tanto de la definición de los proyectos y tareas configuradas como del histórico de compilaciones). También se estudiará si las tareas de administración relacionadas con la plataforma se puedan realizar mediante la interfaz Web. Así mismo, verificaremos si se permite recuperar el historial de las últimas tareas de compilación realizadas, así como poder configurar el número de días y compilaciones que se mantendrán en el historial. Con respecto a la gestión de tareas periódicas, comprobaremos si el sistema permite la definición de tareas periódicas, para facilitar la automatización de tareas repetitivas. Además, teniendo en cuenta que este tipo de herramientas están en continua evolución, se evaluará la facilidad de instalación de nuevas actualizaciones. Por último se estudiará el rendimiento de la solución. Dado que las compilaciones son tareas muy pesadas, se evaluarán los diferentes mecanismos que aporta cada sistema de Integración Continua para la gestión de hilos de compilación y la ejecución distribuida de tareas.
- Seguridad.
Se evaluará la versatilidad y potencia en la gestión de roles, perfiles y permisos asociados a proyectos y acciones disponibles en la herramienta. Resulta interesante además poder definir perfiles de usuario específicos. Así mismo, se estudiará la posibilidad de definición de permisos especiales para proyectos concretos. Por último evaluaremos la complejidad a la hora de establecer una política avanzada de permisos, observando el nivel de personalización que permite la plataforma.
- Integración con sistemas externos.
En la mayoría de los casos es necesario que la herramienta de Integración Continua interactúe con otros sistemas específicos, por lo que puede ser necesario el desarrollo de nuevos plugins que extiendan la funcionalidad de la plataforma. Por este motivo evaluaremos la sencillez y los mecanismos que ofrecen estos sistemas a la hora de inyectar funcionalidad personalizada.
Evaluaremos también el nivel de integración que tienen las diferentes herramientas de Integración Continua con sistemas de control de versiones, así como el nivel de integración con herramientas de evaluación de código (como checkstyle, PMD, etc…). Analizaremos además el nivel de integración con sistemas de gestión de incidencias, particularizando en el sistema Mantis BT. Por último evaluaremos el nivel de integración de soluciones con sistemas de test como Junit o JMeter .
- Tipos de Proyectos soportados.
Evaluaremos los diferentes tipos de proyectos soportados, así como sus posibilidades de extensión para futuros modelos. Aunque haremos especial hincapié en el soporte completo de proyectos basados en Maven2, si bien también es relevante que el sistema permita la gestión del mayor número posible de formatos.
- Facilidad de uso.
Ya que la plataforma de Integración Continua elegida deberá convertirse en una pieza central de nuestro entorno de software, la herramienta deberá ofrecer una interfaz sencilla, usable e intuitiva, de forma que se facilite la interacción con la herramienta por personal no especializado, sin problemas serios de aprendizaje. Haremos especial hincapié en las ayudas en línea disponibles y en la organización de la interfaz.
- Documentación de usuario.
Evaluaremos la cantidad y calidad de la documentación de usuario disponible.
- Estabilidad.
La herramienta elegida deberá disponer de unas excelentes características de estabilidad. El objetivo no es elegir la última tecnología relacionada con la Integración Continua, sino disponer de un entorno funcional operativo lo más robusto posible.
Próximamente:
Apache Continuum. Eligiendo nuestro entorno de Integración Continua (II).
OpenCms Content Relation Engine (CRE)
Una de las principales mejoras de OpenCms 7 es la incorporación del CRE (Content Relation Engine). Esta herramienta desarrollada por Michael Moossen permite establecer relaciones entre recursos del VFS (Vitual File System) de OpenCms.
Imaginemos lo siguiente:
Tenemos en el proyecto Offline un recurso A de OpenCms de tipo página html o jsp, en cuyo código hay un enlace a una imagen (recurso B) usando la etiqueta <img>. Publicamos A.
¿Qué ocurre en versiones anteriores de OpenCms?
En el mejor de los casos si el recurso es de tipo página html el navegador mostraría la típica aspa roja indicando que no encuentra la imagen en el proyecto Online. En el peor de los casos, en jsp, suponiendo que la imagen fuese necesaria ya que leemos alguna propiedad (por ejemplo Description para mostrar el texto alternativo) el sistema podría dar un error 500.
¿Qué ocurre en el nuevo OpenCms si hemos usado CRE?
Pues que el sistema avisaría de que no hemos publicado la imagen y permitiría publicarla junto al recurso al que está asociada.
¿Qué ha cambiado en la base de datos de OpenCms?
Se ha añadido una nueva tabla llamada CMS_ONLINE_RESOURCE_RELATIONS para cada proyecto (sustituir ONLINE por OFFLINE) con los siguientes campos:
RESOURCE_SOURCE_ID: De tipo VARCHAR(36), contiene el id del recurso A.
RESOURCE_SOURCE_PATH: TEXT, contiene el path en ruta absoluta del recurso A.
RESOURCE_TARGET_ID: VARCHAR(36), contiene el id del recurso B.
RESOURCE_TARGET_PATH: TEXT, contiene el el path en ruta absoluta al recurso B.
RELATION_TYPE : INTEGER, es el tipo de relación que vamos a establecer.
¿Dónde se configuran las relaciones?
El el fichero opencms-workplace.xml. La configuración por defecto:
<default-preferences>
<workplace-preferences>
<workplace-generaloptions>
...
<allowbrokenrelations>false</allowbrokenrelations>
<publishrelatedresources>true</publishrelatedresources>
...
</workplace-generaloptions>
</workplace-preferences>
</default-preferences>
¿Cómo se usan las relaciones?
- Desde el menú flotante, opción relaciones: Podemos ver las relaciones de los recursos y asignar categorías.
- En jsp, usando el macro %(link.strong) o %(link.weak). En vez de usar
<img src=”<cms:link>/imagenes/imagen.jpg</cms:link>” />
usamos
<img src=”<cms:link>%(link.strong:/imagenes/imagen.jpg)</cms:link>” />
- Usando categorías:
- Primero hay que definir categorías, esto se hace creando una estructura de carpetas en /system/categories/ , por ejemplo:
/system/categories/categoria1/
/system/categories/categoria1/subcategoria11/
/system/categories/categoria1/subcategoria12/
/system/categories/categoria1/subcategoria13/ - En el .XSD usamos el widget org.opencms.widgets.CmsCategoryWidget para mostrar el listado de categorías.
- En jsp usamos org.opencms.file.collectors.CmsCategoryResourceCollector para listar los recursos pertenecientes a dicha categoría.
- Primero hay que definir categorías, esto se hace creando una estructura de carpetas en /system/categories/ , por ejemplo:
- Desde las clases de CmsObject (crear nuevas relaciones y leer o borrar existentes) y CmsRelationFilter.
Por último hay que destacar que Michael Moossen realizó una presentación muy interesante sobre CRE en los OpenCms Days. Las trasparencias que usó están disponibles en el siguiente enlace.
Trasparencias de Michael Moossen para la presentación de CRE en OpenCms Days.
Hudson. Parte 1 - Introducción.
¿Qué problemática resuelve?
En entornos en los que el desarrollo de software se realiza por un equipo o equipos en los que la evolución y mantenimiento del código se subdivide, el coste de la integración entre las diferentes piezas de software tiende a aumentar, apareciendo conflictos y haciendo de la generación de distribuibles una ardua tarea. Con idea de rebajar los costes adicionales provocados por la gestión del proceso de compilación, integración, empaquetado y generación de entregarles, aparecen herramientas de Integración Continua (CI) como Hudson.
¿ Por qué elegir Hudson?
Aunque es un productor reciente ha evolucionado rápidamente y actualmente es uno de los productos de referencia en el sector de las aplicaciones para Integración Continua de software, gracias a la simplicidad de su interfaz y lo sencillo que resulta el desarrollo de nuevos plugins. Prueba de este crecimiento es que actualmente es utilizado por un gran numero de proyectos entre los que se encuentra por ejemplo JBoss.
Para hacernos una idea los principales motivos que en nuestro caso nos hicieron elegir Hudson son:
- Fácil instalación y uso. Como buen producto cuya utilidad es la de simplificarnos la vida, la instalación y ejecución de Hudson es realmente trivial. Es suficiente con descargar el fichero hudson.war y ejecutarlo con “java -jar hudson.war” o bien desplegarlo en un servidor de aplicaciones. Y esto no queda aquí, gracias a su sencilla interfaz, podemos tener nuestros primeros proyectos configurados en pocos minutos.
- Un sistema de plugins fácilmente extensible. Lo que permite la instalación y creación de nuevos plugins de forma sencilla. Esto ha permitido que exista una gran cantidad de plugins disponibles y que sea muy sencilla la creación de plugins específicos. Como ejemplo, gracias a esta simplicidad pudimos crear un plugin específico para adaptar el comportamiento de Hudson a nuestras necesidades en solo unos días.
- Un soporte completo de Maven, lo que nos ha permitido una migración rápida de todos nuestros proyectos al nuevo sistema.
- Soporte para compilación distribuida basada en sistemas esclavos y maestros, para mejorar los tiempos de compilación y evitar la sobrecarga.
- Soporte para múltiples equipos y grupos de proyectos, lo que nos permite la colaboración.
- Es un Sistema completamente Software Libre.
- Nos permite establecer un sistema de alertas a los desarrolladores sobre el estado de sus proyectos.
- Permite la detección de actualizaciones realizadas en el SCM ( en nuestro caso Subversion) para generar de forma automática los nuevos empaquetados o a intervalos regulares.
- Y el principal motivo. Tras estar utilizándolo en el departamento de I+D durante unos meses, el software no nos dio ni un solo problema, por lo que actualmente la mayoría de nuestros proyectos están ya bajo el control de Hudson.
Instalación
Como se comenta arriba, la instalación del software es realmente sencilla, y el único requisito es tener instalada una versión de Java reciente y opcionalmente un servidor de aplicaciones como por ejemplo Tomcat, si no se desea ejecutar sobre el servidor que trae embebido.
Para la instalación, en primer lugar necesitara descargar la última versión estable de http://hudson.dev.java.net.
Si desea ejecutar Hudson en modo autónomo sera suficiente con ejecutar “java -jar hudson.war”y acceder con el navegador al puerto 8080 (http://localhost:8080) para poder utilizar Hudson. Si lo desea, en la web oficial tambien puede encontrar los scripts para convertir hudson en un servicio.
Por otro lado la instalación más común de Hudson es su despliegue en un servidor de aplicaciones. Por ejemplo, para su despliegue en un Tomcat, bastara con copiar el fichero hudson.war dentro del directorio webapps.
Hudson utiliza un directorio especial para almacenar toda la configuración propia y de los proyectos que gestiona, por lo que no hay peligro de perdida de datos si posteriormente actualizamos a versiones más recientes del software. Por defecto el directorio es “.hudson”, localizado en el home del usuario, pero es posible modificar este directorio estableciendo la variable de entorno HUDSON_HOME.
Primeros pasos
En Hudson cada proyecto de desarrollo es una tarea, por lo que para hacer que un proyecto este gestionado por hudson es suficiente con darle un nombre a la tarea y asignarle un tipo entre los disponibles (Build a maven2 project, Build a free-style software project, …)
En adelante a modo de ejemplo vamos a considerar un proyecto de tipo Maven 2. Una vez que tenemos el tipo de proyecto seleccionado, pasamos a la pantalla de configuración, en la que entre otras muchas opciones, tendremos que seleccionar el SCM en el que se encuentra proyecto(en nuestro caso Subversión), el pom.xml asociado al proyecto y el conjunto de goals que deseamos ejecutar. No entraremos en detalle a explicar cada uno de las opciones de configuración, ya que otra de las ventajas de Hudson es que ofrece ayuda en linea para todas sus opciones.
Una vez guardada la nueva tarea, ésta podrá ser ejecutada automáticamente si hemos configurado algún Build Triggers o lanzada de forma manual pulsando sobre Build Now, y una vez que la tarea esta en ejecución podremos ver en todo momento su estado y consola.
Administración
Desde el menú “Manage Hudson”, situado en la página principal, es posible acceder a todas las opciones de configuración. En particular desde la opción “Configure System” podremos acceder a todos los parametros generales como configuración del SMTP, proxy, rutas a Maven, Ant, …
Y de nuevo para cada opción de configuración dispondremos de ayuda contextual que nos hara la vida mucho más facil.
Como ya hemos comentado, uno de los puntos fuertes de Hudson es su sistema y manejador de plugins, al que podremos acceder desde la opción “Manage Plugins”. Esta opción nos permitirá añadir nuevas características (como herramientas de control de estilo, nuevos SCM soportados, …) directamente desde su interfaz.
La lista de todos los plugins está disponible en la siguiente dirección: http://hudson.gotdns.com/wiki/display/HUDSON/Plugins
Conclusión
Hudson es un es un fantastico software para realizar las tareas de Integración Continua, y aunque aquí se han presentado solo las características básicas, a pesar de la simplicidad de su interfaz es un software altamente extensible y con un gran potencial.
Por otro lado, y aunque se enfoca principalmente hacia proyectos Java, Hudson permite gestionar proyectos de cualquier tipo, como por ejemplo proyectos C o Ruby on Rails, lo que lo hace muy recomendable enentorno heterogéneos.
En general, los productos de Integración Continua suelen ser complejos de manejar y gestionar, y como hemos visto, este no es el caso de Hudson, lo que resulta esencial si queremos convertir esta herramienta en una plataforma horizontal en nuestra empresa o grupo de desarrollo. Su facilidad de uso la ha demostrado durante el tiempo que llevamos utilizándolo, ya que no solo no ha requerido de personal especializado en este tipo de tareas para su gestión, sino que tampoco ha necesitado de una compleja explicación para que nuestros grupos de desarrollo empezaran a utilizarlo.
En resumen una herramienta para simplificarnos la vida a los desarrolladores que ofrece lo que promete.
Próximamente: Hudson. Parde 2. Crea tus propios plugins
Curiosidad: La extraña implementación de NaN
Si probáis a evaluar esta expresión: Double.NaN==Double.NaN , comprobareis con extrañeza que Java retorna false.
Sin embarco, si realizamos la comparación con el método equals, la cosa mejora y todo se comporta como esperábamos.
¿Por qué ocurre esto? ¿Es un Bug?
Por extraño que nos pudiera parecer no es un error en la implementación, y podemos encontrar este comportamiento en otros lenguajes orientados a objetos como C#.
La implementación del tipo double en Java se basa en el estandard “IEEE 754 Binary Floating-Point Arithmetic standard“, que define que dos números con el valor NaN nunca son iguales, por lo que el operador “==” se definió de acuerdo con esta especificación. El problema surge al conciliar este comportamiento con la especificación del método equals para Object, por lo que se decidió sobrescribir el método equals de Double para adaptarlo al comportamiento de comparación de equivalencia esperado. Y aquí tenemos el conflicto “método equals” VS “IEEE 754″.
Por lo que lo recomendado es utilizar el operador cuando deseas hacer comparaciones de acuerdo con el estándar para números flotantes, y utilizar el método equals si en tu código es deseable que un NaN sea igual a un NaN.
La solución.
Si no nos queremos complicar, en la mayoría de los casos bastara con utilizar :
Double.isNaN(varNum) ;
Arroz con Mango: Seam + Logger & Finder Interceptor + Lucene…
Bueno, tuve unas necesidades en un proyecto, así que comencé por hacer un GenericDAO, luego le agregué un FinderInterceptor el cual permite que cuando hagas findByWhatever te busque un NamedQuery que esté definido en la Entidad de JPA, como tip le agregué un logger el cual loguea cada método conjunto con los parámetros pasados, aqui comienzo a explicar uno por uno:
GenericDAO; interfase que define el contrato del JPA DAO el cual implementa de forma genérica las funcionalidades del mismo; esta interfase está interceptada por un Finder del cual hablaremos más tarde; tuve que usar Lucene ya que el proyecto corre sobre MSSQL Server y el bendito no soporta translate:
package com.viavansi.fdu.persistencia.DAO;import java.io.Serializable;
import java.util.List;
import org.apache.lucene.queryParser.ParseException;
import com.viavansi.framework.core.persistencia.servicios.excepciones.ExcepcionPersistencia;/**
* Interface for data access objects.
*
* <p>
* Generic Interface DAO which provides the basic contracted operations for
* every DAO; an implementation is also provided.
*
* @param <T>
* The persistent class.
* @param <PK>
* The class of the primary key of the persistent class.
*/
@FinderExecutor
public interface GenericDao<T, PK extends Serializable> { /**
* Merge.
*
* @param persistentObject
*/
void update(T persistentObject) throws ExcepcionPersistencia; /**
* Make the instance persistent.
*
* @throws ExcepcionPersistencia
*/
void create(T newInstance) throws ExcepcionPersistencia; /**
* Make the object transient.
*
* @param persistentObject
* @throws ExcepcionPersistencia
*/
void delete(T persistentObject) throws ExcepcionPersistencia; /**
* Returns a persistent object specified by its key.
*
* @throws ExcepcionPersistencia
*/
T read(PK id) throws ExcepcionPersistencia; /**
* Returns all persistent entities.
*/
List<T> findAll() throws ExcepcionPersistencia; /**
* Resolves and executes a finder. <p/>
* <p>
* This implementation uses the short name of the type class, appending a .
* and the method name so the name of the query to look up becomes
* Pet1.findByName if the method is findByName and the type Pet1. <p/>
* <p>
* An other implementation would be useful as well that does not return a
* list but a single object.
*/
List<T> executeFinder(String method, Object[] queryArguments)
throws ExcepcionPersistencia; List<T> searchByText(String expresion) throws ExcepcionPersistencia,
ParseException;
Ahora definimos el DAO que contiene injectado el EntityManager:
package com.viavansi.fdu.persistencia.DAO;import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.jboss.seam.annotations.In;
import com.viavansi.framework.core.excepciones.CodigoError;
import com.viavansi.framework.core.persistencia.servicios.excepciones.ExcepcionDatosNoEncontrados;
import com.viavansi.framework.core.persistencia.servicios.excepciones.ExcepcionPersistencia;
/**
* Generic data access object.
*
* @param <T>
* The persistent class.
* @param <PK>
* The class of the primary key of the persistent class.
*/
public abstract class GenericJpaDaoFDU<T, PK extends Serializable> implements
GenericDao<T, PK> {
protected Class<T> type;
@In("fduPersistenceContext")
protected EntityManager entityManager;
public EntityManager getEntityManager() {
return entityManager;
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public void create(T newInstance) throws ExcepcionPersistencia {
try {
this.entityManager.persist(newInstance);
} catch (Exception e) {
throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
}
}
public void delete(T persistentObject) throws ExcepcionPersistencia {
try {
this.entityManager.remove(persistentObject);
} catch (Exception e) {
throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
}
}
public T read(PK id) throws ExcepcionDatosNoEncontrados {
T t = (T) this.entityManager.find(type, id);
if (t == null) {
throw new ExcepcionDatosNoEncontrados(
CodigoError.ERROR_DATOS_NO_ENCONTRADOS,
"Datos no encontrados");
}
return t;
}
public void update(T transientObject) throws ExcepcionPersistencia {
try {
this.entityManager.merge(transientObject);
} catch (Exception e) {
throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
}
}
@SuppressWarnings("unchecked")
public List<T> findAll() throws ExcepcionPersistencia {
try {
return entityManager.createQuery(
"select obj from " + this.type.getName() + " obj")
.getResultList();
} catch (Exception e) {
throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
}
}
/**
* Resolves and executes a finder. <p/>
* <p>
* This implementation uses the short name of the type class, appending a .
* and the method name so the name of the query to look up becomes
* Pet1.findByName if the method is findByName and the type Pet1. <p/>
* <p>
* An other implementation would be useful as well that does not return a
* list but a single object.
*
* @throws ExcepcionPersistencia
*/
@SuppressWarnings( { "unchecked" })
public List<T> executeFinder(String method, Object[] queryArguments)
throws ExcepcionPersistencia {
try {
String queryName = queryNameFromMethod(method);
Query query = entityManager.createNamedQuery(queryName);
for (int i = 0; i < queryArguments.length; i++) {
query.setParameter(i, queryArguments[i]);
}
return query.getResultList();
} catch (Exception e) {
throw new ExcepcionPersistencia(CodigoError.ERROR_NO_DEFINIDO, e);
}
}
/**
* Resolves the name of the named query.
*
* @param finderMethod
* "findPerson, etc."
* @return
*/
protected String queryNameFromMethod(String finderMethod) {
return type.getSimpleName() + "." + finderMethod;
}
/*
* (non-Javadoc)
*
* @see
* com.viavansi.fdu.persistencia.DAO.GenericDao#findWhere(java.lang.String)
*/
@SuppressWarnings("unchecked")
public List<T> searchByText(String expression)
throws ExcepcionPersistencia, ParseException {
PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance()
.getPropertyUtils();
StringBuilder builder = new StringBuilder();
boolean firstField = true;
for (PropertyDescriptor descriptor : propertyUtils
.getPropertyDescriptors(type)) {
if (firstField) {
firstField = false;
} else {
builder.append(" OR ");
}
builder.append(descriptor.getName() + ":" + expression);
}
FullTextEntityManager fullTextEntityManager = Search
.createFullTextEntityManager(entityManager);
QueryParser parser = new QueryParser("id", new ISOLatin1Analyzer());
org.apache.lucene.search.Query luceneQuery = parser.parse(builder
.toString());
Query query = fullTextEntityManager.createFullTextQuery(luceneQuery,
type);
List result = query.getResultList();
if (result.size() == 0) {
throw new ExcepcionDatosNoEncontrados();
}
return result;
}
/**
*
* @param <T>
* @param expression
* @param entityManager
* @param type
* @return
* @throws ExcepcionPersistencia
* @throws ParseException
*/
@SuppressWarnings("unchecked")
public static <T> List<T> searchByText(String expression,
EntityManager entityManager, Class<T> type)
throws ExcepcionPersistencia, ParseException {
PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance()
.getPropertyUtils();
StringBuilder builder = new StringBuilder();
boolean firstField = true;
for (PropertyDescriptor descriptor : propertyUtils
.getPropertyDescriptors(type)) {
if (firstField) {
firstField = false;
} else {
builder.append(" OR ");
}
builder.append(descriptor.getName() + ":" + expression);
}
FullTextEntityManager fullTextEntityManager = Search
.createFullTextEntityManager(entityManager);
QueryParser parser = new QueryParser("id", new ISOLatin1Analyzer());
org.apache.lucene.search.Query luceneQuery = parser.parse(builder
.toString());
Query query = fullTextEntityManager.createFullTextQuery(luceneQuery,
type);
return query.getResultList();
}
}
Nuestro 1er DAO para una Entidad; como verán, se apoya en el GenericDAO, y en su implementación:
package com.viavansi.fdu.persistencia.DAO;import java.util.List;
import org.jboss.seam.annotations.Name;
import com.viavansi.fdu.persistencia.VO.ProcessInfoVO;
/**
* @author gmedina
*
*/
@Name("processInfoDAO")
public class ProcessInfoDAO extends GenericJpaDaoFDU<ProcessInfoVO, Long> {
public ProcessInfoDAO() {
this.type = ProcessInfoVO.class;
}
public List<ProcessInfoVO> findByProcessName(String name) {
return null;
}
public List<ProcessInfoVO> findByJbpmName(String jbpmName) {
return null;
}
public List<ProcessInfoVO> findByArea(String area) {
return null;
}
}
Vamos a mostrar nuestro Finder interceptor el cual sigue el Interceptor pattern y está soportado por Seam; muy sencillo, si el método se llama findByWhatever el busca un named query llamado findByWhatever, en tu implementación del DAO solo debes retornar null, si retornas algo entonces el finder se anula automáticamente:
package com.viavansi.fdu.interceptor;import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.intercept.InvocationContext;
import com.viavansi.fdu.persistencia.DAO.GenericDao;
/**
* @author gmedina
*
*/
public class FinderInterceptor { /**
*
* @param invocation
* @return
* @throws Throwable
*/
@AroundInvoke
@SuppressWarnings("unchecked")
public Object executeFinder(InvocationContext invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
if (methodName.startsWith("findBy")) {
GenericDao dao = (GenericDao) invocation.getTarget();
Object result = invocation.proceed();
return result == null ? dao.executeFinder(methodName, invocation
.getParameters()) : result;
} else
return invocation.proceed();
}
Nuestro persistence.xml con la configuración de Lucene que necesita:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” version=”1.0″
xsi:schemaLocation=”http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd“>
<!–
Conexión por defecto de la aplicación utilizando POOL de conexiones
–>
<persistence-unit name=”fduPersistenceUnit”
transaction-type=”RESOURCE_LOCAL”>
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:comp/env/jdbc/fdu</jta-data-source>
<class>com.viavansi.fdu.persistencia.VO.UsuarioVO</class>
<class>com.viavansi.fdu.persistencia.VO.MiembroVO</class>
<class>com.viavansi.fdu.persistencia.VO.RolVO</class>
<class>com.viavansi.fdu.persistencia.VO.ProcessInfoVO</class>
<properties>
<property name=”hibernate.show_sql” value=”true” />
<property name=”hibernate.dialect” value=”org.hibernate.dialect.SQLServerDialect” />
<property name=”hibernate.cache.provider_class” value=”org.hibernate.cache.EhCacheProvider” />
<!–
Configuración para el soporte de prefijos en Hibernate. Estrategia
para generación de nombres de tablas asociadas a anotaciones Table
EJB3.0.
–>
<property name=”hibernate.ejb.naming_strategy”
value=”com.viavansi.framework.persistencia.jpa.NamingStrategy” />
<property name=”hibernate.search.default.directory_provider”
value=”org.hibernate.search.store.FSDirectoryProvider” />
<property name=”hibernate.search.default.indexBase”
value=”/Java/lucene/fdu/app” />
</properties>
</persistence-unit> </persistence>
Finalmente nuestro DAO el cual tiene anotaciones de JPA y Hibernate Search/Lucene
package com.viavansi.fdu.persistencia.VO;import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.DateBridge;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Resolution;
import org.hibernate.search.annotations.Store;
import com.viavansi.fdu.persistencia.DAO.ISOLatin1Analyzer;
/**
* @author gmedina
*
*/
@NamedQueries( {
@NamedQuery(name = "ProcessInfoVO.findByProcessName", query = "from ProcessInfoVO processInfo where processInfo.processName = ?"),
@NamedQuery(name = "ProcessInfoVO.findByJbpmName", query = "from ProcessInfoVO processInfo where processInfo.jbpmName = ?"),
@NamedQuery(name = "ProcessInfoVO.findByArea", query = "from ProcessInfoVO processInfo where processInfo.area = ?") })
@Indexed
@Analyzer(impl = ISOLatin1Analyzer.class)
@Entity
@Table(name = "`${PREFIX_FDU}PROCESS_INFO`")
public class ProcessInfoVO implements Serializable {
private static final long serialVersionUID = 1570433106072090149L;
private Long id;
private String processName;
private String jbpmName;
private Date startDate;
private Date dueDate;
private boolean active;
private String area;
/**
* @return the id
*/
@Id
@DocumentId
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
/**
* @param id
* the id to set
*/
public void setId(Long id) {
this.id = id;
}
/**
* @return the processName
*/
@Field(index = Index.TOKENIZED, store = Store.NO)
@Column(name = "PROCESS_NAME", unique = true, nullable = false, insertable = true, updatable = true, length = 255)
public String getProcessName() {
return processName;
}
/**
* @param processName
* the processName to set
*/
public void setProcessName(String processName) {
this.processName = processName;
}
/**
* @return the jbpmName
*/
@Field(index = Index.TOKENIZED, store = Store.NO)
@Column(name = "JBPM_NAME", unique = true, nullable = false, insertable = true, updatable = true, length = 255)
public String getJbpmName() {
return jbpmName;
}
/**
* @param jbpmName
* the jbpmName to set
*/
public void setJbpmName(String jbpmName) {
this.jbpmName = jbpmName;
}
/**
* @return the startDate
*/
@Field(index = Index.UN_TOKENIZED)
@DateBridge(resolution = Resolution.DAY)
@Temporal(TemporalType.DATE)
@Column(name = "START_DATE", unique = false, nullable = true, insertable = true, updatable = true)
public Date getStartDate() {
return startDate;
}
/**
* @param startDate
* the startDate to set
*/
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
/**
* @return the dueDate
*/
@Field(index = Index.UN_TOKENIZED)
@DateBridge(resolution = Resolution.DAY)
@Temporal(TemporalType.DATE)
@Column(name = "DUE_DATE", unique = false, nullable = true, insertable = true, updatable = true)
public Date getDueDate() {
return dueDate;
}
/**
* @param dueDate
* the dueDate to set
*/
public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
/**
* @return the active
*/
@Column(name = "ACTIVE", unique = false, nullable = false, insertable = true, updatable = true)
public boolean isActive() {
return active;
}
/**
* @param active
* the active to set
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* @return the area
*/
@Field(index = Index.TOKENIZED, store = Store.NO)
@Column(name = "AREA", unique = false, nullable = false, insertable = true, updatable = true, length = 100)
public String getArea() {
return area;
}
/**
* @param area
* the area to set
*/
public void setArea(String area) {
this.area = area;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof ProcessInfoVO)) {
return false;
}
ProcessInfoVO other = (ProcessInfoVO) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)) {
return false;
}
return true;
}
}
Nota: Lucene necesita construir un indice inicial, les pegaré un pedazo de código de como hacer esto:
@SuppressWarnings("unchecked")
public void reIndexLuceneDB() {
FullTextEntityManager fullTextEntityManager = Search
.createFullTextEntityManager(entityManager);
Class[] entityClasses = { UsuarioVO.class, RolVO.class,
ProcessInfoVO.class };
for (Class entityClass : entityClasses) {
for (Object object : entityManager.createQuery(
"select obj from " + entityClass.getName() + " obj")
.getResultList()) {
fullTextEntityManager.index(object);
}
}
}
Bueno, he pegado tanto código que explicarlo todo en un solo post es difícil, asi que manden sus preguntas.
Que lo disfruten.
Guido.
Webservice-client con Metro JAX-WS y Autenticación Básica
En esta ocasión os voy a comentar cómo generar un cliente de un webService con autenticación básica (usuario/contraseña , Basic Authentication) usando el plugin para maven JAX-WS.
Doy por hecho que todos sabeis cómo integrar este plugin con maven, podeis obtener toda la información necesaria sobre este plugin en su página oficial.
Al final tendremos algo parecido a esto, introduciendo una nueva entrada en la lista de plugins:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
……
</configuration>
<dependencies>
… lista de dependencias del plugin …
</dependencies>
</plugin>
Para el caso que nos ocupa, vamos a suponer que tenemos un WSDL (Web Services Description Language) desplegado en un servidor que requiere Usuario y Contraseña, ¿cómo podemos indicarle a este plugin la URL al fichero descriptor del servicio web? ¿cómo le indicamos el usuario y contraseña?
Pues todo esto se hace en la sección “configuration“.
Tenemos mucha documentación disponible la web relacionada con wsimport.
Un sabio amigo mío se quejaba una vez en clase de programación, diciendo que la programación era como empanar filetes, y que no le podían enseñar a hacer filetes empanados si antes no se había comido ninguno. Pues bien, siguiendo esta corriente filosófica os pongo un ejemplo y os comento después las secciones que nos afectan:
<configuration>
<wsdlUrls>
<wsdlUrl>[http/https]://[servidor1]:[puerto1]/[rutaAlDescriptor1]?wsdl</wsdlUrl>
<wsdlUrl>[http/https]://[servidor2]:[puerto2]/[rutaAlDescriptor2]?wsdl</wsdlUrl>
</wsdlUrls>
<packageName>mipaquete.ws.client</packageName>
<sourceDestDir>${basedir}/src/main/java</sourceDestDir>
<verbose>true</verbose>
<extension>true</extension>
<xadditionalHeaders>true</xadditionalHeaders>
<xdebug>true</xdebug>
<xauthFile>${basedir}/src/site/resources/auth.conf</xauthFile>
</configuration>
En primer lugar definimos una lista de URL a los distintos ficheros descriptores y posteriormente el paquete en el que generarán las clases consumidoras del servicio web.
Otro parámetro importante en el caso que nos ocupa es “xauthFile”. Este parámetro le indica al plugin una ruta a un fichero, en dicho fichero se almacena el Usuario/Contraseña de acceso a la lista de wsld’s anterior.
Este fichero debe contener una serie de lineas de la siguiente manera:
[http/https]://[usuario]:[contraseña]@[servidor]:[puerto]/[rutaAlDescriptor]?wsdl
Tantas líneas como se requieran según las URL’s indicadas en “wsdlUrls”.
Pues bien, si generamos a continuación las clases, nuestro plugin le pedirá a los servidores los distintos wslds’s y se autenticará correctamente usando el fichero auth.conf.
¿Hemos acabado ya?
Pues hemos hecho la mitad, por ahora lo que tenemos es una serie de clases que pueden consumir los distintos servicios web.
Pregunta, ¿Se volverá a requerir Usuario/Contraseña cuando se invoque a unos de estos métodos clientes del servicio web?
Pues sí.
¿Y se supone que ese usuario y contraseña lo coge del fichero de autenticación anterior?
Pues no, este fichero sólo nos ha servido para generar las clases clientes, nada más.
Entonces, un segundo paso es introducir un sistema de autenticación dentro de nuestro cliente, ¡vamos a ello!
Una de las posibles opciones es generar una clase de autenticación, que extienda de “java.net.Authenticator”.
Su implementación es bastante simple, os pongo un ejemplo:
import java.net.Authenticator;
import java.net.PasswordAuthentication;
public class MiAutenticador extends Authenticator {
static final String user = “miNombreDeUsuario”; // Login
static final String pass = “miContraseña”; // Password
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(user, pass.toCharArray()));
}
}
Creo que no merece más comentarios, sólo indicar que los atributos “user” y “pass” deberían leerse de un fichero de propiedades por ejemplo, pero de ninguna manera deberían estar a fuego en nuestro código.
Ahora que tenemos nuestra clase de autenticación ¿cómo “conectarla” en nuestras clases clientes?
Pues bien, lo que debemos hacer es indicar que deseamos usar este “Autenticador” justo antes de la llamada al
webService que requiere dicha autenticación, algo como:
........
Authenticator.setDefault(new MiAutenticador());
API_webService.metodo(params);
........
Esto se puede implementar de manera elegante usando el patrón de Diseño Decorador (Decorator Pattern ), de manera que por defecto se “decore” los distintos métodos del api webService con nuestro autenticador.
Pues nada más, espero haber sido claro.
Gracias, namasté y buena suerte.
Preprocesado de peticiones SOAP en JAX-WS
En el caso de que necesitemos establecer algún tipo de preprocesamiento/filtro/mecanismo de seguridad a un servicio web, una de las formas más interesantes es utilizando los manejadores HandlerChain que proporciona JAX-WS 2.x.
En primer lugar implementamos el manejador, que nos permitirá realizar procesamientos y postprocesamientos sobre las peticiones SOAP que lleguen a nuestro servicios web. Os dejo un ejemplo de un manejador que permite filtrar por ip:
public class SecurityServiceWebHandler implements MessageHandler{
private static Log log=LogFactory.getLog(SecurityServiceWebHandler.class);
public Set getHeaders() {
return null;
}
public void close(MessageContext context) {
}
public boolean handleFault(MessageHandlerContext context) {
return true;
} /** Comprueba que las ips que acceden a la aplicación son efectivamente ip permitidas.
* @see javax.xml.ws.handler.Handler#handleMessage(javax.xml.ws.handler.MessageContext)
*/
public boolean handleMessage(MessageHandlerContext context) {
ServletRequest servletRequest = ((ServletRequest)context.get(MessageContext.SERVLET_REQUEST));
// Obtenemos la ip remota que invoca al Servicio Web
String remoteAddres=servletRequest.getRemoteAddr();
String allowed=”192.168.10.160,80.58.0.12″;
if(StringUtils.contains(allowed, remoteAddres)){
if(log.isInfoEnabled())log.info(”Servicio Web solicitado desde ip: “+remoteAddres);
}else{
log.error(”Acceso denegado. La ip “+remoteAddres+” no tiene permiso para acceder a los WS.”);
throw new WebServiceException(”Acceso denegado. La ip “+remoteAddres+” no tiene permiso para acceder a los WS.”);
}
return true;
}
}
Una vez implementado el manejador, lo siguiente es publicarlo en el fichero handlerchain.xml
<handler-chains xmlns=”http://java.sun.com/xml/ns/javaee”>
<handler-chain>
<handler>
<handler-class>org.viafirma.conector.security.SecurityServiceWebHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>
El último paso es asociar nuestro servicio web con el manejador, mediante la anotación @HandlerChain.
@HandlerChain(file="handlerchain.xml")
@WebService(serviceName="ConectorFirmaRMIService",targetNamespace = "http://viafirma.org/client/", name = "ConectorFirmaRMIClient",portName="ConectorFirmaRMI",
endpointInterface = "org.viafirma.cliente.firma.rmi.FirmaClienteRMI")
public class ConectorFirmaRMI extends UnicastRemoteObject implements FirmaClienteRMI {
Como ejemplo, gracias a este código podremos recuperar y registrar las ips de todas las peticiones entrantes a nuestro servicio web, y denegar las ips no admitidas.
Firma electrónica y accesibilidad web
En las listas de AccesoWeb de SIDAR mantuvimos en 2005 una interesante discusión sobre la compatibilidad entre los puntos de verificación de accesibilidad de WAI y la firma electrónica, que requiere realizar procesos en cliente con tecnologías obviamente ejecutadas en cliente (Javascript + Applet Java o Active X). En tres años ha habido realmente poca evolución a este respecto, y considero que la discusión se puede mantener vigente.
Sobre la firma electrónica
No confundamos firma electrónica con autenticación con certificado. Podemos acceder (login) a una aplicación web con nuestro certificado, y que sea accesible. En lo que se refiere a la firma electrónica en una aplicación web, hay sin embargo una serie de conceptos claros. El proceso técnico es más o menos como el que sigue (ahorrando varios pasos para no hacer un post tipo Biblia):
- El usuario introduce los datos que debe firmar. Esto puede ser de muchas formas; rellenar un formulario HTML, adjuntar un fichero, etc.
- El navegador del usuario debe generar un resumen (digest) con un algoritmo hash (tipo MD5 o SHA-1) de la información a firmar.
- El navegador debe utilizar la clave privada del certificado digital del cliente para encriptar esta información. Esta encriptación es simétrica; lo que se encripta con la clave privada podrá ser desencriptada con la clave pública, y sólo con esta. Para realizar esta operación, el navegador debe consultar de alguna forma el almacén de certificados del cliente, hacerle escoger el certificado con el que quiere firmar, y utilizar la clave privada almacenada en el certificado escogido para encriptar ese hash. Ya tenemos la firma: es el hash encriptado.
- El navegador envía al servidor de firma todos los datos necesarios: el hash encriptado y la clave pública del certificado digital del cliente. Ahora éste lo almacena con el formato que se considere más oportuno. En cualquier momento se puede verificar la validez de esa firma con una sencilla operación: desencriptando la firma con la clave pública del certificado, y comparándola con el resultado de realizar el hash Almacenar el documento original con su firma (el hash encriptado). De esta forma, si algún día se quiere comprobar que lo que introdujo el usuario (el documento original) no ha sido modificado maliciosamente (lo que le confiere validez legal), se puede realizar una sencilla operación de comprobación de firma, consistente en desencriptar la firma con la clave pública del certificado digital, obteniendo el hash, y aplicando el algoritmo de hash sobre el documento original. Si ambos hash coinciden, la firma es válida.
El problema es que los pasos 2 y 3 deben realizarse en cliente, es decir, en el navegador. Esto se totalmente necesario y obligatorio; precisamente, uno de los puntos clave para mantener la seguridad del proceso se basa en que la clave privada del certificado del cliente nunca sale de la máquina del cliente. Hay un número finito de tecnologías para ejecutar esto en un navegador web: lo más normal son applets Java (como el conocido OpenOCES, que utilizamos en Viafirma y que usan otras plataformas como la de Safelayer) o clientes Active X, invocados desde Javascript. De hecho, éste es uno de los puntos claves para que un trámite telemático sea lo más universal y usable posible: la calidad del componente cliente. Por ejemplo, la semana pasada tardé dos horas, experimentos en 3 máquinas diferentes (ya varios navegadores en cada una, aunque con Opera o Safari no llegas muy lejos) y un buen número de reinstalaciones de componentes para conseguir cambiar a mi mujer de médico de cabecera, debido a la infame calidad de los applets Java de firma de la plataforma @Firma. Si a mí me costó tanto, no quiero imaginar qué le ocurrirá a un usuario de a pie. El mayor problema es que funciona muy mal con el JRE Java 6, pero debería tenerse en cuenta que, salvo que se configure de forma diferente, el JRE se autoactualiza automáticamente, con lo que a estas alturas hasta el panadero de la esquina tiene instalado Java 6 en su máquina.
Sobre la accesibilidad web
En lo que se refiere a la accesibilidad web, las pautas WAI te exigen que para acceder a tu contenido web no sea imprescindible el uso de applets o javascript. En concreto, la pauta 6.3 dice que “Asegúrese de que las páginas sigan siendo utilizables cuando se desconecten o no se soporten los scripts, applets u otros objetos programados. Si esto no es posible, proporcione información equivalente en una página alternativa accesible”.
Según esta pauta entendemos que no podemos tener una calificación AA de accesibilidad si utilizamos javascript y applets, ya que deberíamos tener una página alternativa. El problema es que no podemos tener una página alternativa ya que la operación que realizamos no se puede realizar de otra forma de forma legal.
Sobre las Administraciones Públicas
Por último, las Administraciones Públicas exigen por regla general que sus portales web, incluyendo sus oficinas virtuales, alcancen un nivel AA de accesibilidad, esto es, cumplan todos los puntos de verificación de prioridad 1 y 2.
Pero también exigen que para que una operación telemática sea legal, se haga utilizando firma electrónica que, como hemos visto, tiene problemas insalvables de accesibilidad porque hoy por hoy no es posible firmar sin ejecutar lógica de cliente que no todos los navegadores o dispositivos de usuario tienen por qué soportar. Por ello, la alternativa legal que podemos dar a nuestra web con firma electrónica es que el usuario se vaya a la institución físicamente.
¿No es un contrasentido? Se piden dos cosas incompatibles entre sí.
Una conclusión: quedarse en medio.
Bajo mi punto de vista, no debemos olvidar que las pautas WAI se refieren al acceso al contenido. Una Oficina Virtual está mucho más cerca de una aplicación web que de un portal, por lo que debe plantearse si las pautas aplican con la misma restrictividad. En todo caso, siempre se puede tratar de que toda la web sea accesible realmente, y sacar la zona dotada de firma electrónica de la declaración de accesibilidad. Y esperar a que la tecnología avance para superar estos escollos.
Nosotros caminamos junto a hombres gigantes
Hace 335 años Issac Newton utilizo la locución A hombros de gigantes en referencia a los grandes científicos que le habían precedido como Galileo, Kepler, Copérnico, Aristóteles : …si he podido ver más allá es estando parado sobre hombros de gigantes. Y no hay ninguna duda de que el avance de la Ciencia moderna se apoya en los científicos que les precedieron. En nuestro caso la Informática es demasiado joven para tener una verdadera historia y a pesar de eso ya tenemos mucho que agradecer a los gigantes sobre los que nos apoyamos.En nuestro trabajo, si además apostamos por el Software Libre tenemos que ser conscientes de que no solo nos apoyamos sobre hombros de gigantes sino que gracias al Software libre “Nosotros caminamos junto a hombres gigantes“…Sin buscar demasiado, todo nuestro entorno de desarrollo o cualquiera de nuestros proyectos le debe más al software libre de lo que nosotros jamas podamos retornar. A modo de ejemplo, dejo un pequeño listado de algunas de las librerías que aparecen en los proyectos que hacemos en Viavansi, si uno hace cuentas, es fácil sentirse abrumado por la cantidad de código del que hacemos uso, y de lo mucho que tenemos que agradecer.
- iText ( Bruno Lowagie), commons-lang (Daniel Rall, Stephen Colebourne,…), hibernate-annotations (Emmanuel Bernard,…), commons-beanutils (Robert Burrell Donkin, Craig McClanahan,…) , xalan (David Bertoni, John Gentilin, … ), commons-httpclient (Michael Becke, Jeff Dever, …), httpunit (Christian W. Hargraves, Kay Johansen, …), commons-fileupload ( Martin Cooper, John McNally,…), cglib (Juozas Baliuka , …), struts ( Craig R. McClanahan, Ted Husted, … ), dom4j (Curt Arnold, David White, … ), nekohtml( Andy Clark,… ), xml-security:xmlsec ( Derek Morr, Pete Hendry, …), jboss-javassist ( Shigeru Chiba,… ), xerces(Arnaud Le Hors, Tozawa Akihiko, …), hibernate-entitymanager ( Gavin King, …), commons-io (Serge Knystautas, …), net.openid:core (Jason Alexander, …), com.sun.facelets:jsf-facelets (Jacob Hookom, …), myfaces ( Ted Husted, …), bouncycastle (Tito Pena, …), …(1000s…)
Soy consciente de que me he dejado cientos de proyectos y miles de personas fuera de este listado, pero …Mil gracias a todos!
Busca esto rápido
Encuentra lo que buscas de forma sencilla usando el buscador.
Categorías
Encuentra artículos a través de sus "tags"
Archivos mensuales
Encuentra artículos según el mes en el que fueron escritos.



