cocoon-docs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Apache Wiki <wikidi...@apache.org>
Subject [Cocoon Wiki] Update of "CocoonAndHibernateTutorial" by JohannesTextor
Date Mon, 28 May 2007 20:21:40 GMT
Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Cocoon Wiki" for change notification.

The following page has been changed by JohannesTextor:
http://wiki.apache.org/cocoon/CocoonAndHibernateTutorial

------------------------------------------------------------------------------
  ==  Tutorial on using Hibernate with Cocoon ==
  - TARGET-AUDIENCE: beginner '''*advanced*''' expert[[BR]]
- - COCOON-RELEASES: 2.1.5 2.1.6 2.1.7[[BR]]
+ - COCOON-RELEASES: 2.1.5 2.1.6 2.1.7 2.1.10[[BR]]
  - DOCUMENT-STATUS: '''*draft*''' reviewed released[[BR]]
  ----
  
  === Recent changes ===
  
- 2005/08/10:
+ 2007/05/28:
  
- Finally added a minimal example to show how Cocoon and Hibernate interact.
+ Completely reworked most parts of the implementation, following important insights from a discussion on the users list. Sessions now have no longer to be opened in flowscript any more, in fact, the dependency between flowscript and hibernate is effectively remove. The new OpenSessionInView Filter now works analogously as described on http://www.hibernate.org/43.html. 
  
+ Everything is also migrated to Cocoon 2.1.10 and Hibernate 3. For the Impatient, I created a JAR File with all required classes and Hibernate configuration files and a ZIP file with a minimal CRUD Demo application (not much more than a Rails scaffold). 
- 2005/07/25:
- 
- If you have visited this page before, you should check out the new Servlet Filter. It now
- finally uses the request object instead of the servlet session to communicate with cocoon,
- with is MUCH cleaner and most importantly does not require the creation of a Session ...
- Sorry for taking so long to clean this up !
- 
- I am now using all of the code in a project with Hibernate 3. Probably the code snippets will
- soon be updated to use the new Hibernate version by default.
  
  === What you will get from this page ===
  
- This page proposes a very basic way of using Hibernate from your Cocoon application. This is '''not''' an
+ This page proposes a way of using Hibernate from your Cocoon application. This is '''not''' an
  introduction to Hibernate or Cocoon itself. It just covers the specific issues of integration
  between the two.
  
+ Database connections are drawn from Cocoon's connection pool. I strongly recommend using the DAO (Data Access Object) pattern as an additional layer of abstraction, however if you wish to do so, you can also access Hibernate directly from flowscript or Java Flow. 
+ 
+ === Alternative approach ===
+ 
+ You should also consider the frequently used Cocoon / Hibernate / Spring approach, which adds additional abstraction and supports the IOC patterns. A starting point is the "Spring Petstore" at http://new.cocoondev.org/main/117-cd/43-cd.html . 
- The approach presented here is very simple in that it tries to use facilities already provided by
- Cocoon to perform database connection pooling. However, without using another framework you will have
- direct interaction between Hibernate and Cocoon, which means calling Hibernate from flowscript. Many
- people don't want to do this and prefer to use the Spring framework for additional abstraction. If
- you are not sure if this approach is right for you, make sure you read the "Conceptual Overview"
- section first.
  
  === Topics covered ===
  
  The two main problems you have when integrating Hibernate into Cocoon are the following:
  
- * Hibernate's builtin connection pool is for testing purposes only, so sooner or later you will have to use another one.
+ * Hibernate's builtin connection pool is for testing purposes only, so sooner or later you will have to use another one. (At least this was true for earlier versions of Hibernate; am not sure if this is still an issue). 
  
  * When persistent objects are navigated by the view, closing Hibernate sessions from flowscript (=the control)
   may lead to exceptions due to race conditions.
@@ -50, +41 @@

  
  * Configuring Hibernate to use cocoon.xconf datasources
  
- * Setting up a Servlet Filter for managing the disposal of Hibernate sessions
+ * Setting up a Servlet Filter for Hibernate session management (a.k.a. "Open Session in View" Filter) 
  
  === Your basic skills ===
  
@@ -63, +54 @@

  and JX Templates. The samples included will also use CForms. Did I mention
  Flowscript ? That one is especially important.
  
+ If you want to use Hibernate you should also be fluent in Java. Be warned that you are going beyond the "no programming required" - statement sometimes associated with Cocoon. If you want to understand what you are doing, you should have a basic notion of what Avalon is. We are also going to write a Servlet Filter which takes care of Hibernate Session, so if you don't know what a Servlet Filter is get your favourite book or search engine and read up :)
- If you want to use Hibernate you should also be fluent in Java. Be warned that
- you are going beyond the "no programming required" - statement of the Cocoon
- definition. If you want to understand what you are doing, you should have a
- basic notion of what Avalon is. We are also going to write a Servlet Filter which
- takes care of Hibernate Session, so if you don't know what a Servlet Filter is
- get your favourite book or search engine and read up :)
  
  You will have a basic notion of what Hibernate is and what it does, otherwise
  you would not have come to this page :) But make sure you have understood what
- lazy collection initialization is and what it does.
+ lazy collection initialization is and what it does. If you can get a copy of the "Hibernate in Action" book, go ahead. It is a little old now, but still incredibly useful. 
  
  === Technical prerequisites ===
  
+ A running cocoon installation. This was written for and tested on Cocoon 2.1.10, but should also work with earlier versions down to 2.1.6 at least (which the first version of this tutorial was based on). 
- A running cocoon installation. I tried this on Cocoon 2.1.6 and Cocoon 2.1.7.
- This is written for Hibernate v 2.1.X, but it also works with Hibernate 3.0.
- When using Hibernate 3, you'll have to adjust the Hibernate package name,
- which has changed from {{{net.sf.hibernate}}} to {{{org.hibernate}}}.
- 
  
  === Conceptual overview ===
  
