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.