I wrote a Tapestry extension for Hibernate sessions, but I have not tried it yet in 2.3. It creates Hibernate sessions on demand with a scope of a single page for a single request. Sessions are automatically committed when the page renders or when you switch pages (cycle.setPage()).
My source code is attached - let me know if you have any problems using it in 2.3.
Eric Everman
package com.preceda.hibertap;
/**
* @author Eric Everman of Preceda Design
*
* A Tapestry Extension that provides Hibernate Sessions on demand.
*
* <p>
* To use this extension, access it as you would any other Tapetstry Extension
* via the ApplicationSpecification.
*
* <p>
* To configure the extension, add something like this to the .application:
* (Not Javadoc friendly)
* <extension name="SessionSourceExt" class="com.preceda.hibertap.SessionSourceExt"
immediate="yes">
* <configure property-name="classList" type="String">
* com.preceda.contact.Contact, com.other.OtherClasse, ...
* </configure>
* </extension>
*
* <p>
* To access the extension within a page or component, use:
* <code>
* SessionSourceExt sse = (SessionSourceExt)
* (getEngine().getSpecification().getExtension("SessionSourceExt"));
* </code>
*
* <p>
* To use the SessionSource extension, there are only three methods that you
* need to worry about: getSession, commitAndClose, and rollbackAndClose.
* Each method takes a IRequestCycle as an argument. A returned Session has
* a lifetime of a <b>SINGLE PAGE</b> for a <b>SINGLE REQUEST</b>. At the
* end of that lifetime, the transaction will automatically be committed
* via a pageEndRenderer listener. Thus, unless you need to rollback a
* transaction, you would normally not need to worry about flushing & committing.
*
* <p>
* If the page changes during the cycle via
* <code>cycle.setPage()<code>, the current Session's transaction is
* committed and the Session is discarded. Further requests for a Session
* will be serviced by a new Session.
*
* <p>
* Sample Usage (pretty easy):
*
* <code>
* SessionSourceExt sse = (SessionSourceExt)
* (getEngine().getSpecification().getExtension("SessionSourceExt"));
* Session s = sse.getSession(cycle);
* . . .do some work
* cycle.setPage("Success"); //Session is auto committed & closed
* </code>
*
* <p>
* To Do:
* <ul>
* <li>Would a method 'commitAndContinue' be useful?
* </ul>
*
*/
import java.util.StringTokenizer;
import java.io.InputStream;
import java.util.Properties;
import net.sf.tapestry.IPage;
import net.sf.tapestry.IRequestCycle;
import net.sf.tapestry.event.PageEvent;
import net.sf.tapestry.event.PageRenderListener;
import cirrus.hibernate.Session;
import cirrus.hibernate.SessionFactory;
import cirrus.hibernate.Transaction;
import cirrus.hibernate.Datastore;
import cirrus.hibernate.Hibernate;
public class SessionSourceExt {
public final static String SESSION_KEY = "DEFAULT_HIBERNATE_SESSION";
private String classList; //List of classes to persist
//Full absolute path name to the hibernate.properties file.
//Defaults to Hibernate default.
private String propertiesFileName = "/hibernate.properties";
private SessionFactory sessionFactory; //Used to create all requested Sessions
//Single instance listener used to close & commit sessions
//when a page completes the redering process.
private PageRenderListener pageRenderListener;
/**
* Default empty constructor
*/
public SessionSourceExt() {}
/**
* A list of persisted classes.
*
* A list of fully qualified class names that Hibernate should persist.
* Names can be separated with commas, semi-colons, or spaces.
* For each class persisted, Hibernate expects to find a file named
* "ClassName.hbm.xml" in the same package location as the class.
*
* @param classList String
*/
public void setClassList(String classList) {
this.classList = classList;
}
/**
* Fully qualified name of the Hibernate Properties file.
*
* Optional - defaults to "/hibernate.properties".
*
* By Hibernate convention, this file is normally placed in the classpath
* root, however it could be placed anywhere. Note that the name must
* be of this form: "/package/package/name.properties" or if in the
* classpath root: "/name.properties".
*
* <b>Limitation</b>The properties file must configure Hibernate to supply its
* own connections.
*
* @param propertiesFileName String
*/
public void setPropertiesFileName(String propertiesFileName) {
this.propertiesFileName = propertiesFileName;
}
/**
* Retrieves an existing Hibernate Session or creates a new one.
*/
public Session getSession(IRequestCycle cycle) throws Exception {
//System.out.print("*** getSession");
SessionWrapper sw = (SessionWrapper)cycle.getAttribute(SESSION_KEY);
Session sess = null;
if (sw != null) sess = sw.session;
if (sess == null) {
sess = createSession(cycle); //No previous session, create
new
} else if (! sess.isOpen()) {
closeAndRollback(cycle); //The user
closed the session (do cleanup)
sess = createSession(cycle); //Now create a new session
} else if (! sess.isConnected()) {
sess.reconnect(); //The user disconnected the
session
}
return sess;
}
/**
* Create a new session and store it as an attribute of the RequestCycle.
*
* This method also adds a pageEndRender listener on the current page so
* that the session can be automatically committed when the page has
* completed rendering.
*/
private Session createSession(IRequestCycle cycle) throws Exception {
//System.out.print("*** Create Session");
if (sessionFactory == null) init();
//Create a Session & begin a transaction on it
Session session = sessionFactory.openSession();
Transaction trans = session.beginTransaction();
//Store Session & Transaction to a SessionWrapper & store as cycle
attrib
SessionWrapper sw = new SessionWrapper();
sw.session = session;
sw.transaction = trans;
cycle.setAttribute(SESSION_KEY, sw);
//Add pageRenderListener to allow auto commits
IPage page = cycle.getPage();
page.addPageRenderListener(pageRenderListener);
return session;
}
/**
* Close and commits the session contained in the passed cycle
*
* After calling this method, the current Session will be defunct and future
* calls to getSession will be servied by a new Session.
*/
public void closeAndCommit(IRequestCycle cycle) {
//System.out.print("*** Close And Commit Session");
SessionWrapper sw = (SessionWrapper)cycle.getAttribute(SESSION_KEY);
if (sw != null) {
Session session = sw.session;
Transaction trans = sw.transaction;
if (session != null && session.isOpen()) {
try {
try {
trans.commit();
//System.out.print("*** Session Closed
OK");
} catch (Exception ee) {
System.out.print("*** Error during
session close");
trans.rollback();
} finally {
session.close();
}
} catch (Exception e) {
//Can't do anything at this point - rollback
or close failed
}
} else {
System.out.print("*** Session is closed (!!)");
}
//Cleanup
cycle.removeAttribute(SESSION_KEY);
cycle.getPage().removePageRenderListener(pageRenderListener);
} else {
System.out.print("*** There is no SessionWrapper!!!");
}
}
/**
* Rolls back the session's transaction and closes the session.
*
* After calling this method, the current Session will be defunct and future
* calls to getSession will be servied by a new Session.
*/
public void closeAndRollback(IRequestCycle cycle) {
//System.out.print("*** Rollback");
SessionWrapper sw = (SessionWrapper)cycle.getAttribute(SESSION_KEY);
if (sw != null) {
Session sess = sw.session;
Transaction trans = sw.transaction;
if (trans != null) {
try {
trans.rollback();
} catch (Exception e) { /* Not much we can do */ }
try {
sess.close();
} catch (Exception e) { /* Not much we can do */ }
}
//Cleanup
cycle.removeAttribute(SESSION_KEY);
cycle.getPage().removePageRenderListener(pageRenderListener);
} else {
System.out.print("*** You are trying to rollback a cycle with
no Session!!");
}
}
private void init() throws Exception {
if (classList == null || classList.length() == 0) {
throw new Exception(
"The Required init param classNames was not set or was
empty.");
} else {
pageRenderListener = new PageRenderListener() {
public void pageBeginRender(PageEvent event) { /*Don't
need this event*/ }
public void pageEndRender(PageEvent event) {
//System.out.print("*** PageEndRender Event");
closeAndCommit(event.getRequestCycle());
event.getRequestCycle().removeAttribute(SESSION_KEY);
event.getPage().removePageRenderListener(pageRenderListener);
}
};
StringTokenizer st = new StringTokenizer(classList, ",; ");
Datastore ds = Hibernate.createDatastore();
try {
while (st.hasMoreElements()) {
ds.storeClass(Class.forName(st.nextToken()));
}
} catch (Exception e) {
throw new Exception(
"An error occured while building Hibernate's
persistant class list. " +
"Most likely, one of the class names is not on
the classpath.",
e);
}
//Load Hibernate Properties from properties file
Properties props = new Properties();
InputStream iStream = null;
try {
// "/" causes the loader to not convert dots to
slashes (ie abs path).
iStream =
this.getClass().getResourceAsStream(propertiesFileName);
props.load(iStream);
iStream.close();
} catch (Exception e) {
throw new Exception(
"The properties file '" + propertiesFileName +
"' could not be loaded.", e);
}
sessionFactory = ds.buildSessionFactory(props); //Build
session factory
try {
//Test session
Session s = sessionFactory.openSession();
Transaction t = s.beginTransaction();
t.commit();
s.close();
} catch (Exception e) {
throw new Exception(
"An error occured while testing the Hibernate
SessionFactory.",
e);
}
}
}
/**
* Inner class used to encapsilate a session with its transaction
*/
static class SessionWrapper {
protected Session session;
protected Transaction transaction;
}
}
<<< text/plain; charset=us-ascii; format=flowed: Unrecognized >>>
