viernes, 31 de diciembre de 2010

Crear estructura de proyecto inicial con Spring Roo

Una de las cosas que más tiempo lleva cuando queremos empezar un nuevo proyecto Java es la creación de la estructura inicial: Estructura inicial de directorios, importar librerías, configurar frameworks (spring, mvc, persistencia, etc...).

Una posible solución rápida para esto puede ser utilizar Spring Roo.

Resumiendo un poco, Spring Roo es una herramienta que te proporciona una consola mediante la que puedes ejecutar comandos para ir añadiendo funcionalidades a tu aplicación. Por ejemplo, con un solo comando te añade todo lo necesario para conectarte mediante JPA a una base de datos (librerías necesarias, configuración de Spring, etc...). También te permite crear entidades fácilmente y por cada una de ellas te genera un CRUD automáticamente. Si en este punto quieres saber más sobre esta herramienta te recomiendo un webinar de aproximadamente una hora que impartió ecamacho: Webinar sobre Spring-Roo.

En mi caso voy a utilizar Spring Roo para que me cree la estructura inicial del proyecto, que me configure la capa de la persistencia con Hibernate y PostgreSQL, y que me añada las dependencias necesarias para funcionar con Spring MVC.

A continuación explico los pasos necesarios:

Primero nos descargamos Spring-Roo de la siguiente dirección: http://www.springsource.org/download. Una vez lo tengamos descargado y descomprimido, nos creamos un directorio donde guardaremos nuestro proyecto y ejecutamos el script que se encuentra en '/bin/roo.sh'



Si vamos tecleando 'hint' y hacemos caso de las instrucciones que nos va indicando llegaremos rápidamente a la creación de nuestro proyecto. En mi caso los comandos que he utilizado son:

project --topLevelPackage com.mlopez.appname
persistence setup --provider HIBERNATE --database POSTGRES
entity --class com.mlopez.appname.entities.Location
controller all --package ~.web

En estos momentos ya tenemos un proyecto con todo lo necesario para que se nos visualice un CRUD para una entidad de nombre 'Location'. Lo siguiente que vamos a hacer es importar este proyecto desde el eclipse y probar a ejecutarlo.

Es necesario tener instalado en eclipse el plugin m2eclipse.

Para importarlo creamos un nuevo proyecto de tipo 'Dynamic Web Project' apuntando al directorio donde ha generado todo Spring roo, Dentro del wizard tendremos que indicar que el source folder va a ser 'src/main/java', el default output folder 'target/classes' y el content directory va a ser 'src/main/webapp'

Para que coja las dependencias de Maven, sobre el proyecto hacemos botón derecho y ejecutamos "Maven -> Enable Dependency Management". De esta forma ya tendremos el proyecto sin ningún error de compilación.

Antes de arrancar la aplicación vamos a abrir el fichero 'database.properties' que se encuentra dentro del directorio 'src/main/resources/META-INF/spring' para configurar la conexión con la base de datos:

database.password=turismo
database.url=jdbc\:postgresql\://localhost\:5432
database.username=turismo
database.driverClassName=org.postgresql.Driver

Si desde el eclipse añadimos la aplicación a un tomcat y lo arrancamos veremos como se nos visualiza la siguiente pantalla:



Genial, ya tenemos nuestra aplicación funcionando con las versiones de los frameworks más nuevos sin haber gastado nada de tiempo.

Llegado a este punto, a mi ya no me interesa utilizar Spring Roo para nada y quiero empezar a introducir código desde el eclipse como yo quiera. Si echamos un vistazo a lo que ha generado Spring Roo vemos que ha generado un controller y una entidad y que su contenido está vacio y se inyecta mediante ficheros con extensión aj. Los pasos para eliminar completamente Roo de nuestra aplicación son los siguientes:

El contenido de los aj lo pasamos a su clase correspondiente y borramos posteriormente esos ficheros. Eliminamos todas las anotaciones relacionadas con Roo y en el pom.xml eliminamos la dependencia que tenemos con springroo.

Al haber añadido una anotación @Configurable en nuestra entidad es conveniente ejecutar un mvn clean de nuestro proyecto para que se vuelvan a compilar todas las clases de nuevo. Esto es necesario porque estamos utilizando 'Compile Time Weaving' para que en tiempo de compilación se modifique el class de nuestra entidad y cada vez que se cree una nueva instancia se le inyecten todos los @Autowired y @PersistenceContext que posea. Para leer más sobre esto recomiendo el siguiente weblog.