+ Again, before you begin, consider using the Cocoon / Hibernate / Spring approach. Unlike in earlier versions, the approach presented here is now equally "clean" as this solution, but you might want to use Spring anyway, and the upcoming versions of Cocoon will be based on Spring instead of Avalon. 
- The approach presented here is built to work with Cocoon and Hibernate alone,
- without any further frameworks. This creates a dependency between Cocoon and Hibernate since
- you will need to manage Hibernate sessions from flow script. Creating such a dependency
- is considered bad design by many people. For example, you might build a webshop which
- stores articles in a database. Using Hibernate, you will be able to replace the database
- without too much hassle (switching from MySQL to Oracle, for example), but it will still
- be a database. When you want to manage your articles via a remote web service (or a flat
- text file or whatever means of data storage), you'll have to rewrite your flowscript.
  
+ The philosophy of this approach is to hide Hibernate from the flowscript layer as much as possible. This means that a layer of DAOs (Data Access Objects) will be used from flowscript instead of directly interacting with Hibernate. DAO is a well-established pattern, you can read more about it at http://www.hibernate.org/328.html . On that page, you can also see how elegantly DAO can be implemented with Java 5. However, here we use Java 1.4 compatible code. 
- You can avoid this dependency by using an additional framework like Spring or Avalon which
- supports the "Inversion of Control" and "Data Access Object" design patterns. This will yield
- a cleaner and more modular designed web application.
  
+ We will use Cocoon's builtin connection pool to supply JDBC connections to Hibernate. A few "tricks" are required to tie DAOs, Cocoon and the Servlet Filter together, but this work has already been done for you. 
- So before you begin, think if the approach presented here is right for you. I use it for smaller
- projects, when even a change in the underlying database is very unlikely, and I know the
- customer will never switch to something else than a database for data storage. Moreover, adding
- yet another framework - like Spring - to the mix, the learning curve for new developers gets
- even steeper, and the resulting web application even larger, which is too costly for some
- projects.
- 
- However, if you have a larger application or want a clean implementation of the IOC and DAO
- design patterns, you should definately have a look at the Spring framework. The starting
- point is the SpringPetstore at cocoondev.org.
  
  == Setting up ==
  
@@ -114, +78 @@

  There are several wiki and documentation pages on how to do this. For instance, if you use
  MySQL have a look at ["MySQL"].
  
- For now, I will assume that your SQL datasource is named "test".
+ For now, I will assume that your SQL datasource is named "hibernate-datasource".
  
