001/*
002 * Copyright 2002-2015 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 *      https://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
017package org.springframework.orm.hibernate3.support;
018
019import java.io.IOException;
020import javax.servlet.FilterChain;
021import javax.servlet.ServletException;
022import javax.servlet.http.HttpServletRequest;
023import javax.servlet.http.HttpServletResponse;
024
025import org.hibernate.FlushMode;
026import org.hibernate.Session;
027import org.hibernate.SessionFactory;
028
029import org.springframework.dao.DataAccessResourceFailureException;
030import org.springframework.transaction.support.TransactionSynchronizationManager;
031import org.springframework.util.Assert;
032import org.springframework.web.context.WebApplicationContext;
033import org.springframework.web.context.request.async.WebAsyncManager;
034import org.springframework.web.context.request.async.WebAsyncUtils;
035import org.springframework.web.context.support.WebApplicationContextUtils;
036import org.springframework.web.filter.OncePerRequestFilter;
037
038/**
039 * Servlet 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}. 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} during a read-write transaction, with the flush
055 * mode reset to {@code FlushMode.NEVER} 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} 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};
079 * the default bean name is "sessionFactory".
080 *
081 * @author Juergen Hoeller
082 * @since 1.2
083 * @see #setSingleSession
084 * @see #setFlushMode
085 * @see #lookupSessionFactory
086 * @see OpenSessionInViewInterceptor
087 * @see OpenSessionInterceptor
088 * @see org.springframework.orm.hibernate3.HibernateTransactionManager
089 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
090 * @see org.springframework.transaction.support.TransactionSynchronizationManager
091 * @see org.hibernate.SessionFactory#getCurrentSession()
092 * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
093 */
094@Deprecated
095public class OpenSessionInViewFilter extends OncePerRequestFilter {
096
097        public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";
098
099
100        private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME;
101
102        private boolean singleSession = true;
103
104        private FlushMode flushMode = FlushMode.MANUAL;
105
106
107        /**
108         * Set the bean name of the SessionFactory to fetch from Spring's
109         * root application context. Default is "sessionFactory".
110         * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME
111         */
112        public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
113                this.sessionFactoryBeanName = sessionFactoryBeanName;
114        }
115
116        /**
117         * Return the bean name of the SessionFactory to fetch from Spring's
118         * root application context.
119         */
120        protected String getSessionFactoryBeanName() {
121                return this.sessionFactoryBeanName;
122        }
123
124        /**
125         * Set whether to use a single session for each request. Default is "true".
126         * <p>If set to "false", each data access operation or transaction will use
127         * its own session (like without Open Session in View). Each of those
128         * sessions will be registered for deferred close, though, actually
129         * processed at request completion.
130         * @see org.springframework.orm.hibernate3.SessionFactoryUtils#initDeferredClose
131         * @see org.springframework.orm.hibernate3.SessionFactoryUtils#processDeferredClose
132         */
133        public void setSingleSession(boolean singleSession) {
134                this.singleSession = singleSession;
135        }
136
137        /**
138         * Return whether to use a single session for each request.
139         */
140        protected boolean isSingleSession() {
141                return this.singleSession;
142        }
143
144        /**
145         * Specify the Hibernate FlushMode to apply to this filter's
146         * {@link org.hibernate.Session}. Only applied in single session mode.
147         * <p>Can be populated with the corresponding constant name in XML bean
148         * definitions: e.g. "AUTO".
149         * <p>The default is "MANUAL". Specify "AUTO" if you intend to use
150         * this filter without service layer transactions.
151         * @see org.hibernate.Session#setFlushMode
152         * @see org.hibernate.FlushMode#MANUAL
153         * @see org.hibernate.FlushMode#AUTO
154         */
155        public void setFlushMode(FlushMode flushMode) {
156                this.flushMode = flushMode;
157        }
158
159        /**
160         * Return the Hibernate FlushMode that this filter applies to its
161         * {@link org.hibernate.Session} (in single session mode).
162         */
163        protected FlushMode getFlushMode() {
164                return this.flushMode;
165        }
166
167
168        /**
169         * Returns "false" so that the filter may re-bind the opened Hibernate
170         * {@code Session} to each asynchronously dispatched thread and postpone
171         * closing it until the very last asynchronous dispatch.
172         */
173        @Override
174        protected boolean shouldNotFilterAsyncDispatch() {
175                return false;
176        }
177
178        /**
179         * Returns "false" so that the filter may provide a Hibernate
180         * {@code Session} to each error dispatches.
181         */
182        @Override
183        protected boolean shouldNotFilterErrorDispatch() {
184                return false;
185        }
186
187        @Override
188        protected void doFilterInternal(
189                        HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
190                        throws ServletException, IOException {
191
192                SessionFactory sessionFactory = lookupSessionFactory(request);
193                boolean participate = false;
194
195                WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
196                String key = getAlreadyFilteredAttributeName();
197
198                if (isSingleSession()) {
199                        // single session mode
200                        if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
201                                // Do not modify the Session: just set the participate flag.
202                                participate = true;
203                        }
204                        else {
205                                boolean isFirstRequest = !isAsyncDispatch(request);
206                                if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
207                                        logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
208                                        Session session = getSession(sessionFactory);
209                                        org.springframework.orm.hibernate3.SessionHolder sessionHolder = new org.springframework.orm.hibernate3.SessionHolder(session);
210                                        TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
211
212                                        AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
213                                        asyncManager.registerCallableInterceptor(key, interceptor);
214                                        asyncManager.registerDeferredResultInterceptor(key, interceptor);
215                                }
216                        }
217                }
218                else {
219                        // deferred close mode
220                        Assert.state(!isAsyncStarted(request), "Deferred close mode is not supported on async dispatches");
221                        if (org.springframework.orm.hibernate3.SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
222                                // Do not modify deferred close: just set the participate flag.
223                                participate = true;
224                        }
225                        else {
226                                org.springframework.orm.hibernate3.SessionFactoryUtils.initDeferredClose(sessionFactory);
227                        }
228                }
229
230                try {
231                        filterChain.doFilter(request, response);
232                }
233                finally {
234                        if (!participate) {
235                                if (isSingleSession()) {
236                                        // single session mode
237                                        org.springframework.orm.hibernate3.SessionHolder sessionHolder =
238                                                        (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
239                                        if (!isAsyncStarted(request)) {
240                                                logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
241                                                closeSession(sessionHolder.getSession(), sessionFactory);
242                                        }
243                                }
244                                else {
245                                        // deferred close mode
246                                        org.springframework.orm.hibernate3.SessionFactoryUtils.processDeferredClose(sessionFactory);
247                                }
248                        }
249                }
250        }
251
252        /**
253         * Look up the SessionFactory that this filter should use,
254         * taking the current HTTP request as argument.
255         * <p>The default implementation delegates to the {@link #lookupSessionFactory()}
256         * variant without arguments.
257         * @param request the current request
258         * @return the SessionFactory to use
259         */
260        protected SessionFactory lookupSessionFactory(HttpServletRequest request) {
261                return lookupSessionFactory();
262        }
263
264        /**
265         * Look up the SessionFactory that this filter should use.
266         * <p>The default implementation looks for a bean with the specified name
267         * in Spring's root application context.
268         * @return the SessionFactory to use
269         * @see #getSessionFactoryBeanName
270         */
271        protected SessionFactory lookupSessionFactory() {
272                if (logger.isDebugEnabled()) {
273                        logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter");
274                }
275                WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
276                return wac.getBean(getSessionFactoryBeanName(), SessionFactory.class);
277        }
278
279        /**
280         * Get a Session for the SessionFactory that this filter uses.
281         * Note that this just applies in single session mode!
282         * <p>The default implementation delegates to the
283         * {@code SessionFactoryUtils.getSession} method and
284         * sets the {@code Session}'s flush mode to "MANUAL".
285         * <p>Can be overridden in subclasses for creating a Session with a
286         * custom entity interceptor or JDBC exception translator.
287         * @param sessionFactory the SessionFactory that this filter uses
288         * @return the Session to use
289         * @throws DataAccessResourceFailureException if the Session could not be created
290         * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
291         * @see org.hibernate.FlushMode#MANUAL
292         */
293        protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
294                Session session = org.springframework.orm.hibernate3.SessionFactoryUtils.getSession(sessionFactory, true);
295                FlushMode flushMode = getFlushMode();
296                if (flushMode != null) {
297                        session.setFlushMode(flushMode);
298                }
299                return session;
300        }
301
302        /**
303         * Close the given Session.
304         * Note that this just applies in single session mode!
305         * <p>Can be overridden in subclasses, e.g. for flushing the Session before
306         * closing it. See class-level javadoc for a discussion of flush handling.
307         * Note that you should also override getSession accordingly, to set
308         * the flush mode to something else than NEVER.
309         * @param session the Session used for filtering
310         * @param sessionFactory the SessionFactory that this filter uses
311         */
312        protected void closeSession(Session session, SessionFactory sessionFactory) {
313                org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(session);
314        }
315
316        private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, String key) {
317                if (asyncManager.getCallableInterceptor(key) == null) {
318                        return false;
319                }
320                ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession();
321                return true;
322        }
323
324}