Si volvemos a arrancar la aplicación con los cambios realizados, todo debería funcionar igualmente. Ahora ya podemos trastear todo lo queramos con el código y hacer las modificaciones que creamos convenientes.

miércoles, 8 de diciembre de 2010

Personalizar proyecto según el cliente con Maven

Es un caso bastante habitual que se desarrolle un proyecto web que después vaya a ser vendido a varios clientes. En el producto final que se le venderá al cliente se tendrán que realizar una serie de personalizaciones, por ejemplo la imagén del logo, alguna css para cambiar los estilos generales de la aplicación, los valores de algunos ficheros properties (mensajes internacionalizados), etc...

Lo que considero que nunca habría que hacer es hacer una copia del proyecto y modificar los ficheros necesarios, ya que cada vez que haya que hacer un cambio en la base, también habría que hacerlo en todos los clientes.

Con maven he encontrado una manera sencilla y rápida de hacer esto, que consiste en configurarlo con profiles para que justo antes de que se genere el war final, se sobreescriban o añadan una serie de ficheros.

A continuación voy a explicar qué tipos de cosas son las que yo he necesitado personalizar y cómo hacerlo en el pom.xml de Maven.

Ficheros properties que se encuentran en el classpath de la aplicación. Generalmente esos ficheros deberían encontrarse en 'src/main/resources'. Podría interesarnos modificar algun valor concreto: Por ejemplo, imaginaros que tenemos el fichero bundle con las textos traducidos que se van a mostrar en la web, y que un texto es:

footer.text = Copyright 2010, Compañia bla bla bla bla Compañia bla bla bla.

En nuestro caso querríamos sustituir el literal "Compañia" por el nombre del cliente. Para hacer esto, lo más sencillo me parece lo siguiente: Primero sustituimos el texto footer.text con el siguiente valor:

footer.text = Copyright 2010, ${company.name} bla bla bla bla ${company.name} bla bla bla.

Nos vamos a crear un directorio nuevo 'src/main/profiles' donde vamos a crear una carpeta por cada cliente nuevo. En nuestro caso vamos a crear una carpeta 'cliente1' y ponemos en un fichero de propiedades 'filters.properties' el valor de la propiedad company.name.

company.name=Cliente1

Ahora tenemos que configurar en el pom.xml para que en los ficheros properties se filtren las propiedades y se sustituyan por los valores concretos y también habrá que crear un profile nuevo para que cuando lo utilicemos tenga en cuenta que los valores de las propiedades se encuentran en el fichero 'src/main/profiles/cliente1/filters.properties'



.....


src/main/resources

**/*.properties

true


.....

......


cliente1


src/main/profiles/cliente1/filter.properties






Ejecutando 'mvn clean package -P cliente1' ya tendremos en nuestro fichero de propiedades el valor correcto para el footer.text.

Supongamos que este modo de trabajar no nos vale porque lo que queremos hacer es sobreescribir el fichero de propiedades entero. En este caso yo dejaría los ficheros finales en el directorio 'src/main/profiles/cliente1/resources' y configurar dentro del profile para que se sobreescriban.


......


org.apache.maven.plugins
maven-resources-plugin
2.4.3

ISO-8859-1
true



......


cliente1


src/main/profiles/cliente1/filter.properties



src/main/profiles/cliente1/resources

**/*.properties

true







La primera parte donde se define el plugin 'maven-resources-plugin' es muy importante, ya que hay que definir la configuración '<overwrite>true</overwrite>' porque sino los ficheros no se sobreescribirán.

Otra cosa que podemos querer sobreescribir son las imagenes, css y contenido estático que se encuentre en el directorio 'src/main/webapp'.

Para ello colgamos los ficheros que queramos sobreescribir en el directorio 'src/main/profiles/cliente1/webapp' manteniendo dentro la misma estructura de directorios que se sigue en 'src/main/webapp'. Y después lo único que nos falta sería configurar maven para que nos copie esos ficheros. Esto último es un poco más complicado que como lo hemos hecho hasta ahora porque no se puede utilizar el tag <resources> ya que siempre nos lo copiará dentro del directorio WEB-INF/classes. Si queremos definir un nuevo directorio lo tenemos que hacer de la siguiente manera:



cliente1


src/main/profiles/cliente1/filter.properties



maven-resources-plugin
2.4.3

ISO-8859-1
true



copy-personalization
process-resources

copy-resources


${basedir}/target/${project.artifactId}-${project.version}


src/main/profiles/cliente1/webapp