+ Now download hibernate from [http://www.hibernate.org]. As I'm writing this, 3.3 is the current production release. The following jars from the Hibernate distribution need to be copied to your WEB-INF/lib directory:
- Now download hibernate from [http://www.hibernate.org]. I tried this with the current production
- release, 2.1.6. The following jars from the Hibernate distribution need to be copied to your
- WEB-INF/lib directory:
  
- * hibernate2.jar
+ * hibernate3.jar
  
  From the "lib" directory of the Hibernate distribution:
  
  * dom4j-1.4.jar
  * cglib-full-2.0.2.jar
  * jta.jar
+ * asm.jar
+ * asm-attrs.jar
  
+ Note: "asm.jar" and "asm-attrs.jar" may already be present in the WEB-INF/lib directory. At least in Cocoon 2.1.7, these versions were not compatible with Hibernate 3. The only option was to replace them with those shipped with Hibernate. I can not guarantee that Cocoon does not rely on the particular version it ships with, though. 
- If you build cocoon exclude ojb block,this lib requried too:
- * odmg-3.0.jar
  
+ Someone reported that if you build cocoon exclude ojb block, this lib is also required: odmg-3.0.jar. However, I could not confirm that with Cocoon 2.1.10.
- '''Hibernate 3 note''': Hibernate 3 also depends on "asm.jar" and "asm-attrs.jar".
- In Cocoon 2.1.7, these JARs are already provided in the WEB-INF/lib directory.
- Unfortunately, the versions provided by Cocoon are not compatible with
- Hibernate 3. You'll have to delete those JARs and replace them with those shipped
- with Hibernate, although i'm not sure if some parts of Cocoon rely on those
- particular versions (it works fine for me so far).
  
  == Registering Hibernate with Avalon ==
  
+ Now we will register Hibernate with Cocoon. If you are like me, you will fire up Eclipse to accomplish the following part. If you are lazy, simply copy the JAR "hibernate-interface.jar" which is attached to this page to your "WEB-INF/lib" folder. Then you may skip the following sections and read on at "Registering the HibernateFactory". 
- Now we will register Hibernate with Cocoon. If you are like me, you will
- fire up Eclipse to accomplish the following part.
  
  The first thing we'll do is create an interface {{{PersistenceFactory}}} that
+ extends the Component interface from Avalon: (Note: This approach originates from the page CformsHibernateAndFlow!)
- extends the Component interface from Avalon: (Note: {{{PersistenceFactory}}}
- and {{{HibernateFactory}}} originate from the Page CformsHibernateAndFlow!)
  
  {{{
- package org.test; // Put the name of the target package here. Using the default package is not recommended!
+ package org.apache.cocoon.hibernate; // or some other package as you please 
  
  import org.apache.avalon.framework.component.Component;
  
  public interface PersistenceFactory extends Component {
- 	String ROLE = PersistenceFactory.class.getName();
+         String ROLE = PersistenceFactory.class.getName();
  
- 	public net.sf.hibernate.Session createSession();
+         public org.hibernate.Session createSession();
  }
  }}}
  
+ As you can see the {{{PersistenceFactory}}} will be responsible for the creation of Hibernate Sessions. This is the point where we'll have to decide how Hibernate will connect to the database. My prefered solution is using the cocoon connection pool. Many documents out there will tell you to create a file called hibernate.properties and put your database access information in there. This will work, but Hibernate will then use its own builtin connection pool, and the documentation states clearly that ''''the Hibernate builtin connection pool is not for production use''''.
- As you can see the {{{PersistenceFactory}}} will be responsible for the creation
- of Hibernate Sessions. This is the point where we'll have to decide how Hibernate will
- connect to the database. My preffered solution is using the cocoon connection pool.
  
- However, most tutorials will tell you to create a file called hibernate.properties and
- put your database access information in there. This will work, but Hibernate will use its
- own builtin connection pool, and the documentation states clearly that ''''the Hibernate
- builtin connection pool is not for production use''''.
- 
- For example, when using MySQL, you will experience problems when leaving the webapp running
+ For example, when using MySQL, you might experience problems when leaving the webapp running
  overnight and coming back in the morning. MySQL connections die after 8 hours of idletime, and the
  Hibernate builtin pool is not capable of dealing with this. So the first thing you will see
  is an error message. It goes away after reloading the page, but still it's unacceptable for
@@ -183, +132 @@

  connection pool, you will need to change that part according to your configuration.
  
  {{{
- package org.test;
+ package org.apache.cocoon.hibernate;
  
+ import org.apache.avalon.excalibur.datasource.DataSourceComponent;
- import org.apache.avalon.framework.logger.AbstractLogEnabled;
- import org.apache.avalon.framework.thread.ThreadSafe;
- import org.apache.avalon.framework.configuration.Configuration;
- import org.apache.avalon.framework.configuration.Configurable;
- import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.framework.activity.Disposable;
  import org.apache.avalon.framework.activity.Initializable;
+ import org.apache.avalon.framework.configuration.Configurable;
+ import org.apache.avalon.framework.configuration.Configuration;
+ import org.apache.avalon.framework.configuration.ConfigurationException;
+ import org.apache.avalon.framework.context.Context;
+ import org.apache.avalon.framework.context.ContextException;
+ import org.apache.avalon.framework.context.Contextualizable;
+ import org.apache.avalon.framework.logger.AbstractLogEnabled;
+ import org.apache.avalon.framework.service.ServiceException;
  import org.apache.avalon.framework.service.ServiceManager;
+ import org.apache.avalon.framework.service.ServiceSelector;
  import org.apache.avalon.framework.service.Serviceable;
- import org.apache.avalon.framework.service.ServiceException;
- import org.apache.avalon.excalibur.datasource.DataSourceComponent;
- import org.apache.avalon.framework.service.ServiceSelector;
+ import org.apache.avalon.framework.thread.ThreadSafe;
+ import org.apache.cocoon.Constants;
+ import org.apache.cocoon.environment.http.HttpContext;
  
+ public class HibernateFactory extends AbstractLogEnabled implements
- // import all classes from your package so you can later persist them
- import org.test.*;
- 
- public class HibernateFactory
-         extends AbstractLogEnabled
-         implements PersistenceFactory, Configurable, Serviceable, Initializable,
+ 		PersistenceFactory, Configurable, Serviceable, Initializable,
+ 		Disposable, Contextualizable, ThreadSafe {
-             Disposable, ThreadSafe
- {
  
-     private boolean initialized = false;
+ 	private boolean initialized = false;
+ 
-     private boolean disposed = false;
+ 	private boolean disposed = false;
+ 
-     private ServiceManager manager = null;
+ 	private ServiceManager manager = null;
  
-     // do not confuse with Avalon Configuration, Cocoon Session, etc.
+ 	// do not confuse with Avalon Configuration, Cocoon Session, etc.
-     net.sf.hibernate.cfg.Configuration cfg;
+ 	org.hibernate.cfg.Configuration cfg;
+ 
-     net.sf.hibernate.SessionFactory sf;
+ 	org.hibernate.SessionFactory sf;
  
-     //for debugging: which instance am I using?
+ 	// for debugging: which instance am I using?
-     long time_start;
+ 	long time_start;
  
-     public HibernateFactory() {
+ 	public HibernateFactory() {
-         System.out.println("Hibernate factory instance created");
+ 		//System.out.println("Hibernate factory instance created");
-     }
+ 	}
  
-     public final void configure(Configuration conf) throws ConfigurationException {
+ 	public final void configure(Configuration conf)
+ 			throws ConfigurationException {
-         if (initialized || disposed) {
+ 		if (initialized || disposed) {
-             throw new IllegalStateException ("Illegal call");
+ 			throw new IllegalStateException("Illegal call");
-         }
-         System.out.println("Hibernate configure called");
-     }
+ 		}
+ 		getLogger().debug("Hibernate configure called");
+ 	}
  
-     public final void service(ServiceManager smanager) throws ServiceException {
+ 	public final void service(ServiceManager smanager) throws ServiceException {
-         if (initialized || disposed) {
+ 		if (initialized || disposed) {
-             throw new IllegalStateException ("Illegal call");
+ 			throw new IllegalStateException("Illegal call");
-         }
+ 		}
  
-         if (null == this.manager) {
+ 		if (null == this.manager) {
-             this.manager = smanager;
+ 			this.manager = smanager;
-         }
-         System.out.println("Hibernate service called");
-     }
+ 		}
+ 		getLogger().debug("Hibernate service called");
+ 	}
  
-     public final void initialize() throws Exception {
+ 	public final void initialize() throws Exception {
-         if (null == this.manager) {
+ 		if (null == this.manager) {
-             throw new IllegalStateException("Not Composed");
+ 			throw new IllegalStateException("Not Composed");
-         }
+ 		}
  
-         if (disposed) {
+ 		if (disposed) {
-             throw new IllegalStateException("Already disposed");
+ 			throw new IllegalStateException("Already disposed");
-         }
+ 		}
  
-         //  adapt:
+ 		// adapt:
-         //  build sessionfactory
+ 		// build sessionfactory
-         //  map all classes we need
+ 		// map all classes we need
-         //  keep in sync with configuration file
+ 		// keep in sync with configuration file
-         //
-         try {
+ 		//
+ 		try {
-               cfg = new net.sf.hibernate.cfg.Configuration();
+ 			cfg = new org.hibernate.cfg.Configuration();
  
-               /* ***** ADD PERSISTENT CLASSES, VARIANT 1 ***** */
+ 			/* ***** ADD PERSISTENT CLASSES, VARIANT 1 ***** */
-               // persistent classes can be added here using
+ 			// persistent classes can be added here using
- 
-               // cfg.addClass(org.test.myClass.class);
+ 			// cfg.addClass(org.test.myClass.class);
- 
-               // Make sure the corresponding .class and .hbm.xml files are located in
+ 			// Make sure the corresponding .class and .hbm.xml files are located
+ 			// in
-               // (the same directory of) your classpath (e.g. WEB-INF/classes)
+ 			// (the same directory of) your classpath (e.g. WEB-INF/classes)
-               sf = cfg.buildSessionFactory();
+ 			// sf = cfg.buildSessionFactory();
- 
-               /* ***** ADD PERSISTENT CLASSES, VARIANT 2 ***** */
+ 			/* ***** ADD PERSISTENT CLASSES, VARIANT 2 ***** */
- 	      // alternatively, you might be using a hibernate.cfg.xml file to load mappings,
+ 			// alternatively, you might be using a hibernate.cfg.xml file to
+ 			// load mappings,
-               // then use the following line instead:
+ 			// then use the following line instead:
- 
-               // sf = cfg.configure().buildSessionFactory();
+ 			sf = cfg.configure().buildSessionFactory();
  
-               // no additional cfg.addClass(...) statements needed, since you can define
+ 			// no additional cfg.addClass(...) statements needed, since you can
+ 			// define
-               // mappings in the XML config file
+ 			// mappings in the XML config file
-         }
-         catch ( Exception e) {
+ 		} catch (Exception e) {
-               getLogger().error("Hibernate:" + e.getMessage());
+ 			getLogger().error("Hibernate:" + e.getMessage());
-               return;
-         }
+ 			return;
+ 		}
-         this.initialized = true;
+ 		this.initialized = true;
-         System.out.println("Hibernate initialize called");
-     }
+ 		getLogger().debug("Hibernate initialize called");
+ 	}
  
-     public final void dispose()  {
+ 	public final void dispose() {
+ 		//
+ 		try {
+ 			sf.close();
-         //
-         try {
-               sf.close();
-         }
-         catch ( Exception e) {
+ 		} catch (Exception e) {
-               getLogger().error("Hibernate:" + e.getMessage());
+ 			getLogger().error("Hibernate:" + e.getMessage());
+ 		} finally {
+ 			sf = null;
+ 			cfg = null;
+ 		}
-         }
-         finally {
-               sf = null;
-               cfg = null;
-         }
-         this.disposed = true;
+ 		this.disposed = true;
-         this.manager = null;
+ 		this.manager = null;
-         System.out.println("Hibernate dispose called");
-     }
+ 		getLogger().debug("Hibernate dispose called");
+ 	}
  
- 
- 
-     public net.sf.hibernate.Session createSession() {
+ 	public org.hibernate.Session createSession() {
-       net.sf.hibernate.Session hs;
+ 		org.hibernate.Session hs;
-       DataSourceComponent datasource = null;
+ 		DataSourceComponent datasource = null;
  
- 	// When creating a session, use a connection from
+ 		// When creating a session, use a connection from
-         // cocoon's connection pool
+ 		// cocoon's connection pool
-       try {
+ 		try {
- 	// Select the DataSourceComponent named "test"
+ 			// Select the DataSourceComponent named "test"
-         // This is a normal SQL connection configured in cocoon.xconf
+ 			// This is a normal SQL connection configured in cocoon.xconf
-       	ServiceSelector dbselector =
-             (ServiceSelector) manager.lookup(DataSourceComponent.ROLE+"Selector");
+ 			ServiceSelector dbselector = (ServiceSelector) manager
+ 					.lookup(DataSourceComponent.ROLE + "Selector");
-         datasource = (DataSourceComponent) dbselector.select("test");
+ 			datasource = (DataSourceComponent) dbselector.select("hibernate-datasource");
-         	//name as defined in cocoon.xconf
+ 			// name as defined in cocoon.xconf
-       	hs = sf.openSession(datasource.getConnection());
+ 			hs = sf.openSession(datasource.getConnection());
-       }
-       catch ( Exception e) {
+ 		} catch (Exception e) {
-           getLogger().error("Hibernate: " + e.getMessage());
+ 			getLogger().error("Hibernate: " + e.getMessage());
-           hs = null;
-       }
-       return hs;
-     }
+ 			hs = null;
+ 		}
+ 		return hs;
+ 	}
+ 
+ 	public void contextualize(Context context) throws ContextException {
+ 		getLogger().debug("Contextualize called: "+context);
+ 		/* Register the HibernateFactory in the Servlet Context so it 
+ 		 * may be accessed by the Servlet Filter. 
+ 		 */
+ 	    HttpContext sContext = (HttpContext)context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
+ 	    sContext.setAttribute(PersistenceFactory.ROLE, this);
+ 	}
  }
  }}}
  
- The crucial part is createSession(), where we tell Hibernate to use a connection from the
- Cocoon pool. This connection is selected using the avalon framework API.
- 
- Compile these two files. I needed the following jars in my classpath: avalon-framework-api-4.1.5.jar,
+ Compile these two files. I needed the following jars in my classpath: avalon-framework-api-4.3.jar,
- excalibur-datasource-1.1.1.jar, and hibernate2.jar. All these should be in your WEB-INF/lib
+ excalibur-datasource-2.1.jar, and hibernate3.jar. All these should be in your WEB-INF/lib
- folder anyway. When you're done, copy the .class files to a directory "org/test" (which obviously
+ folder anyway. When you're done, copy the .class files to a directory "org/apache/cocoon/hibernate" (which obviously
- depends on the package name you chose) in the "WEB-INF/classes/" folder of your cocoon installation.
+ depends on the package name you chose) in the "WEB-INF/classes/" folder of your cocoon installation, or export them as a JAR and place them in WEB-INF/lib. 
+ 
+ Now, you need a hibernate.cfg.xml file in your "WEB-INF/classes" folder. Since we supply connections ourselves, this boils down to: 
+ 
+ {{{
+ <?xml version='1.0' encoding='utf-8'?>
+ <!DOCTYPE hibernate-configuration PUBLIC
+ "-//Hibernate/Hibernate Configuration DTD//EN"
+ "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
+ 
+ <hibernate-configuration>
+ <session-factory>
+       <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
+       <!-- Mapping files -->
+       <!-- <mapping resource="org/test/beans/Language.hbm.xml"/> -->
+ </session-factory>
+ </hibernate-configuration>
+ }}}
+ 
+ The "mapping" tag is meant as an example. We do not actually map anything yet, so it is commented out. We will come back to this point later. The most important line is where you tell Hibernate about the actual SQL dialect you are using. If you do not supply this information, Hibernate won't be able to behave accordingly.
+ For example, Hibernate will try to use subqueries which are not supported if you use MySQL.
+ 
+ === Registering the HibernateFactory === 
  
  To register your {{{HibernateFactory}}} with Cocoon, put the following line in cocoon.xconf:
  
  {{{
- 	<component class="org.test.HibernateFactory" role="org.test.PersistenceFactory"/>
+ 	<component class="org.apache.cocoon.hibernate.HibernateFactory" role="org.apache.cocoon.hibernate.PersistenceFactory"/>
  }}}
  
  (Not sure whether it is actually important where you put this. There is a bunch of other component ... class ...
- statements in cocoon.xconf, so I just put mine above the Xalan XSLT processor component. Need some feedback here!)
+ statements in cocoon.xconf, so I just put mine above the Xalan XSLT processor component. (Still, after 3 years) need some feedback here!)
  
