Tapestry Hibernate Session Extension
Eric Everman <[email protected]>
| Newsgroups | gmane.comp.java.tapestry.devel |
|---|---|
| Message-ID | <[email protected]> |
Howard- Thanks for the suggestion. I guess I missed the fact that there was such a thing as an extension. For anyone interested, attached is a Tapestry Extension helper bean which creates Hibernate Sessions on demand. Setup and use info is in the class javadocs. It does make some assumptions about how you configure Hibernate and how you use Sessions, so it is not a be-all solution. Suggestions or questions are welcome, Eric Everman At 11/24/2002, Howard M. Lewis Ship wrote: >I took a *very* cursory look at this. Have you considered using the >Tapestry <extension> mechanism to support this? It is a more natural way to >provide global resources to applications and doesn't require subclassing the >ApplicationServlet.
SessionSourceExt.java
(text/plain, 10 KB)
package com.preceda.hibertap;
/**
* @author Eric Everman
*
* 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;
}
}