Si ejecutamos 'mvn clean package -P cliente1' deberían estar todos los ficheros sobreescritos, pero no es así. ¿Por qué? La razón es que en la fase 'process-resources' en la que se está ejecutando la tarea 'copy-personalization' no se ha creado todavía el directorio 'target/${project.artifactId}-${project.version}'. La única forma posible que he encontrado de solucionar este problema es indicarle a maven que adelante la creación de este directorio mediante el goal 'war:exploded'. Por lo que la ejecución de Maven final que funciona es:

mvn clean war:exploded package -P cliente1

lunes, 6 de diciembre de 2010

Vistas de Bases de datos con JPA

Una de las cosas que más me gusta de JPA es que una vez que desplegamos nuestra aplicación web no tenemos que preocuparnos de la creación de las tablas en la base de datos ni de los tipos de los campos.

Si tenemos que trabajar con vistas en base de datos, JPA las trata de la misma forma que si fueran tablas. Con que pongamos la anotación @Entity encima de una clase y se le de el nombre de una vista, se trabajaría igual que con cualquier tabla. La única diferencia es que no se pueden realizar operaciones de escritura (inserts y updates).

Si tenemos configurado JPA para que automáticamente genere todas las tablas, tenemos un pequeño problema, ya que para crear la vista, primero tendríamos que arrancar la aplicación web y que se creen todas las tablas necesarias. En ese momento también se creará una con el nombre de la vista que nosotros queremos crear, ya que como hemos dicho antes, le hemos añadido la anotación @Entity como al resto de entidades.

Esta tabla no tiene ningún sentido porque nunca vamos a meter datos en ella. Lo que tendremos que hacer es eliminarla y generar la vista manualmente después del primer arranque.

No sé si será lo más óptimo posible o si hay alguna otra posibilidad que yo desconozca, pero se me ocurrió una forma de poder añadir un listener en nuestra aplicación web para que no sea necesario tener que realizar todos estos pasos y que en el momento de arrancar la aplicación, se realicen automáticamente y no tengamos que preocuparnos en adelante por la creación manual de las vistas.

La idea es añadir a nuestra aplicación un Listener que se encargue de realizar los siguientes pasos:

- Mirar si existe la tabla con el nombre de la vista que queremos crear. En caso de que exista (Esto ocurriría únicamente la primera vez que arranca la aplicación web) se borrará.
- Si la vista existe (Esto ocurriría en los posteriores arranques de la aplicación), ésta se borrará para que si ha habido algún cambio de versión en la aplicación que requiere añadir, suprimir o modificar algún campo de la vista, no sea necesaria ninguna intervención manual.
- La query de la vista estará harcodeada en el listener, y lo que se hará después será crearla.

Este listener necesita que todas las tablas estén ya creadas, por lo que se tendrá que ejecutar siempre después del de Spring, que es el que se encarga de cargar JPA. Por ello, es importante que no se nos olvide ponerlo siempre después del ContextLoaderListener en el fichero web.xml


.....


org.springframework.web.context.ContextLoaderListener




RUTA DEL LISTENER

.....


El código del listener lo pongo a continuación. Si alguien lo quiere reutilizar que tenga en cuenta que la forma en la que he obtenido por código una referencia al DataSource de la base de datos posiblemente será diferente, ya que yo he aprovechado que el DataSource lo tengo definido como un Bean de Spring. Otra persona igual tiene que recuperarlo directamente mediante JNDI.



