En esta p�gina finalmente dejaremos atr�s la aburrida aplicaci�n basada en la l�nea de comandos e integraremos Tomcat, WebWork e Hibernate para crear una peque�a aplicaci�n web.
El primer paso es instalar Tomcat. Este ejemplo utiliza Tomcat 4.1.29.
�Reestructurar el Directorio de Trabajo
Primero, reestructuraremos nuestro directorio de trabajo, para mantener las partes de nuestra aplicaci�n claramente separadas:
. +src +de +gloegl +road2hibernate +data User.java Event.java User.hbm.xml Event.hbm.xml +actions hibernate.cfg.xml log4j.properties +config +static-web +views +lib cglib2.jar commons-logging.jar hibernate2.jar jta.jar odmg.jar commons-collections.jar dom4j.jar jdbc2_0-stdext.jar log4j.jar
Hemos movido nuestras clases y mapeos al subdirectorio data de road2hibernate. Por supuesto que tambi�n hemos tenido que modificar las declaracioens package de los ficheros Java y los nombres de clases en los ficheros de mapeo.
Adem�s, hemos creado nuevos directorios:
- src/de/gloegl/road2hibernate/actions
Contendr� el c�digo fuente de las acciones WebWork. - config
Contendr� los ficheros de configuraci�n que despu�s se situar�n en el WEB-INF/config de nuestra aplicaci�n web. - static-web
Contendr� todo el contenido est�tico de nuestra aplicacion como ficheros HTML y de imagen. - views
Contendr� todos nuestros ficheros de vistas que contienen el html que despu�s se mostrar� al usuario.
Como utilizaremos WebWork para nuestra aplicaci�n y Velocity como motor de plantillas de vistas, necesitar�s descargar WebWork y Velocity. S�lo necesitaremos webwork.jar y velocity-dep-1.3.1.jar, para situarlos en el directorio lib bajo el directorio de trabajo.
�Configurar WebWork
Ahora configuraremos nuestra aplicaci�n para que utilice WebWork. Primero necesitamos un fichero web.xml que situaremos en el directorio config:
�config/web.xml:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-appara> <display-name>Eventmanager</display-name> <servlet> <servlet-name>action</servlet-name> <servlet-class>webwork.dispatcher.ServletDispatcher</servlet-class> <load-on-startupara>1</load-on-startupara> </servlet> <servlet> <servlet-name>velocity</servlet-name> <servlet-class>webwork.view.velocity.WebWorkVelocityServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>velocity</servlet-name> <url-pattern>*.vm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-appara>
Este fichero contiene todos los servlets y mapeos de servlets que WebWork necesita para ejecutarse.
Adem�s, necesitaremos un fichero webwork.properties, que situaremos directamente en el directorio src:
�src/webwork.properties:
webwork.action.packages=de.gloegl.road2hibernate.actions, webwork.action.standard
Este fichero contiene s�lo la declaraci�n de los paquetes donde WebWork buscar� sus acciones.
�Actualizar el Proceso de Construcci�n
Ahora haremos que nuestro proceso de construcci�n situe juntos todos los ficheros y genere la estructura adecuada para una aplicaci�n web:
�en build.xml:
<project name="hibernate-tutorial" default="compile"> <property name="sourcedir" value="${basedir}/src"/> <property name="targetdir" value="${basedir}/bin"/> <property name="librarydir" value="${basedir}/lib"/> <property name="configdir" value="${basedir}/config"/> <property name="wardir" value="${basedir}/war"/> <property name="viewsdir" value="${basedir}/views"/> <property name="static-htmldir" value="${basedir}/static-web"/> <target name="setup-war-structure" depends="compile"> <mkdir dir="${wardir}"/> <mkdir dir="${wardir}/WEB-INF"/> <mkdir dir="${wardir}/WEB-INF/classes"/> <mkdir dir="${wardir}/WEB-INF/lib"/> <mkdir dir="${wardir}/WEB-INF/views"/> <copy todir="${wardir}/WEB-INF/classes"> <fileset dir="${targetdir}"/> </copy> <copy todir="${wardir}/WEB-INF/lib"> <fileset dir="${librarydir}"/> </copy> <copy todir="${wardir}/WEB-INF/views"> <fileset dir="${viewsdir}"/> </copy> <copy todir="${wardir}/WEB-INF"> <fileset dir="${configdir}"/> </copy> <copy todir="${wardir}/"> <fileset dir="${static-htmldir}"/> </copy> </target> ...
Ejecutando este objetivo se crear� un directorio llamado war, que contendr� la aplicaci�n web igual que luego se estructurar� en el fichero war. Despu�s de ejecutar este objetivo, el directorio war se parecer� a esto:
+war +WEB-INF web.xml +classes +de +gloegl +road2hibernate +data User.class Event.class User.hbm.xml Event.hbm.xml +actions hibernate.cfg.xml log4j.properties webwork.properties +lib <all library files here>
Ahora a�adiremos las tareas espec�ficas de Tomcat a nuestro fichero de construcci�n, para as� poder instalar nuestra aplicaci�n en Tomcat utilizando ant. Necesitar�s ajustar el ejemplo a tu entorno:
�en build.xml:
<project name="hibernate-tutorial" default="compile"> .... <!-- ADJUST THIS ! --> <property name="manager.url" value="http://localhost:8080/manager"/> <property name="tomcatdir" value="C:/Program Files/Tomcat"/> <property name="app.path" value="/eventmanager"/> <property name="manager.username" value="username"/> <property name="manager.password" value="password"/> <path id="tasks.classpath"> <fileset dir="${tomcatdir}/server/lib"> <include name="catalina-ant.jar"/> </fileset> </path> <taskdef name="install" classname="org.apache.catalina.ant.InstallTask" classpathref="tasks.classpath"/> <taskdef name="reload" classname="org.apache.catalina.ant.ReloadTask" classpathref="tasks.classpath"/> <taskdef name="remove" classname="org.apache.catalina.ant.RemoveTask" classpathref="tasks.classpath"/> <target name="install" depends="setup-war-structure"> <install url="${manager.url}" username="${manager.username}" password="${manager.password}" path="${app.path}" war="file://${wardir}"/> </target> <target name="reload" depends="setup-war-structure"> <reload url="${manager.url}" username="${manager.username}" password="${manager.password}" path="${app.path}"/> </target> <target name="remove"> <remove url="${manager.url}" username="${manager.username}" password="${manager.password}" path="${app.path}"/> </target> ... </project>
Ahora arrancamos Tomcat y ejecutamos ant install desde la l�nea de comandos -- esto instalar� la aplicaci�n en Tomcat. Deber�as poder acceder a la aplicaci�n eventmanager apuntando tu navegador a la direcci�n http://localhost:8080/eventmanager/ - sin embargo s�lo obtendr�s un listado de directorio generado por Tomcat. Todav�a no hemos creado ning�n contenido para que lo muestre la aplicaci�n.
�Una Primera Acci�n WebWork
Nuestra primera acci�n WebWork ser� muy simple - no hara nada excepto reenviar a una vista:
�/src/de/gloegl/road2hibernate/actions/EventList.java:
package de.gloegl.road2hibernate.actions; import webwork.action.ActionSupport; public class EventList extends ActionSupport { }
La clase extiende ActionSupport pero no sobreescribe ning�n m�todo. Por eso tiene el comportamiento por defecto de ActionSupport: la acci�n ser� reenviada a la vista definida como success. Esto es lo que tenemos que hacer en un fichero llamado views.properties, situado en el directorio src:
�/src/views.properties:
eventlist.action=EventList eventlist.success=/WEB-INF/views/eventlist.vm
Esto define una acci�n llamada eventlist que ser� manejada por nuestra clase EventList. Adem�s, se ha definido la vista success. Ahora tenemos que escribir el fichero eventlist.vm, que situaremos el directorio views - ant lo copiar� a la localizaci�n correcta.
�views/eventlist.vm:
<html> <head> <title>Event List</title> </head> <body> <h1>Successful</h1> </body> </html>
Como puedes ver, es s�lo un fichero HTML ... que modificaremos para incluir contenido din�mico m�s adeltante. Ahora ejecuta ant reload. Deber�as poder probar la acci�n apuntando tu nevegador a http://localhost:8080/eventmanager/eventlist.action - deber�as ver la cabecera Successful en tu navegador.
�El Patr�n "Open Session in View"
Cuando utilizamos caracter�sticas como la carga lenta, Hibernate necesita una conexi�n abierta. Para hacer esto f�cilmente en una aplicaci�n web usaremos un patr�n sencillo. Crearemos un servlet filter que manejar� nuestra sesi�n y la cerrar� al final de la petici�n web.
�src/de/gloegl/road2hibernate/util/SessionManager.java:
package de.gloegl.road2hibernate.util; import java.io.*; import javax.servlet.*; import net.sf.hibernate.SessionFactory; import net.sf.hibernate.Session; import net.sf.hibernate.Transaction; import net.sf.hibernate.HibernateException; import net.sf.hibernate.cfg.Configuration; public class SessionManager implements Filter { protected static ThreadLocal hibernateHolder = new ThreadLocal(); protected static ThreadLocal txHolder = new ThreadLocal(); protected static SessionFactory factory; public void init(FilterConfig filterConfig) throws ServletException { // Initialize hibernate try { factory = new Configuration().configure().buildSessionFactory(); } catch (HibernateException ex) { throw new ServletException(ex); } } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (hibernateHolder.get() != null) throw new IllegalStateException( "A session is already associated with this thread! " + "Someone must have called getSession() outside of the context " + "of a servlet request."); try { chain.doFilter(request, response); } finally { Session sess = (Session)hibernateHolder.get(); Transaction tx = (Transaction)txHolder.get(); if (sess != null) { hibernateHolder.set(null); try { if (tx != null) { tx.commit(); } sess.close(); } catch (HibernateException ex) { throw new ServletException(ex); } } } } public static Session getSession() throws HibernateException { Session sess = (Session)hibernateHolder.get(); Transaction tx = (Transaction)txHolder.get(); if (sess == null) { sess = factory.openSession(); txHolder.set(sess.beginTransaction()); hibernateHolder.set(sess); } else { if (tx == null) { throw new IllegalStateException("Transaction was allready rolled back"); } } return sess; } public static void rollback() throws HibernateException { Transaction tx = (Transaction)txHolder.get(); if (tx == null) { throw new IllegalStateException("Transaction was allready rolled back"); } tx.rollback(); txHolder.set(null); } public void destroy() { try { factory.close(); } catch (HibernateException ex) { throw new RuntimeException(ex); } } }
Veamos que se ha hecho aqu�: la clase SessionManager almacenar� una sesi�n Hibernate en una variable ThreadLocal. Un ThreadLocal contendr� un ejemplar de una variable por cada thread. Por eso si hay dos Threads paralelos ejecut�ndose, el ThreadLocal contendr� dos sesiones. Se llamar� al m�todo doFilter en cada petici�n. Permitir� que la petici�n contin�e llamando a chain.doFilter(). Despu�s de que se haya terminado el procesamiento de la petici�n chain.doFilter() retorna, recupera la Session del thread actual de ThreadLocal y lo cierra. Chequea si la transaci�n todav�a existe y la entrega si es as�.
En la funci�n init(), se configurar� la SessionFactory cuando el SessionManager se necesite por primera vez.
Nuestras acciones pueden invocar el m�todo est�tico getSession() del SessionManager para obtener una Session - si se invoca varias veces durante un thread, se utilizar� siempre la misma sesi�n.
Nuestras acciones pueden invocar al m�todo rollback() para deshacer una transaci�n.
tenemos que modificar el classpath en nuestro objetivo compile para hacer que esto fucione:
�en build.xml:
<target name="compile" depends="copy-resources"> <javac srcdir="${sourcedir}" destdir="${targetdir}" debug="on" > <classpath> <fileset dir="${librarydir}"> <include name="*.jar"/> </fileset> <fileset dir="${tomcatdir}/common/lib"> <include name="*.jar"/> </fileset> </classpath> </javac> </target>
Finalmente, tenemos que configurar el filtro en el fichero web.xml:
�config/web.xml:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-appara> <display-name>Eventmanager</display-name> <filter> <filter-name>sessionmanager</filter-name> <filter-class>de.gloegl.road2hibernate.util.SessionManager</filter-class> </filter> <filter-mapping> <filter-name>sessionmanager</filter-name> <servlet-name>action</servlet-name> </filter-mapping> <servlet> <servlet-name>action</servlet-name> <servlet-class>webwork.dispatcher.ServletDispatcher</servlet-class> <load-on-startupara>1</load-on-startupara> </servlet> <servlet> <servlet-name>velocity</servlet-name> <servlet-class>webwork.view.velocity.WebWorkVelocityServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>velocity</servlet-name> <url-pattern>*.vm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-appara>
�Acceder a Hibernate desde la Acci�n
Ya hemos configurado nuestro marco de trabajo, finalmente podemos empezar a utilizar Hibernate para cargar los datos en nuestra acci�n:
�src/de/gloegl/road2hibernate/actions/EventList.java:
package de.gloegl.road2hibernate.actions; import java.util.List; import webwork.action.ActionSupport; import net.sf.hibernate.Session; import net.sf.hibernate.HibernateException; import de.gloegl.road2hibernate.util.SessionManager; public class EventList extends ActionSupport { private List events; public List getEvents() { return events; } public String doExecute() { try { Session s = SessionManager.getSession(); events = s.find("from Event"); return SUCCESS; } catch (HibernateException e) { e.printStackTrace(); return ERROR; } } }
Simplemente recuperamos la sesi�n desde nuestro SessionManager, y cargamos la lista de Events utilizando session.find(). Luego lo asignamos a un atributo de nuestra acci�n. Ahora podemos utilizar este atributo en nuestra vista:
�views/eventlist.vm:
<html> <head> <title>Event List</title> </head> <body> <h1>Events</h1> <ul> #foreach( $event in $events ) <li>$event.title</li> #end </ul> </body> </html>
Utilizando el lenguaje de plantillas de velocity, simplemente iteramos sobre los eventos, e imprimimos sus t�tulos. Ahora que nuestra infraestructura est� situada, puedes ver qu� f�cil es crear acciones y vistas. Si ahora llamas a http://localhost:8080/eventmanager/eventlist.action, no ver�s nada - porque no tenemos Events en la base de datos. Por eso crearemos otra acci�n para crear eventos:
Pero primero crearemos un fichero index.html, donde enlazar nuestras acciones:
�static-web/index.html:
<html> <head> <title>Hibernate Event Manager</title> </head> <body> <h2>Hibernate Event Manager</h2> <ul> <li> <a href="eventlist.action">Event Listing</a> </li> <li> <a href="newevent.enter.action">New Event</a> </li> </ul> </body> </html>
Luego, crearemos la acci�n NewEvent:
�src/de/gloegl/road2hibernate/actions/NewEvent.java:
package de.gloegl.road2hibernate.actions; import java.util.List; import webwork.action.ActionSupport; import webwork.action.CommandDriven; import net.sf.hibernate.Session; import net.sf.hibernate.HibernateException; import de.gloegl.road2hibernate.util.SessionManager; import de.gloegl.road2hibernate.data.Event; public class NewEvent extends ActionSupport implements CommandDriven { private String title; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String doExecute() { try { Session s = SessionManager.getSession(); Event event = new Event(); event.setTitle(title); s.save(event); s.flush(); return SUCCESS; } catch (HibernateException e) { e.printStackTrace(); return ERROR; } } public String doEnter() { return INPUT; } }
Como puedes ver, esta acci�n tiene dos m�todos doXXX(). La raz�n es porque esta vez usaremos el soporte de acciones dirigias por comandos que tiene Webwork -- esto es por lo que esta acci�n implementa el interface CommandDriven. Echemos un vistazo al fichero views.properties que nos aclarar� esto:
�src/views.properties:
eventlist.action=EventList eventlist.success=/WEB-INF/views/eventlist.vm newevent.action=NewEvent newevent.input=/WEB-INF/views/newEventForm.vm newevent.success=index.html newevent.enter.action=NewEvent!enter
Tenemos dos acciones: newevent usar� la clase NewEvent con el comando por defecto que llamar� a doExecute(). newevent.enter usara la acci�n NewEvent con el comando enter, que llamar� al m�todo doEnter(). B�sicamente newevent.enter nos traer� la vista newEventForm, y newevent nos llevar� a index.html despu�s de grabar el nuevo objeto Event.
Finalmente necesitamos codificar el fichero vista que hemos especificado.
�views/newEventForm.vm:
<html> <head> <title>Event List</title> </head> <body> <h1>New Event</h1> <form method="POST" action="newevent.action"> <table> <tr> <td>Title:</td> <td><input type="text" name="title" value="$!title"/></td> </tr> <tr> <td colspan="2"><input type="submit"/></td> </tr> </table> </form> </body> </html>
Si ahora compilas y recargas la aplicaci�n y vas a la direcci�n http://localhost:8080/eventmanager, te deber�a aparecer la p�gina index.html desde donde podr�s crear nuevos eventos y listarlos.