001 /*
002 * Databinder: a simple bridge from Wicket to Hibernate
003 * Copyright (C) 2006 Nathan Hamblen nathan@technically.us
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either
008 * version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013 * Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public
016 * License along with this library; if not, write to the Free Software
017 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
018 */
019
020 /*
021 * Note: this class contains code adapted from wicket-contrib-database.
022 */
023
024 package net.databinder.hib.conv;
025
026 import net.databinder.hib.DataRequestCycle;
027 import net.databinder.hib.Databinder;
028 import net.databinder.hib.conv.components.IConversationPage;
029
030 import org.apache.wicket.Page;
031 import org.apache.wicket.Response;
032 import org.apache.wicket.protocol.http.WebApplication;
033 import org.apache.wicket.protocol.http.WebRequest;
034 import org.hibernate.FlushMode;
035 import org.hibernate.HibernateException;
036 import org.hibernate.classic.Session;
037 import org.hibernate.context.ManagedSessionContext;
038 import org.slf4j.Logger;
039 import org.slf4j.LoggerFactory;
040
041 /**
042 * Supports extended Hibernate sessions for long conversations. This is useful for a page or
043 * a series of pages where changes are made to an entity that can not be immediately
044 * committed. Using a "conversation" session, HibernateObjectModels are used normally, but
045 * until the session is flushed the changes are not made to persistent storage.
046 * @author Nathan Hamblen
047 */
048 public class DataConversationRequestCycle extends DataRequestCycle {
049 private static final Logger log = LoggerFactory.getLogger(DataConversationRequestCycle.class);
050
051 public DataConversationRequestCycle(WebApplication application, WebRequest request, Response response) {
052 super(application, request, response);
053 }
054
055 /**
056 * Does nothing; The session is open or retreived only when the request target is known.
057 */
058 @Override
059 protected void onBeginRequest() {
060 }
061
062 /**
063 * Called by DataStaticService when a session is needed and does not already exist.
064 * Determines current page and retrieves its associated conversation session if
065 * appropriate. Does nothing if current page is not yet available.
066 * @param key factory key object, or null for the default factory
067 */
068 public void dataSessionRequested(Object key) {
069 Page page = getResponsePage();
070 if (page == null)
071 page = getRequest().getPage();
072
073 if (page == null) {
074 Class pageClass = getResponsePageClass();
075 if (pageClass != null) {
076 openHibernateSession(key);
077 // set to manual if we are going to a conv. page
078 if (IConversationPage.class.isAssignableFrom(pageClass))
079 Databinder.getHibernateSession(key).setFlushMode(FlushMode.MANUAL);
080 }
081 return;
082 }
083
084 // if continuing a conversation page
085 if (page instanceof IConversationPage) {
086 // look for existing session
087 IConversationPage convPage = (IConversationPage) page;
088 org.hibernate.classic.Session sess = convPage.getConversationSession(key);
089
090 // if usable session exists, try to open txn, bind, and return
091 if (sess != null && sess.isOpen()) {
092 try {
093 sess.beginTransaction();
094 ManagedSessionContext.bind(sess);
095 keys.add(key);
096 return;
097 } catch (HibernateException e) {
098 log.warn("Existing session exception on beginTransation, opening new", e);
099 }
100 }
101 // else start new one and set in page
102 sess = openHibernateSession(key);
103 sess.setFlushMode(FlushMode.MANUAL);
104 ((IConversationPage)page).setConversationSession(key, sess);
105 return;
106 }
107 // start new standard session
108 openHibernateSession(key);
109 }
110
111 /**
112 * Inspects responding page to determine if current Hibernate session should be closed
113 * or left open and stored in the page.
114 */
115 @Override
116 protected void onEndRequest() {
117 for (Object key : keys) {
118 if (!ManagedSessionContext.hasBind(Databinder.getHibernateSessionFactory(key)))
119 return;
120 org.hibernate.classic.Session sess = Databinder.getHibernateSession(key);
121 boolean transactionComitted = false;
122 if (sess.getTransaction().isActive())
123 sess.getTransaction().rollback();
124 else
125 transactionComitted = true;
126
127 Page page = getResponsePage() ;
128
129 if (page != null) {
130 // check for current conversational session
131 if (page instanceof IConversationPage) {
132 IConversationPage convPage = (IConversationPage)page;
133 // close if not dirty contains no changes
134 if (transactionComitted && !sess.isDirty()) {
135 sess.close();
136 sess = null;
137 }
138 convPage.setConversationSession(key, sess);
139 } else
140 sess.close();
141 }
142 ManagedSessionContext.unbind(Databinder.getHibernateSessionFactory(key));
143 }
144 }
145
146 /**
147 * Closes and reopens Hibernate session for this Web session. Unrelated models may try to load
148 * themselves after this point.
149 */
150 @Override
151 public Page onRuntimeException(Page page, RuntimeException e) {
152 for (Object key : keys) {
153 if (Databinder.hasBoundSession(key)) {
154 Session sess = Databinder.getHibernateSession(key);
155 try {
156 if (sess.getTransaction().isActive())
157 sess.getTransaction().rollback();
158 } finally {
159 sess.close();
160 ManagedSessionContext.unbind(Databinder.getHibernateSessionFactory(key));
161 }
162 }
163 openHibernateSession(key);
164 }
165 return null;
166 }
167
168 }