public class DBViewsCreator implements ServletContextListener
{
private static final Log log = LogFactory.getLog(DBViewsCreator.class);
private static final String TESTVIEW_NAME = "vplayer";
private static final String TESTVIEW_QUERY = "Select * from player p where p.active = true";

public void contextInitialized(ServletContextEvent servletContextEvent)
{
log.info("Creating DB views");
Connection conn = null;
try {
DataSource ds = getDatasource();
conn = ds.getConnection();
DatabaseMetaData md = conn.getMetaData();
DatabaseUtils.recreateView (md, TESTVIEW_NAME, TESTVIEW_QUERY, conn);
} catch (Exception e) {
log.error("Error during Database Views creation",e);
} finally {
try {
if (conn != null) conn.close(); }
catch (SQLException e) {
log.error("Error closing database connection",e);
}
}
}

private DataSource getDatasource (){
//Obtenemos el datasource de donde nos venga mejor, ya sea mediante JNDI o accediendo a un bean de Spring.
// En mi caso he utilizado la clase AppContext para recuperar el bean con nombre "dataSource". Explicada en
// http://blog.jdevelop.eu/2008/07/06/access-the-spring-applicationcontext-from-everywhere-in-your-application/
DataSource ds = (Datasource)AppContext.getApplicationContext().getBean ("dataSource");
return ds;
}

public void recreateView (DatabaseMetaData md, String viewName, String query, Connection conn) throws SQLException{
deleteView(md, viewName, query, conn);
createView(md, viewName, query, conn);
}

public void deleteView (DatabaseMetaData md, String viewName, String query, Connection conn) throws SQLException{
//primero comprobamos si existe la definición con el nombre de la vista, ya sea tabla o vista.
if (existsQuartzSchema(md, viewName)){
//Si existe la borramos y recreamos para tenerla siempre actualizada.
Statement stmt = conn.createStatement();
try {
//La primera vez que se arranque la aplicación, la siguiente instrucción dará error
// porque no existe la vista.
stmt.execute("DROP VIEW "+viewName);
} catch (SQLException e) {
try {
stmt.execute("DROP TABLE "+viewName);
} catch (SQLException e1) {
log.error("Error droping table "+viewName, e1);
throw e;
}
} finally{
if (stmt != null) stmt.close();
}
}
}

public static void createView (DatabaseMetaData md, String viewName, String query, Connection conn) throws SQLException{
//Creamos la vista.
Statement stmt = conn.createStatement();
try{
stmt.execute("CREATE VIEW "+viewName+" AS ("+query+")");
log.info("view "+viewName+" created successfully");
} catch (SQLException e) {
log.error("Error creating view "+viewName+" with query: "+query, e);
throw e;
} finally{
if (stmt != null) stmt.close();
}
}

public void contextDestroyed(ServletContextEvent servletContextEvent) {}
}


Espero que os sirva de ayuda, y si a alguien se le ocurre una idea mejor de cómo integrar las vistas en JPA que lo comente por favor.

domingo, 21 de noviembre de 2010

Autenticación en servicios web con CXF

En esta entrada voy a explicar los pasos necesarios para montar una capa de servicios web, en la que, para invocarlos, sea necesario enviar un usuario y contraseña en la cabecera de la petición (HTTP Basic Authentication).

Cómo ahora en el trabajo estoy utilizando Apache CXF, me voy centrar en este framework.

Lo primero de todo es incluir dentro de nuestro proyecto la siguiente clase: BasicAuthAuthorizationInterceptor.

Una vez tengamos la clase dentro de nuestro classpath, creamos un bean de spring sobre ella:











Como podemos apreciar, este bean esta pensado para que se le inyecte en la propiedad "users" un listado de todos los usuarios/contraseñas a los que se les permitirá invocar a los servicios web. Este comportamiento puede ser modificado muy fácilmente para que los usuarios y contraseñas que utilice no sean estos, sino que se obtengan de una base de datos o de algún fichero de propiedades.

Una vez que tenemos creado el interceptor, lo aplicamos a los endpoints de CXF que deseemos.


id="serviceEndpoint"
implementor="#service"
address="/ws/Service">







Y con esto ya está funcionando la validación en la capa de servicios web. Muy fácil de configurar :-)

Toda esta información la he sacado del siguiente enlace.

jueves, 18 de noviembre de 2010

Configurar CXF en proyecto con Hibernate

Recientemente he tenido que incorporar las librerías de CXF para exponer unos beans de Spring como servicios web. He seguido todos los datos que se explican en el sitio web del proyecto y aún así me estaba encontrando continuamente con un problema.

Los métodos que exponía como servicio web estaban devolviendo un objeto básico con sus getters/setters y al invocarlo me daba el siguiente error:


‘org.apache.cxf.interceptor.Fault: Marshalling Error: ReturnObject is not known to this context’.


Trás mucho indagar por internet vi la solución. Gracias sobre todo al siguiente enlace. Resulta que hay una incompatibilidad entre la versión de la librería ASM que usa Hibernate (1.5.3) y la que necesita CXF (2.2.3).

Las dependencias de mi proyecto están gestionadas con Maven, por lo que la solución fue sencilla: Poner una cláusula de exclusion en la dependencia de hibernate para que no se traiga ASM ni CGLIB, añadir una dependencia con CGLIB-NODEP y dejar que las dependencias de CXF se traigan exactamente la versión de ASM que necesitan.

En el pom.xml quedaría así:



org.hibernate
hibernate
3.2.5.ga
compile


asm
asm


asm
asm-attrs


cglib
cglib





cglib
cglib-nodep
2.1_3
compile



org.apache.cxf
cxf-rt-frontend-jaxws
${cxf.version}



