jueves, 31 de marzo de 2011

Conectar 2 bases de datos con JPA

Leyendo sobre el tema he visto que esto es algo tan sencillo como duplicar los beans entityManager, entityManagerFactory y datasource con la información de conexión a la base de datos nueva. También habría que generar un nuevo persistence-unit en el fichero persistence.xml.

Después en la aplicación, suponiendo que inyectamos el EntityManager a nuestros DAOs mediante la anotación @PersistenceContext, a cada uno de ellos habría que incluirle el atributo unitName especificado en el persistence.xml:

@PersistenceContext(unitName="defaultDB")

En mi caso esto no me ha valido porque en el momento de tener que conectar otra base de datos a mi aplicación, ésta importaba una serie de librerías comunes que utilizamos para más proyectos en los que había objetos DAO con un @PersistenceContext sin especificar ningún unitName. Cambiar la librería común para que especifira el unitName es inviable ya que en cada aplicación en la que se utiliza su valor es diferente.

Al arrancar la aplicación sin hacer nada la excepción que aparece es:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 2
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:536)

Esta claro. El DAO que tiene el @PersistenceContext no sabe qué entityManager tiene que recibir y no podemos modificarlo para especificárselo.

En la excepción se puede ver que el error ocurre en el método findDefaultEntityManagerFactory de la clase PersistenceAnnotationBeanPostProcessor de Spring. Es el encargado de inyectar el entityManager a los @PersistenceContext.

Me he creado una clase que hereda de PersistenceAnnotationBeanPostProcessor y sobreescribe el método findDefaultEntityManagerFactory de tal manera que voy a devolver el entityManagerFactory que yo quiera. La clase en cuestión quedaría así:



public class PersistenceAnnotationBeanPostProcessor extends org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor{

private EntityManagerFactory defaultEntityManagerFactory;

protected EntityManagerFactory findDefaultEntityManagerFactory(String requestingBeanName)
throws NoSuchBeanDefinitionException{
if (defaultEntityManagerFactory!=null){
return defaultEntityManagerFactory;
}
return super.findDefaultEntityManagerFactory(requestingBeanName);
}

public void setDefaultEntityManagerFactory(
EntityManagerFactory defaultEntityManagerFactory) {
this.defaultEntityManagerFactory = defaultEntityManagerFactory;
}

}



Lo único que tendríamos hacer a continuación es definir un bean en la configuración de Spring con esta clase en vez de la que utiliza Spring por defecto:









Eso sí, tenemos que especificar en el atributo "defaultEntityManagerFactory" qué entityManager queremos que se inyecte cuando nos encontramos con un @PersistenceContext sin especificar un unitName.

La conexión a la segunda base de datos yo sólo la he utilizado para realizar consultas sobre ella, nunca para realizar modificaciones. No sé si habría algún fallo en las transacciones, ya que el JpaTransactionManager está ligado únicamente a un EntityManagerFactory.

lunes, 28 de marzo de 2011

Define custom mapping for namespace to package

En esta entrada voy a comentar una cosa bastante simple.

Cuando queremos crear un cliente de servicio web desde eclipse llegamos a un punto en el que podemos configurar un mapeo de paquetes, para que no se creen los stubs automáticamente en el paquete que le corresponda segun el namespace que venga indicado en el wsdl, sino en el sitio en el que nosotros queramos.

Esto se consigue marcando la opción "Define custom mapping for namespace to package".



Para no tener que repetir la tarea de indicar el namespace y el paquete al que queremos que se mapee, podemos crearnos un fichero de propiedades y darle al botón "Import".



Tenemos que tener en cuenta que en este fichero no podemos poner la ruta del namespace tal cual, ya que los dos puntos no lo reconoce. Lo tenemos que poner en utf8. Ejemplo:

http\u003A//namespace.marcos.com=com.marcos.ws.stubs
http\u003A//namespace.sub.dominiomarcos.com=com.marcos.ws.stubs

miércoles, 19 de enero de 2011

Propagar atributos en tiles

Una vez que estamos configurando nuestra plantilla de tiles mediante xml se nos puede presentar la necesidad de utilizar atributos dentro de una jsp que se está visualizando mediante un <tiles:insertAttribute />. Voy a poner un ejemplo para que se entienda mejor el caso:

En nuestro fichero tiles-def.xml tenemos el siguiente definition:












El main.jsp tiene el siguiente contenido:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
....





Hasta aqui nada raro. Pero, ¿Qué pasa si dentro del header queremos utilizar el atributo menu? header.jsp:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>

<img src="<%=request.getContextPath()%>/images/logo.png" />



Si ejecutamos esto nos dara una excepción NoSuchAttributeException, quejándose de que no existe el atributo 'menu'. ¿Qué soluciones tenemos?

La primera opción que se me ocurre y seguramente sea la más elegante es reordenar un poco la definición que tenemos dejándola de la siguiente manera:














Y el main.jsp quedaría:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
....





Si esta solución no os convence y no podeis separar la definición existente en dos (como me ha ocurrido recientemente) se puede hacer lo siguiente:

Primero, exponer todos los atributos de la definición como atributos de la request dentro del main.jsp:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
....








Después modificamos el header para que, en vez de utilizar el <tiles:insertAttribute/>, se use el <jsp:include/>:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>

<img src="<%=request.getContextPath()%>/images/logo.png" />



No me gusta nada esto de tener que utilizar el requestScope y jsp:includes directamente y en mi opinión tiene que haber otra forma de hacerlo más
elegante, pero estuve investigando un poco y no encontré nada. Como esto funciona no le dí muchas más vueltas jeje.

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.