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.hibernate3.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 org.hibernate.FlushMode;
027: import org.hibernate.Session;
028: import org.hibernate.SessionFactory;
029:
030: import org.springframework.dao.DataAccessResourceFailureException;
031: import org.springframework.orm.hibernate3.SessionFactoryUtils;
032: import org.springframework.orm.hibernate3.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.hibernate3.HibernateTransactionManager}
047: * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
048: * as for non-transactional execution (if configured appropriately).
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: * If you intend to use this filter without transactions, consider changing
057: * the default flush mode (through the "flushMode" property).
058: *
059: * <p><b>WARNING:</b> Applying this filter to existing logic can cause issues that
060: * have not appeared before, through the use of a single Hibernate Session for the
061: * processing of an entire request. In particular, the reassociation of persistent
062: * objects with a Hibernate Session has to occur at the very beginning of request
063: * processing, to avoid clashes with already loaded instances of the same objects.
064: *
065: * <p>Alternatively, turn this filter into deferred close mode, by specifying
066: * "singleSession"="false": It will not use a single session per request then,
067: * but rather let each data access operation or transaction use its own session
068: * (like without Open Session in View). Each of those sessions will be registered
069: * for deferred close, though, actually processed at request completion.
070: *
071: * <p>A single session per request allows for most efficient first-level caching,
072: * but can cause side effects, for example on <code>saveOrUpdate</code> or when
073: * continuing after a rolled-back transaction. The deferred close strategy is as safe
074: * as no Open Session in View in that respect, while still allowing for lazy loading
075: * in views (but not providing a first-level cache for the entire request).
076: *
077: * <p>Looks up the SessionFactory in Spring's root web application context.
078: * Supports a "sessionFactoryBeanName" filter init-param in <code>web.xml</code>;
079: * the default bean name is "sessionFactory". Looks up the SessionFactory on each
080: * request, to avoid initialization order issues (when using ContextLoaderServlet,
081: * the root application context will get initialized <i>after</i> this filter).
082: *
083: * @author Juergen Hoeller
084: * @since 1.2
085: * @see #setSingleSession
086: * @see #setFlushMode
087: * @see #lookupSessionFactory
088: * @see OpenSessionInViewInterceptor
089: * @see org.springframework.orm.hibernate3.HibernateInterceptor
090: * @see org.springframework.orm.hibernate3.HibernateTransactionManager
091: * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
092: * @see org.springframework.transaction.support.TransactionSynchronizationManager
093: */
094: public class OpenSessionInViewFilter extends OncePerRequestFilter {
095:
096: public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";
097:
098: private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME;
099:
100: private boolean singleSession = true;
101:
102: private FlushMode flushMode = FlushMode.NEVER;
103:
104: /**
105: * Set the bean name of the SessionFactory to fetch from Spring's
106: * root application context. Default is "sessionFactory".
107: * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME
108: */
109: public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
110: this .sessionFactoryBeanName = sessionFactoryBeanName;
111: }
112:
113: /**
114: * Return the bean name of the SessionFactory to fetch from Spring's
115: * root application context.
116: */
117: protected String getSessionFactoryBeanName() {
118: return this .sessionFactoryBeanName;
119: }
120:
121: /**
122: * Set whether to use a single session for each request. Default is "true".
123: * <p>If set to "false", each data access operation or transaction will use
124: * its own session (like without Open Session in View). Each of those
125: * sessions will be registered for deferred close, though, actually
126: * processed at request completion.
127: * @see SessionFactoryUtils#initDeferredClose
128: * @see SessionFactoryUtils#processDeferredClose
129: */
130: public void setSingleSession(boolean singleSession) {
131: this .singleSession = singleSession;
132: }
133:
134: /**
135: * Return whether to use a single session for each request.
136: */
137: protected boolean isSingleSession() {
138: return this .singleSession;
139: }
140:
141: /**
142: * Specify the Hibernate FlushMode to apply to this filter's
143: * {@link org.hibernate.Session}. Only applied in single session mode.
144: * <p>Can be populated with the corresponding constant name in XML bean
145: * definitions: e.g. "AUTO".
146: * <p>The default is "NEVER". Specify "AUTO" if you intend to use
147: * this filter without service layer transactions.
148: * @see org.hibernate.Session#setFlushMode
149: * @see org.hibernate.FlushMode#NEVER
150: * @see org.hibernate.FlushMode#AUTO
151: */
152: public void setFlushMode(FlushMode flushMode) {
153: this .flushMode = flushMode;
154: }
155:
156: /**
157: * Return the Hibernate FlushMode that this filter applies to its
158: * {@link org.hibernate.Session} (in single session mode).
159: */
160: protected FlushMode getFlushMode() {
161: return this .flushMode;
162: }
163:
164: protected void doFilterInternal(HttpServletRequest request,
165: HttpServletResponse response, FilterChain filterChain)
166: throws ServletException, IOException {
167:
168: SessionFactory sessionFactory = lookupSessionFactory(request);
169: boolean participate = false;
170:
171: if (isSingleSession()) {
172: // single session mode
173: if (TransactionSynchronizationManager
174: .hasResource(sessionFactory)) {
175: // Do not modify the Session: just set the participate flag.
176: participate = true;
177: } else {
178: logger
179: .debug("Opening single Hibernate Session in OpenSessionInViewFilter");
180: Session session = getSession(sessionFactory);
181: TransactionSynchronizationManager.bindResource(
182: sessionFactory, new SessionHolder(session));
183: }
184: } else {
185: // deferred close mode
186: if (SessionFactoryUtils
187: .isDeferredCloseActive(sessionFactory)) {
188: // Do not modify deferred close: just set the participate flag.
189: participate = true;
190: } else {
191: SessionFactoryUtils.initDeferredClose(sessionFactory);
192: }
193: }
194:
195: try {
196: filterChain.doFilter(request, response);
197: }
198:
199: finally {
200: if (!participate) {
201: if (isSingleSession()) {
202: // single session mode
203: SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager
204: .unbindResource(sessionFactory);
205: logger
206: .debug("Closing single Hibernate Session in OpenSessionInViewFilter");
207: closeSession(sessionHolder.getSession(),
208: sessionFactory);
209: } else {
210: // deferred close mode
211: SessionFactoryUtils
212: .processDeferredClose(sessionFactory);
213: }
214: }
215: }
216: }
217:
218: /**
219: * Look up the SessionFactory that this filter should use,
220: * taking the current HTTP request as argument.
221: * <p>The default implementation delegates to the {@link #lookupSessionFactory()}
222: * variant without arguments.
223: * @param request the current request
224: * @return the SessionFactory to use
225: */
226: protected SessionFactory lookupSessionFactory(
227: HttpServletRequest request) {
228: return lookupSessionFactory();
229: }
230:
231: /**
232: * Look up the SessionFactory that this filter should use.
233: * <p>The default implementation looks for a bean with the specified name
234: * in Spring's root application context.
235: * @return the SessionFactory to use
236: * @see #getSessionFactoryBeanName
237: */
238: protected SessionFactory lookupSessionFactory() {
239: if (logger.isDebugEnabled()) {
240: logger.debug("Using SessionFactory '"
241: + getSessionFactoryBeanName()
242: + "' for OpenSessionInViewFilter");
243: }
244: WebApplicationContext wac = WebApplicationContextUtils
245: .getRequiredWebApplicationContext(getServletContext());
246: return (SessionFactory) wac.getBean(
247: getSessionFactoryBeanName(), SessionFactory.class);
248: }
249:
250: /**
251: * Get a Session for the SessionFactory that this filter uses.
252: * Note that this just applies in single session mode!
253: * <p>The default implementation delegates to the
254: * <code>SessionFactoryUtils.getSession</code> method and
255: * sets the <code>Session</code>'s flush mode to "NEVER".
256: * <p>Can be overridden in subclasses for creating a Session with a
257: * custom entity interceptor or JDBC exception translator.
258: * @param sessionFactory the SessionFactory that this filter uses
259: * @return the Session to use
260: * @throws DataAccessResourceFailureException if the Session could not be created
261: * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
262: * @see org.hibernate.FlushMode#NEVER
263: */
264: protected Session getSession(SessionFactory sessionFactory)
265: throws DataAccessResourceFailureException {
266: Session session = SessionFactoryUtils.getSession(
267: sessionFactory, true);
268: FlushMode flushMode = getFlushMode();
269: if (flushMode != null) {
270: session.setFlushMode(flushMode);
271: }
272: return session;
273: }
274:
275: /**
276: * Close the given Session.
277: * Note that this just applies in single session mode!
278: * <p>Can be overridden in subclasses, e.g. for flushing the Session before
279: * closing it. See class-level javadoc for a discussion of flush handling.
280: * Note that you should also override getSession accordingly, to set
281: * the flush mode to something else than NEVER.
282: * @param session the Session used for filtering
283: * @param sessionFactory the SessionFactory that this filter uses
284: */
285: protected void closeSession(Session session,
286: SessionFactory sessionFactory) {
287: SessionFactoryUtils.closeSession(session);
288: }
289:
290: }
|