org.apache.cxf
cxf-rt-transports-http
${cxf.version}



Una vez realizados estos cambios, el servicio web funcionó como un tiro :-D

martes, 16 de noviembre de 2010

Importar certificado en la máquina virtual

Si se desea realizar una conexión segura desde un cliente de java (Por ejemplo para invocar a un servicio web remoto mediante https) hay que realizar los siguientes pasos:

  • Obtener el certificado del servidor al que nos queremos conectar. Muchas veces se puede obtener desde el mismo navegador. Se puede exportar desde el firefox muy fácilmente.

  • Ejecutar el siguiente comando en la consola desde el directorio donde tengamos instalado el ejecutable keytool de la máquina virtual de java:


keytool -import -alias <Alias> -file <FICHERO_CERTIFICADO> -keystore <JRE_HOME>/jre/lib/security/cacerts

la contraseña por defecto es "changeit".

Si estamos utilizando el cliente de java desde una aplicación desplegada en un tomcat por ejemplo, será necesario reiniciarlo para que funcione correctamente.

Así de sencillo.

jueves, 21 de octubre de 2010

Reemplazar textos con Maven

En el trabajo he tenido que mostrar la versión actual del producto en el footer de la página.

Lo normal en este caso creo que sería tener un fichero de propiedades en algún sitio de la aplicación y desde el footer.jsp mostrar el valor de esa propiedad. Para que cuando generamos el war de la aplicación automaticamente en ese .properties aparezca la versión utilizada en el pom.xml nos podemos valer fácilmente de:




src/main/resources

**/*.properties
**/*.xml
**/*.xmls
**/*.sql

true




Y dentro del fichero .properties tener una propiedad que sea "version=${pom.version}".

Pero la verdad es que no lo he hecho así. Me ha dado pereza crearme un fichero de propiedades sólo para mostrar la versión.

¿Por qué no se puede hacer el filtering de los resources sobre todas las jsps? Indicándole que el sería "src/main/java/WEB-INF/jsp". Así lo pensé yo y cuando se me generó el war y lo desplegué me llevé una desagradable sorpresa.

En las jsps estabamos utilizando expresiones EL, que tienen la misma nomenclatura que las variables de maven: ${...}. Y al realizar el filtering de Maven, se me estaba modificando el valor de algunas variables EL.

Debo reconocer que llegado a este punto debería haber vuelto a la idea inicial, pero siguió dándome pereza e intenté investigar otra forma de realizar sustituciones de tokens concretos y llegué a este interesante plugin: maven-replacer-plugin.

En footer.jsp puse el siguiente código:

version: PROJECT_VERSION

y en maven configuré el plugin:




com.google.code.maven-replacer-plugin
maven-replacer-plugin
1.3.2



project_version

prepare-package

replace




target/${project.artifactId}-${project.version}/WEB-INF/**/*.jsp

false

PROJECT_VERSION

${pom.version}







De esta forma le indicamos que busque en las jsps el token exacto 'PROJECT_VERSION'.

Sólo hay que tener en cuenta una cosa más para que esto funcione. en el momento en el que se ejecuta este plugin el directorio de la aplicación todavía no se encuentra en el directorio target, por lo que no se realiza bien el replace si se ejecuta la tarea 'mvn clean package'. Es necesario ejecutar 'mvn clean war:exploded package'

lunes, 18 de octubre de 2010

Integración Spring MVC con Tiles

En el siguiente post voy a explicar los pasos necesarios para realizar una integración sencilla de Spring MVC con Tiles.

El objetivo de esta integración es que automáticamente se aplique una plantilla a una vista sin necesidad de que se tenga que definir en el fichero tiles-def.xml cada vez. Dependiendo de la url con la que se invoque a la aplicación se ejecutará una u otra-

Por ejemplo:
- http://server/appname/player/home --> Cómo la url empieza por /player automáticamente cogerá la plantilla de tiles para los jugadores
- http://server/appname/crm/home --> Cómo la url empieza por /crm automáticamente cogerá la plantilla de tiles para los usuarios del CRM.

Para hacer esto me he basado en el siguiente tutorial: Spring by Example's Dynamic Tiles 2 Spring MVC Module. Recomiendo la lectura de este enlace, ya que voy a extender la funcionalidad que se explica ahí.

Lo primero que tenemos que hacer es importar las librerías necesarias mediante Maven:



springbyexample.org
Spring by Example
http://www.springbyexample.org/maven/repo


....


org.apache.tiles
tiles-core
2.2.1


org.apache.tiles
tiles-jsp
2.2.1