+ If you want to see log messages from Hibernate (you probably want to at this point), create a file "log4j.properties" in "WEB-INF/classes" and put the following into it: 
- Now, you need a hibernate.properties file in your "WEB-INF/classes" folder. A very basic config file I
- use is the following:
  
  {{{
+ # Set root logger level to DEBUG and its only appender to A1.
+ log4j.rootLogger=info, A1
- hibernate.cglib.use_reflection_optimizer = false
- hibernate.statement_cache.size=0
- hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
- }}}
  
+ # A1 is set to be a ConsoleAppender.
+ log4j.appender.A1=org.apache.log4j.ConsoleAppender
- It basically tells hibernate to turn off the reflection optimizer (whatever that means? :) ) and the
- statement cache (although statement caching can boost performance, you should leave it turned off until you
- are really sure how to use it). The most important line is where you tell Hibernate about the actual SQL
- dialect you are using. If you do not supply this information, Hibernate won't be able to behave accordingly.
- For example, Hibernate will try to use subqueries which are not supported if you use MySQL.
  
- Now restart cocoon. If everything went right, you will see the following line in your servlet container log
- file (catalina.out if you are using Tomcat):
+ # A1 uses PatternLayout.
+ log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+ log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
  
- {{{ Hibernate initialize called }}}
+ log4j.logger.org.apache.commons.digester=INFO
+ }}}
  
