001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.orm.hibernate.support;
018:
019: import java.io.IOException;
020:
021: import javax.servlet.FilterChain;
022: import javax.servlet.ServletException;
023: import javax.servlet.http.HttpServletRequest;
024: import javax.servlet.http.HttpServletResponse;
025:
026: import net.sf.hibernate.FlushMode;
027: import net.sf.hibernate.Session;
028: import net.sf.hibernate.SessionFactory;
029:
030: import org.springframework.dao.DataAccessResourceFailureException;
031: import org.springframework.orm.hibernate.SessionFactoryUtils;
032: import org.springframework.orm.hibernate.SessionHolder;
033: import org.springframework.transaction.support.TransactionSynchronizationManager;
034: import org.springframework.web.context.WebApplicationContext;
035: import org.springframework.web.context.support.WebApplicationContextUtils;
036: import org.springframework.web.filter.OncePerRequestFilter;
037:
038: /**
039: * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire
040: * processing of the request. Intended for the "Open Session in View" pattern,
041: * i.e. to allow for lazy loading in web views despite the original transactions
042: * already being completed.
043: *
044: * <p>This filter makes Hibernate Sessions available via the current thread, which
045: * will be autodetected by transaction managers. It is suitable for service layer
046: * transactions via {@link org.springframework.orm.hibernate.HibernateTransactionManager}
047: * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
048: * as for non-transactional read-only execution.
049: *
050: * <p><b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate Session,
051: * with the flush mode set to <code>FlushMode.NEVER</code>. It assumes to be used
052: * in combination with service layer transactions that care for the flushing: The
053: * active transaction manager will temporarily change the flush mode to
054: * <code>FlushMode.AUTO</code> during a read-write transaction, with the flush
055: * mode reset to <code>FlushMode.NEVER</code> at the end of each transaction.
056: *
057: * <p><b>WARNING:</b> Applying this filter to existing logic can cause issues that
058: * have not appeared before, through the use of a single Hibernate Session for the
059: * processing of an entire request. In particular, the reassociation of persistent
060: * objects with a Hibernate Session has to occur at the very beginning of request
061: * processing, to avoid clashes with already loaded instances of the same objects.
062: *
063: * <p>Alternatively, turn this filter into deferred close mode, by specifying
064: * "singleSession"="false": It will not use a single session per request then,
065: * but rather let each data access operation or transaction use its own session
066: * (like without Open Session in View). Each of those sessions will be registered
067: * for deferred close, though, actually processed at request completion.
068: *
069: * <p>A single session per request allows for most efficient first-level caching,
070: * but can cause side effects, for example on <code>saveOrUpdate</code> or when
071: * continuing after a rolled-back transaction. The deferred close strategy is as safe
072: * as no Open Session in View in that respect, while still allowing for lazy loading
073: * in views (but not providing a first-level cache for the entire request).
074: *
075: * <p>Looks up the SessionFactory in Spring's root web application context.
076: * Supports a "sessionFactoryBeanName" filter init-param in <code>web.xml</code>;
077: * the default bean name is "sessionFactory". Looks up the SessionFactory on each
078: * request, to avoid initialization order issues (when using ContextLoaderServlet,
079: * the root application context will get initialized <i>after</i> this filter).
080: *
081: * @author Juergen Hoeller
082: * @since 06.12.2003
083: * @see #setSingleSession
084: * @see #lookupSessionFactory
085: * @see OpenSessionInViewInterceptor
086: * @see org.springframework.orm.hibernate.HibernateInterceptor
087: * @see org.springframework.orm.hibernate.HibernateTransactionManager
088: * @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession
089: * @see org.springframework.transaction.support.TransactionSynchronizationManager
090: */
091: public class OpenSessionInViewFilter extends OncePerRequestFilter {
092:
093: public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";
094:
095: private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME;
096:
097: private boolean singleSession = true;
098:
099: /**
100: * Set the bean name of the SessionFactory to fetch from Spring's
101: * root application context. Default is "sessionFactory".
102: * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME
103: */
104: public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
105: this .sessionFactoryBeanName = sessionFactoryBeanName;
106: }
107:
108: /**
109: * Return the bean name of the SessionFactory to fetch from Spring's
110: * root application context.
111: */
112: protected String getSessionFactoryBeanName() {
113: return this .sessionFactoryBeanName;
114: }
115:
116: /**
117: * Set whether to use a single session for each request. Default is "true".
118: * <p>If set to "false", each data access operation or transaction will use
119: * its own session (like without Open Session in View). Each of those
120: * sessions will be registered for deferred close, though, actually
121: * processed at request completion.
122: * @see SessionFactoryUtils#initDeferredClose
123: * @see SessionFactoryUtils#processDeferredClose
124: */
125: public void setSingleSession(boolean singleSession) {
126: this .singleSession = singleSession;
127: }
128:
129: /**
130: * Return whether to use a single session for each request.
131: */
132: protected boolean isSingleSession() {
133: return this .singleSession;
134: }
135:
136: protected void doFilterInternal(HttpServletRequest request,
137: HttpServletResponse response, FilterChain filterChain)
138: throws ServletException, IOException {
139:
140: SessionFactory sessionFactory = lookupSessionFactory(request);
141: boolean participate = false;
142:
143: if (isSingleSession()) {
144: // single session mode
145: if (TransactionSynchronizationManager
146: .hasResource(sessionFactory)) {
147: // Do not modify the Session: just set the participate flag.
148: participate = true;
149: } else {
150: logger
151: .debug("Opening single Hibernate Session in OpenSessionInViewFilter");
152: Session session = getSession(sessionFactory);
153: TransactionSynchronizationManager.bindResource(
154: sessionFactory, new SessionHolder(session));
155: }
156: } else {
157: // deferred close mode
158: if (SessionFactoryUtils
159: .isDeferredCloseActive(sessionFactory)) {
160: // Do not modify deferred close: just set the participate flag.
161: participate = true;
162: } else {
163: SessionFactoryUtils.initDeferredClose(sessionFactory);
164: }
165: }
166:
167: try {
168: filterChain.doFilter(request, response);
169: }
170:
171: finally {
172: if (!participate) {
173: if (isSingleSession()) {
174: // single session mode
175: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
176: .unbindResource(sessionFactory);
177: logger
178: .debug("Closing single Hibernate Session in OpenSessionInViewFilter");
179: closeSession(sessionHolder.getSession(),
180: sessionFactory);
181: } else {
182: // deferred close mode
183: SessionFactoryUtils
184: .processDeferredClose(sessionFactory);
185: }
186: }
187: }
188: }
189:
190: /**
191: * Look up the SessionFactory that this filter should use,
192: * taking the current HTTP request as argument.
193: * <p>Default implementation delegates to the <code>lookupSessionFactory</code>
194: * without arguments.
195: * @return the SessionFactory to use
196: * @see #lookupSessionFactory()
197: */
198: protected SessionFactory lookupSessionFactory(
199: HttpServletRequest request) {
200: return lookupSessionFactory();
201: }
202:
203: /**
204: * Look up the SessionFactory that this filter should use.
205: * <p>Default implementation looks for a bean with the specified name
206: * in Spring's root application context.
207: * @return the SessionFactory to use
208: * @see #getSessionFactoryBeanName
209: */
210: protected SessionFactory lookupSessionFactory() {
211: if (logger.isDebugEnabled()) {
212: logger.debug("Using SessionFactory '"
213: + getSessionFactoryBeanName()
214: + "' for OpenSessionInViewFilter");
215: }
216: WebApplicationContext wac = WebApplicationContextUtils
217: .getRequiredWebApplicationContext(getServletContext());
218: return (SessionFactory) wac.getBean(
219: getSessionFactoryBeanName(), SessionFactory.class);
220: }
221:
222: /**
223: * Get a Session for the SessionFactory that this filter uses.
224: * Note that this just applies in single session mode!
225: * <p>The default implementation delegates to SessionFactoryUtils'
226: * getSession method and sets the Session's flushMode to NEVER.
227: * <p>Can be overridden in subclasses for creating a Session with a custom
228: * entity interceptor or JDBC exception translator.
229: * @param sessionFactory the SessionFactory that this filter uses
230: * @return the Session to use
231: * @throws DataAccessResourceFailureException if the Session could not be created
232: * @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory, boolean)
233: * @see net.sf.hibernate.FlushMode#NEVER
234: */
235: protected Session getSession(SessionFactory sessionFactory)
236: throws DataAccessResourceFailureException {
237: return openSession(sessionFactory);
238: }
239:
240: /**
241: * Get a Session for the SessionFactory that this filter uses.
242: * Note that this just applies in single session mode!
243: * <p>The default implementation delegates to the
244: * <code>SessionFactoryUtils.getSession</code> method and
245: * sets the <code>Session</code>'s flush mode to "NEVER".
246: * <p>Can be overridden in subclasses for creating a Session with a
247: * custom entity interceptor or JDBC exception translator.
248: * @param sessionFactory the SessionFactory that this filter uses
249: * @return the Session to use
250: * @throws DataAccessResourceFailureException if the Session could not be created
251: * @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory, boolean)
252: * @see net.sf.hibernate.FlushMode#NEVER
253: */
254: protected Session openSession(SessionFactory sessionFactory)
255: throws DataAccessResourceFailureException {
256: Session session = SessionFactoryUtils.getSession(
257: sessionFactory, true);
258: session.setFlushMode(FlushMode.NEVER);
259: return session;
260: }
261:
262: /**
263: * Close the given Session.
264: * Note that this just applies in single session mode!
265: * <p>Can be overridden in subclasses, e.g. for flushing the Session before
266: * closing it. See class-level javadoc for a discussion of flush handling.
267: * Note that you should also override getSession accordingly, to set
268: * the flush mode to something else than NEVER.
269: * @param session the Session used for filtering
270: * @param sessionFactory the SessionFactory that this filter uses
271: */
272: protected void closeSession(Session session,
273: SessionFactory sessionFactory) {
274: SessionFactoryUtils.closeSession(session);
275: }
276:
277: }
|