org.apache.tiles
tiles-servlet
2.2.1


org.apache.tiles
tiles-servlet-wildcard
2.2.1


org.apache.tiles
tiles-el
2.2.1


org.springbyexample
org.springbyexample.dynamic.tiles2
1.2


org.springframework
org.springframework.beans


org.springframework
org.springframework.web


org.springframework
org.springframework.context


org.springframework
org.springframework.context.support


org.springframework.webflow
org.springframework.webflow


org.apache.tiles
com.springsource.org.apache.tiles.core


org.apache.commons
com.springsource.org.apache.commons.digester


org.apache.commons
com.springsource.org.apache.commons.beanutils


org.apache.commons
com.springsource.org.apache.commons.logging


org.apache.tiles
com.springsource.org.apache.tiles


org.springframework
org.springframework.core


org.springframework
org.springframework.web.servlet


org.springframework.webflow
org.springframework.js





Después de esto hay que definir en los ficheros de configuración de Spring para que se cojan las definiciones de tiles de su fichero correspondiente:



class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">


/WEB-INF/tiles-def.xml





Dentro de este fichero 'tiles-def.xml' configuramos las dos plantillas que queremos utilizar:



"-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN"
"http://tiles.apache.org/dtds/tiles-config_2_0.dtd">

























Vamos a crear una nueva clase en nuestro proyecto que extienda la funcionalidad que nos proporciona 'springbyexample'. En este caso vamos a prepararlo para que dependiendo de la ruta de la vista a la que vayamos a llamar se invoque a una plantilla o a la otra. La clase en cuestión es la siguiente:



public class ViewNameStartTilesUrlBasedViewResolver extends org.springbyexample.web.servlet.view.tiles2.TilesUrlBasedViewResolver{

private String viewNameStartsWith = "";

@Override
protected boolean canHandle(String viewName, Locale locale) {
if (viewName.startsWith(viewNameStartsWith)){
return super.canHandle(viewName, locale);
}
return false;
}

public void setViewNameStartsWith(String viewNameStartsWith) {
this.viewNameStartsWith = viewNameStartsWith;
}

}



Después definimos dos beans en Spring que se encarguen de evaluar si se tiene que ejecutar una plantilla o la otra.



class="com.example.ViewNameStartTilesUrlBasedViewResolver">
value="org.springbyexample.web.servlet.view.tiles2.DynamicTilesView">








class="com.example.ViewNameStartTilesUrlBasedViewResolver">
value="org.springbyexample.web.servlet.view.tiles2.DynamicTilesView">










Tal y como hemos configurado esto, cada vez que Spring devuelva una vista, se ejecutará primero la comprobación de si la vista empieza por "player/". En caso de que así sea se ejecutará la plantilla '.playerMainTemplate'. Si no es así se evaluará a ver si empieza por 'crm/' y se ejecutaría su plantilla correspondiente.

Otra cosa a tener en cuenta es donde se tiene que encontrar la jsp para que la vista se ejecute correctamente. Si Spring MVC devuelve una vista con valor 'player/account/home', se buscará una jsp en la ruta: 'WEB-INF/jsp/player/account/home.jsp' automáticamente, sin necesidad de que lo definamos nosotros en tiles-def.xml.

jueves, 15 de abril de 2010

Excluir clases al obtener la cobertura de tests

Considero que saber en todo momento el porcentaje (cobertura) de nuestro código que está siendo testeado por nuestras pruebas unitarias es algo muy util para saber la calidad con la que ha sido desarrollado.

También me parece que hay que intentar mantener que este porcentaje nunca baje de un límite (por ejemplo 80%).

Si se utiliza Scrum creo que es un elemento que hay que comprobar en las finalizaciones de todos los Sprints y tenerlo muy en cuenta en las retrospectivas para saber si vamos por buen camino o no.

También se puede llegar a indicarle a Maven que si el porcentaje de tests está por debajo de un limite ni siquiera llegue a compilarse el paquete y genere un error.

Recientemente nos hemos encontrado con un problema en el trabajo, y es que nuestra cobertura estaba bajando debido a unas clases que no pueden ser testeadas o en las que no se debería perder tiempo haciendo tests tontos:

- Entidades: ¿Realmente es necesario que todos los getter/setters de un bean se testeen si lo unico que hacen es devolver y settear un valor? ¿Acaso no confiamos en la generación automática de get/set del Eclipse? Yo personalmente no perdería el tiempo testeando estas clases.

- Stubs de Web services: Supongamos que tenemos unos stubs generados a partir de un WSDL. Me niego rotundamente a tener que testear estas clases.