+ Now restart cocoon. You should see a bunch of messages from Hibernate, meaning that the HibernateFactory is being initialized correctly. However, since we did not create an actual session yet, we can not yet be sure that the Database connection is working right. So, we now want to open and close some sessions, which will be the task of our "OpenSessionInView" filter. 
- This means that Hibernate is ready for action. At this point you may start to deploy your beans and store,
- get and edit them following the instructions on the Yet To Be Written page.
- 
- However, at some point you will notice that there is still no proper way of managing Hibernate Sessions.
- Of course you can create and dispose Hibernate Session from Flow Script, but very probably you will sooner
- or later experience the (in)famous Lazy Initialization Problem (tm). The solution for this problem is to
- create a servlet filter to manage Hibernate Sessions, so if you are'nt fed up on Java yet read on :)
  
  == A Servlet Filter for Disposing Hibernate Sessions ==
  
@@ -380, +344 @@

  
  If you're getting serious about Hibernate, sooner or later you will want to use lazy collection initialization
  (Read the corresponding section in GettingStartedWithCocoonAndHibernate for an introduction on this topic).
- Say you are accessing Hibernate sessions from flowscript, i.e. as follows:
+ Say you are accessing Hibernate sessions from flowscript (you should not, but this is just an example), i.e. as follows:
  
  {{{
    var factory = cocoon.getComponent(Packages.org.test.PersistenceFactory.ROLE);
@@ -411, +375 @@

  
  === Creating the filter ===
  
+ Again, if you don't want to compile the filter yourself, it is already in the JAR. You may then skip to "Installing the filter". 
- Our filter needs to communicate with cocoon. Unfortunately, this cannot be done via the Avalon framework. The most
- straightforward way to get around this limitation is using the request object.
  
+ Our filter must communicate with cocoon in two ways: First, it must somehow obtain the HibernateFactory in order to be able to open sessions; this is done via the "ServletContext" which exists precisely to share resources among servlets, and is also usable by filters, of course. The "contextualize" method in the HibernateFactory provides this link. The opened sessions must be passed to the DAOs which live totally outside of the Servlet world; this is done via an InheritableThreadLocal, wrapped in a convenience object "PersistenceUtil":
- The example filter below will search the current request for an attribute called  {{{DisposeHibernateSession}}}. If found,
- it will try to cast the attribute value to a Hibernate Session, and close this session and the related
- JDBC connection if successful.
  
- Note that the session is always flushed before closing. Like this, you can be sure that any changes made to persistent
- objects are reflected in the database after the session has been closed. You might want to alter this behaviour later
- on.
+ {{{
+ package org.apache.cocoon.hibernate;
+ 
+ import java.util.HashMap;
+ 
+ import org.hibernate.Session;
+ 
+ public class PersistenceUtil {
+ 
+ 	private static InheritableThreadLocal scope = new InheritableThreadLocal() {
+         protected Object initialValue() {
+             return null;
+           }		
+ 	};
+ 
+ 	public static void initializeScope(){
+ 		scope.set(new HashMap());
+ 	}
+ 
+ 	public static void destroyScope(){
+ 		scope.set(null);
+ 	}
+ 	
+ 	public static Object getScopeAttribute(String key) {
+ 		return ((HashMap)scope.get()).get(key);
+ 	}
+ 	
+ 	public static void setScopeAttribute(String key, Object value){
+ 		((HashMap)scope.get()).put(key,value);
+ 	}
+ 
+ 	public static Session getHibernateSession(){		
+ 		return (Session) getScopeAttribute("HibernateSession");
+ 	}
+ 	
+ 	public static void setHibernateSession(Session hs){
+ 		setScopeAttribute("HibernateSession",hs);		
+ 	}
+ 	
+ 	public static void flushTransaction(){
+ 		getHibernateSession().getTransaction().commit();
+ 		getHibernateSession().beginTransaction();
+ 	}
+ }
+ }}}
+ 
+ This is a generic method to communicate between Cocoon, the DAOs and the Filter. Apart from managing the session itself, this is also useful for internationalization: the current locale may be stored in via setScopeAttribute and then accessed by the DAOs. You can read more about i18n and Hibernate at http://www.theserverside.com/tt/blogs/showblog.tss?id=HibernateInternational .
+ 
+ Note that the filter will close the session after the view has been rendered completely, thus solving the LazyInitializationExpection problem. In addition to commiting the transaction, you may want to extend the filter to roll it back in case something goes wrong. 
  
  To compile the filter you need the following jars in your classpath:
  
-  * hibernate2.jar
+  * hibernate3.jar
   * servlet-2.3.jar (or higher; e.g. from the common/lib directory of a Tomcat distribution)
  
  {{{
- package org.test;
+ package org.apache.cocoon.hibernate;
  
- import java.io.*;
+ import java.io.IOException;
  import java.sql.SQLException;
  
- import javax.servlet.*;
+ import javax.servlet.Filter;
- import javax.servlet.http.*;
+ import javax.servlet.FilterChain;
+ import javax.servlet.FilterConfig;
+ import javax.servlet.ServletContext;
+ import javax.servlet.ServletException;
+ import javax.servlet.ServletRequest;
+ import javax.servlet.ServletResponse;
  
- import net.sf.hibernate.Session;
- import net.sf.hibernate.HibernateException;
+ import org.hibernate.HibernateException;
+ import org.hibernate.Session;
  
- public class HibernateFilter implements Filter {
+ public class OpenSessionInViewFilter implements Filter {
- 
-   private FilterConfig config = null;
+  
+  private PersistenceFactory persistenceFactory = null;
+  private ServletContext ctxt = null;
  
    public void init(FilterConfig config) throws ServletException {
-     this.config = config;
+ 	/* Get and store ServletContext.
+ 	 * 
+ 	 * Since this will probably be called *before* Cocoon
+ 	 * is initialized, we cannot obtain the persistenceFactory
+ 	 * right here, but must do so in the doFilter method.
+ 	 */
+ 	this.ctxt = config.getServletContext(); 
+ 
    }
  
    public void destroy() {
-     config = null;
+ 	  ctxt = null;
+ 	  persistenceFactory = null;
    }
  
    public void doFilter(ServletRequest request, ServletResponse response,
                       FilterChain chain) throws IOException, ServletException {
-     // Pass the request on to cocoon
+     /* Obtain persistenceFactory from the servlet context
+      * if possible 
+      */
+ 	if( persistenceFactory == null )
+ 		persistenceFactory = (PersistenceFactory)ctxt.getAttribute(PersistenceFactory.ROLE);
+ 	
+ 	/* Create request-local scope */ 
+ 	PersistenceUtil.initializeScope();
+ 	
+ 	if( persistenceFactory != null ){
+ 		/* If we have a persistenceFactory, 
+ 		 * open a Hibernate session to be used throughout
+ 		 * this request. 
+ 		 */
+ 		Session hs = persistenceFactory.createSession();
+ 		if( hs != null ){
+ 			PersistenceUtil.setHibernateSession(hs);
+ 		    hs.beginTransaction();
-     chain.doFilter(request, response);
+ 		    chain.doFilter( request, response );
+ 		    /* The transaction closed here is not the same 
+ 		     * one we opened before if the user has called
+ 		     * PersistenceUtil.flushTransaction() 
+ 		     */
+ 		    hs.getTransaction().commit();
- 
-     // After cocoon has finished processing, close the
-     // corresponding Hibernate session if it has been opened
-     if( request.getAttribute("DisposeHibernateSession") != null )
-     {
-      Session hs = (Session) request.getAttribute("DisposeHibernateSession");
-      try{
+ 		    try {
-       hs.flush();
+ 		    	/* Although this method is deprecated, as of 
+ 		    	 * Hibernate 3.2 there is no alternative method
+ 		    	 * to obtain the underlying connection ... this is
+ 		    	 * scheduled for Hibernate 3.3! 
+ 		    	 * So, it is not possible for the moment to replace
+ 		    	 * this with something that does not give a 
+ 		    	 * deprecation warning :/ 
+ 		    	 */
-       hs.connection().close();
+ 				hs.connection().close();
+ 			} catch (HibernateException e) {
+ 				e.printStackTrace();
+ 			} catch (SQLException e) {
+ 				e.printStackTrace();
+ 			}
-       hs.close();
+ 		    hs.close();
-      }
-      catch( HibernateException e ){
-       System.out.println(e.getMessage());
-      }
-      catch( SQLException e ){
-       System.out.println(e.getMessage());
-         }
-         request.setAttribute("DisposeHibernateSession",null);
-     }
+ 		} else {
+ 			System.out.println("Hibernate Session could not be opened!");
+ 			chain.doFilter( request, response );
+ 		}
+ 	} else {
+ 		/* Otherwise, do nothing. */ 
+ 		chain.doFilter(request, response ); 
+ 	}
+ 	PersistenceUtil.destroyScope();
    }
  }
  }}}
@@ -493, +542 @@

  
  <filter-mapping>
    <filter-name>hibernateFilter</filter-name>
-   <url-pattern>/*</url-pattern>
+   <url-pattern>/languages/*</url-pattern>
  </filter-mapping>
  }}}
  
+ This tells the container to invoke our filter on all requests on the path "/languages/*". Unfortunately, filter mappings are rather unflexible: you can only map to whole subtrees or to all files with a specific ending (like "*.jsp"). Be careful not to open Hibernate sessions on image URLs, for example; although this would not do any damage, it would still slow the request down unnecessarily. 
- This tells the container to invoke our filter on all requests. That is OK since our filter
- will only dispose sessions that have previously been opened. However, you might want to
- use the filter only on URLs that require the opening of a Hibernate session. In that case,
- modify the url-pattern accordingly. You may also define multiple filter mappings.
  
- === Using the filter ===
+ = An Example "CRUD" Application =
  
+ "Now that Hibernate is installed, how do I continue?" - Well the answer is up to you, since there are so many ways of using Hibernate. What follows is some kind of a minimal example so you get a feeling how Hibernate '''could''' interact with Cocoon. As always, you can compile the classes here yourself, or use the hibernate-interface.jar. 
- Since the filter only cares for disposing open sessions, you still have to open them yourself
- using the {{{PersistenceFactory}}} class designed above. Also you need to make sure that every
- time a session is opened, it is correctly stored in the servlet request such that the
- filter will later close it.
  
+ In this example we'll use Hibernate to manage a list of ISO language codes. So the first thing we need is a Java Bean that represents those language codes. Our bean will just have two attributes, the code and a name for each language, plus a numerical ID (I use one for each and every class). The code doesn't look all that interesting:
- It might be a good idea to create a
- little helper function for doing this. I like to have the session as a global variable since
- it is often used across functions during the processing of a pipeline. In the code snippet below,
- this variable is called {{{hs}}}. From cocoon 2.1.6 onwards, an Exception will be thrown if you
- do not explicitly declare this global variable. And oh, the package name ... :)
  
  {{{
- var hs;
+ package org.test.beans;
  
- function openHibernateSession()
- {
- 	// Make sure Hibernate Sessions are not opened twice
- 	if(hs && hs.isOpen())
- 		return;
+ public class Language {
+     private long id;
+     private String isocode;
+     private String name;
+     /**
+      *
+      */
+     public Language() {
+             super();
+     }
+     /**
+      * @return Returns the iD.
+      */
+     public long getId() {
+             return id;
+     }
+     /**
+      * @param id The iD to set.
+      */
+     public void setId(long id) {
+             this.id = id;
+     }
+     /**
+      * @return Returns the isoCode.
+      */
+     public String getIsocode() {
+             return isocode;
+     }
+     /**
+      * @param isoCode The isoCode to set.
+      */
+     public void setIsocode(String isocode) {
+             this.isocode = isocode;
+     }
+     /**
+      * @return Returns the name.
+      */
+     public String getName() {
+             return name;
+     }
  
+     /**
+      * @param name The name to set.
+      */
+     public void setName(String name) {
+             this.name = name;
+     }
- 	// Get new Session from PersistenceFactory
- 	var factory = cocoon.getComponent(Packages.org.test.PersistenceFactory.ROLE);
- 	hs = factory.createSession();
- 	if (hs == null) {
- 		throw new Packages.org.apache.cocoon.ProcessingException("Hibernate session is null ");
- 	}
- 
- 	// Release PersistenceFactory
- 	cocoon.releaseComponent(factory);
- 
- 	// Send "Message" to HibernateFilter to dispose the session after the view was rendered
- 	cocoon.request.setAttribute("DisposeHibernateSession",hs);
  }
  }}}
  
+ The language beans will be accessed via DAOs, which could inherit from an abstract class as follows: 
- After calling {{{openHibernateSession()}}}, you are finally ready to use the session for doing some
- Persistence.
  
- Note:
- Of course now you need to make sure that you manage a session (I.E. add the session action to your pipeline). If you will not add the session management, the filter will crash with "Cannot create a session after the response has been committed".
- 
- === A first Example ===
- 
- "Now that Hibernate is installed, how do I continue?" - Well the answer is up to you, since there are so many ways of using Hibernate. What follows is some kind of a minimal example so you get a feeling how Hibernate '''could''' interact with Cocoon.
- 
- In this example we'll use Hibernate to manage a list of ISO language codes. So the first thing we need is a Java Bean that represents those language codes. Our bean will just have two attributes, the code and a name for each language, plus a numerical ID (I use one for each and every class). The code doesn't look all that interesting:
- 
  {{{
- package org.test;
+ package org.test.daos;
+ import org.apache.cocoon.hibernate.PersistenceUtil;
+ import org.hibernate.Session;
  
+ public abstract class DAO {
+ 	public Session session(){
+         if (PersistenceUtil.getHibernateSession() == null)
+             throw new IllegalStateException("Session has not been set on DAO before usage");
+ 		return PersistenceUtil.getHibernateSession(); 
- public class DCLanguage {
- 	private long ID;
- 	private String IsoCode;
- 	private String Name;
- 	/**
- 	 *
- 	 */
- 	public DCLanguage() {
- 		super();
  	}
+ 	
+ 	public void flush(){
+ 		PersistenceUtil.flushTransaction();
- 	/**
- 	 * @return Returns the iD.
- 	 */
- 	public long getID() {
- 		return ID;
- 	}
- 	/**
- 	 * @param id The iD to set.
- 	 */
- 	public void setID(long id) {
- 		ID = id;
- 	}
- 	/**
- 	 * @return Returns the isoCode.
- 	 */
- 	public String getIsoCode() {
- 		return IsoCode;
- 	}
- 	/**
- 	 * @param isoCode The isoCode to set.
- 	 */
- 	public void setIsoCode(String isoCode) {
- 		IsoCode = isoCode;
- 	}
- 	/**
- 	 * @return Returns the name.
- 	 */
- 	public String getName() {
- 		return Name;
- 	}
- 
-         /**
- 	 * @param name The name to set.
- 	 */
- 	public void setName(String name) {
- 		Name = name;
  	}
  }
  }}}
  
+ The actual implementation of a DAO for languages might look like this: 
+ 
+ {{{
+ package org.test.daos;
+ 
+ import java.util.List;
+ 
+ import org.hibernate.criterion.Order;
+ import org.test.beans.Language;
+ 
+ public class LanguageDAO extends DAO {
+ 	public List findAll(){
+ 		return session()
+ 			.createCriteria(Language.class)
+ 			.addOrder(Order.asc("isocode"))
+ 			.list();
+ 	}
+ 	
+ 	public Language findById(Long id){
+ 		return (Language)session().load(Language.class, id);
+ 	}
+ 	
+ 	public void makePersistent(Language l){
+ 		session().saveOrUpdate(l);
+ 	}
+ 	
+ 	public void makeTransient(Language l){
+ 		session().delete(l);
+ 	}
+ }
+ }}}
+ 
- Compile this class and store it in the corresponding subfolder of WEB-INF/classes (Package name ...).
+ Compile these classes and store them in the corresponding subfolders of WEB-INF/classes. 
- Next, we shall create a table in our database to store the objects in.
+ Next, we will create a table in our database to store the objects in.
  
  {{{
- CREATE TABLE `dclanguage` (
+ CREATE TABLE `language` (
-   `ID` int(11) NOT NULL auto_increment,
+   `id` int(11) NOT NULL auto_increment,
-   `IsoCode` varchar(255) NOT NULL default '',
+   `isocode` varchar(255) NOT NULL default '',
-   `Name` varchar(255) NOT NULL default '',
+   `name` varchar(255) NOT NULL default '',
    PRIMARY KEY  (`ID`)
  )
  }}}
@@ -619, +674 @@

  Fill in some sample values, or use the following if you're too lazy: :)
  
  {{{
-  INSERT INTO `dclanguage` VALUES (1,'de','german');
+  INSERT INTO `language` VALUES (1,'de','german');
-  INSERT INTO `dclanguage` VALUES (2,'en','english');
+  INSERT INTO `language` VALUES (2,'en','english');
-  INSERT INTO `dclanguage` VALUES (3,'fr','french');
+  INSERT INTO `language` VALUES (3,'fr','french');
-  INSERT INTO `dclanguage` VALUES (4,'ru','russian');
+  INSERT INTO `language` VALUES (4,'ru','russian');
-  INSERT INTO `dclanguage` VALUES (5,'pl','polish');
+  INSERT INTO `language` VALUES (5,'pl','polish');
-  INSERT INTO `dclanguage` VALUES (6,'da','danish');
+  INSERT INTO `language` VALUES (6,'da','danish');
  }}}
  
  The glue between our POJO and the SQL table is the hibernate mapping definition. This is an XML file which should be called {{{DCLanguage.hbm.xml}}}, since our class is called {{{DCLanguage.class}}}. Create the file with the following content and put it into the subfolder of WEB-INF/classes where your DCLanguage class resides:
  
  {{{
  <?xml version="1.0"?>
+ 
  <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
+ 	"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "hibernate-mapping-2.0.dtd">
+     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+ 
  <hibernate-mapping>
-         <class name="org.test.DCLanguage" table="dclanguage">
+         <class name="org.test.beans.Language" table="language">
-                 <id name="ID" column="ID" type="long">
+                 <id name="id" column="id" type="long">
                          <generator class="native"/>
                  </id>
-                 <property name="IsoCode" column="IsoCode" type="string"/>
+                 <property name="isocode" column="isocode" type="string" not-null="true"/>
-                 <property name="Name" column="Name" type="string"/>
+                 <property name="name" column="name" type="string" not-null="true"/>
          </class>
  </hibernate-mapping>
  }}}
@@ -658, +715 @@

  
  Now you're almost done. Just insert the following statement:
  
- {{{cfg.addClass(DCLanguage.class);}}}
+ {{{<mapping resource="org/test/beans/Language.hbm.xml"/>}}}
  
- into the {{{HibernateFactory}}} (see ''ADD PERSISTENT CLASSES, VARIANT 1'' in the source code), recompile it, overwrite it in WEB-INF/classes and restart Cocoon. Congratulations, you should now be able to manage your ISO language codes via Hibernate and Cocoon :)
+ into the {{{hibernate.cfg.xml}}} (see ''ADD PERSISTENT CLASSES, VARIANT 2'' in the source code) and restart Cocoon. Congratulations, you should now be able to manage your ISO language codes via Hibernate and Cocoon :)
  
- To verify this, create a testing sub-sitemap, attach a flowscript and insert the following function into it:
+ To verify this, create a testing sub-sitemap, attach a flowscript and insert the following mini-function into it:
  
  {{{
  function language_list()
  {
+ 	var ldao = new Packages.org.test.daos.LanguageDAO();
+ 	cocoon.sendPage("language-list.jxt",{languages:ldao.findAll()});
- 	openHibernateSession(); // as defined in Tutorial
- 
- 	var languages = hs.find("from org.test.DCLanguage order by IsoCode asc");
- 		// HQL String to get a list of all beans, ordered by their IsoCode
- 
- 	cocoon.sendPage("language-list.jxt", {
- 		languages:languages
- 	}); // Send objects to the view layer (here: JX Template)
- 
- 	// Hibernate Session will be closed automatically
- 	// by the servlet filter
  }
  }}}
  
@@ -705, +753 @@

  </languages>
  }}}
  