Seguramente que habrá mil casos mas de clases que no es necesario testear y que mucha gente se planteará hacerlo para que la cobertura del proyecto no baje.

En vez de hacer malabarismos, hay una solución muy sencilla que es indicarle al plugin maven de cobertura que excluya las clases que queramos:



org.codehaus.mojo
cobertura-maven-plugin



com/company/models/*.class






clean







Con esta configuración le estamos indicando a cobertura que pase de todos mis modelos, que son los que tienen los get/set :-)

Testear daos con testng

Al hilo del anterior post, ahora voy a comentar como realizar tests de los daos mediante otro popular framework de testing: TestNG.

En esta ocasión la clase de Spring de la que va a extender nuestro test es AbstractTransactionalTestNGSpringContextTests.

Para incorporar esta clase a nuestro classpath podemos hacerlo fácilmente añadiendo la dependencia maven del proyecto spring-test:


org.springframework
spring-test
2.5.6
test



Tenemos que utilizar una anotación @ContextConfiguration para indicarle donde se encuentra el fichero xml con la configuración de spring para cargar la configuración de conexión a la base de datos de testing.

Si queremos que las tablas de nuestra base de datos se encuentren en un estado determinado, podemos realizar las inserts que consideremos oportunas en el método que tenga la anotación @BeforeMethod

A continuación pongo el ejemplo:


@ContextConfiguration(locations={"classpath:/testng-spring-core-services-config.xml"})
public class RoleDaoTest extends AbstractTransactionalTestNGSpringContextTests {

@Autowired
private IRoleDao roleDao;

@Test
public void testFindByRoleNameExists (){
Role role = roleDao.findByRoleName(RoleName.Admin);
assertNotNull (role);
}

@Test
public void testFindByRoleNameNotExists (){
Role role = roleDao.findByRoleName(RoleName.AdvancedCrmUser);
assertNull (role);
}

@BeforeMethod
protected void onSetUpInTransaction() throws Exception {
Role roleAdmin = new Role();
roleAdmin.setName(RoleName.Admin);
roleDao.create(roleAdmin);
}

}


Como se ha comentado antes, el fichero testng-spring-core-services-config.xml es el que tiene la configuración de spring necesaria para que funcione el test. En mi caso tiene la siguiente pinta:



xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">



class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">











class="org.springframework.jdbc.datasource.DriverManagerDataSource">












Testear daos con JUnit

Para testear daos con junit me voy a basar en una clase que proporciona Spring: org.springframework.test.jpa.AbstractJpaTests.

Para incorporar esta clase a nuestro classpath podemos hacerlo fácilmente añadiendo la dependencia maven del proyecto spring-test:


org.springframework
spring-test
2.5.6
test



Para poder crear el test hacemos que nuestra clase extienda de AbstractJpaTests. Dentro de ella tenemos dos métodos importantes:

- onSetUpInTransaction: Es el método en el que vamos a realizar los inserts en nuestras tablas de la base de datos de tests para que se quede en el estado que necesitamos. Podemos estar tranquilos porque una vez terminado el test, estos cambios son desechados automáticamente.

- getConfigLocations: En este método es donde le indicamos donde se encuentra el fichero xml de spring donde se carga la configuración de la base de datos de test.

Posteriormente ya podemos implementar nuestros métodos de test (por supuesto empezando todos con "test" para que lo coja JUnit) en los que llamaremos a los daos y comprobaremos mediante asserts el correcto funcionamiento de los mismos.


public class GenericDaoTest extends AbstractJpaTests {

protected void onSetUpInTransaction() throws Exception {
jdbcTemplate.execute("insert into GenericModel (id, attr1) values (1, 'dato1')");
jdbcTemplate.execute("insert into GenericModel (id, attr1) values (2, 'dato2')");
jdbcTemplate.execute("insert into GenericModel (id, attr1) values (3, 'dato3')");
jdbcTemplate.execute("insert into GenericModel (id, attr1) values (4, 'dato4')");
}

protected String[] getConfigLocations() {
setAutowireMode(AUTOWIRE_BY_NAME);
return new String[] { "classpath:/junit-spring-services-config.xml" };
}

// METODOS DE TEST

}


A continuación se muestra un ejemplo de cómo podría ser el fichero de configuración de spring para que cargue correctamente los atributos de conexión a la base de datos:































lunes, 15 de marzo de 2010

Receta de pizza

A petición de minipimer voy a hacer una excepción en el contenido de este blog y voy a explicar una forma sencilla de hacer una masa de pizza :-D

Los ingredientes para 2 personas son:
  • 150 gr. de Harina
  • 50 gr de Mantequilla
  • 2 cucharadas soperas de aceite
  • Un poco de agua
Cortamos la mantequilla y la ponemos a calentar en pequeñas tiras en una sartén para que se haga líquida. Mientras echamos en un bol los 150 gr de harina y el aceite.


Una vez lo tenemos todo en el bol, únicamente hay que echar un poco de agua. La medida que utilizo yo es con un vaso de los pequeños. Para que os hagáis una idea añado una imagen con la cantidad de agua:


Una vez echamos el agua, lo removemos.

Hay que remover primero con la cuchara y después un poco con la mano hasta que se quede una bola. Le echamos un poco de harina por encima y lo dejamos reposar un rato. Yo lo suelo dejar 20 minutos aprox. El tiempo justo que tardo en preparar los ingredientes que voy a echar por encima.


Una vez ha reposado toca extenderlo. Primero con la mano y luego con un amasador. Yo como de lo último no tengo, utilizo una botella de cristal :-)

Echamos un poco de aceite sobre la bandeja que vayamos a utilizar y extendemos la pizza sobre ella.


Ahora ya sólo queda rellenarla de lo que más os guste. Últimamente yo suelo echar los siguientes ingredientes (en orden): tomate, tranchetes, revuelto de pimiento, cebolla, champis, jamón york, y un huevo. Encima de este revuelto echo una capa de tomates cortados en finas láminas y anchoas. Un poco más de queso por encima y el orégano de rigor.



El tiempo que lo suelo dejar en el horno es de 20 minutos a 200º grados. Supongo que cada horno es un mundo.

Pues ale, que aproveche!!

domingo, 14 de marzo de 2010

Ofuscar código Java con Maven

Recientemente he tenido que configurar un plugin en Maven para que automáticamente genere los jars ofuscados.

La solución que voy a proponer se basa en el proyecto Yguard. También existe Proguard que se integra bien con Maven aunque, para las necesidades que tenía, se ajustaba mejor el primero.

La configuración del plugin que hay que añadir al pom.xml de Maven es la siguiente:




maven-antrun-plugin


yguard
yguard
2.3.0.1




package







in="${project.build.directory}/${project.build.finalName}.${project.packaging}"
out="${project.build.directory}/${project.build.finalName}.jar" >

replaceClassNameStrings="true">



















run







Como se puede ver, este plugin se ejecuta en la fase 'package' de Maven, justo antes de generar el jar final.

No todo el código debe ser ofuscado. Esto lo tenemos que tener en cuenta sobre todo si estamos desarrollando una librería o unos beans de Spring que tienen que ser accedidos desde otros proyectos. Por ello hay que indicarle a YGuard que no ofusque las partes definidas como públicas en el código.

Si estamos trabajando con JPA y con beans definidos como entidades (@Entity) nos podemos encontrar con la desagradable sorpresa de que los atributos que tiene definidos como Columnas (@Column) son privados y por ello también se han ofuscado. Esto quiere decir que si vamos a la base de datos veremos que, en vez de los nombres de columnas que habíamos definido inicialmente, se han creado columnas en las tablas con nombres como A,B,C,D,etc...

No he encontrado una manera muy elegante para solucionar esto. Lo ideal sería especificarle a YGuard que no ofusque aquellas clases que tengan la anotación @Entity, pero no he encontrado ninguna configuración que lo permita. Si alguien sabe cómo hacer esto agradecería que lo comentara :). En cambio lo que sí se puede especificar es que no ofusque aquellas clases que acaben con Entity.

En nuestro equipo hemos llegado a la convención de que todas las clases que sean entidades terminen con el nombre Entity: (UserEntity, RoleEntity, etc...)

la configuración para especificar que no ofusque estas clases viene en la sección keep:











Buenos días

Buenos días

Inicio este weblog con la intención de aportar mi granito de arena al mundo del desarrollo Java. Durante toda mi carrera profesional me he apoyado sobre información que he leido en weblogs y foros y muy pocas veces han sido las veces en las que he contribuido con soluciones a los problemas con los que he ido encontrándome y solucionando.

Mi idea es ir publicando mini soluciones a cosas comunes que pueden darse en las tecnologías con las que trabajo día a día: Java, Maven, Hudson, Tests, Spring, etc...

Espero que no sea una iniciativa que quede en el olvido en un par de meses y que poco a poco consiga que sea un blog con buen contenido :-)

Nos vemos