- Alas, you're done. Surf to "base-uri-of-your-sitemap"/language-list and contemplate the power of Cocoon and Hibernate :)
+ Alas, you're done. Surf to "base-uri-of-your-sitemap"/language-list and contemplate the power of Cocoon and Hibernate :) 
  
- As said before, this is a minimal example. Now you're ready to continue on your own, the Hibernate docs will be your friend. Note that this example does not represent the recommended way of doing things - particularly since your querying your objects from Flowscript. Many people will rather want to do this from a Data Access Object (DAO) written in Java. The next part of this tutorial will cover this.
+ As said before, this is a minimal example. Now you're ready to continue on your own, the Hibernate docs will be your friend. Also, the attached ZIP file contains an example "application" which covers all CRUD operations and uses CForms. 
+ 
+ If you've really read the whole tutorial, yes, you have finally come to an end. If you try this out and run into problems, questions and feedback are appreciated. Also make sure to search through the mailing list archives. 
  
  == Appendix ==
  
  === Links to other information sources ===
  
- [GettingStartedWithCocoonAndHibernate]
+ [GettingStartedWithCocoonAndHibernate] [[BR]]
  [CformsHibernateAndFlow] [[BR]]
  [UsingHibernateToMakeYourJavaBeansPersistent]
  

Mime
View raw message