001/*
002 * Copyright 2002-2014 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 org.hibernate.HibernateException;
020import org.hibernate.Session;
021
022import org.springframework.dao.DataAccessException;
023import org.springframework.transaction.support.TransactionSynchronizationManager;
024import org.springframework.ui.ModelMap;
025import org.springframework.web.context.request.AsyncWebRequestInterceptor;
026import org.springframework.web.context.request.WebRequest;
027import org.springframework.web.context.request.async.WebAsyncManager;
028import org.springframework.web.context.request.async.WebAsyncUtils;
029
030/**
031 * Spring web request interceptor that binds a Hibernate {@code Session} to the
032 * thread for the entire processing of the request.
033 *
034 * <p>This class is a concrete expression of the "Open Session in View" pattern, which
035 * is a pattern that allows for the lazy loading of associations in web views despite
036 * the original transactions already being completed.
037 *
038 * <p>This interceptor makes Hibernate {@code Sessions} available via the current
039 * thread, which will be autodetected by transaction managers. It is suitable for
040 * service layer transactions via
041 * {@link org.springframework.orm.hibernate3.HibernateTransactionManager} or
042 * {@link org.springframework.transaction.jta.JtaTransactionManager} as well as for
043 * non-transactional execution (if configured appropriately).
044 *
045 * <p><b>NOTE</b>: This interceptor will by default <i>not</i> flush the Hibernate
046 * {@code Session}, with the flush mode being set to {@code FlushMode.NEVER}.
047 * It assumes that it will be used in combination with service layer transactions
048 * that handle the flushing: the active transaction manager will temporarily change
049 * the flush mode to {@code FlushMode.AUTO} during a read-write transaction,
050 * with the flush mode reset to {@code FlushMode.NEVER} at the end of each
051 * transaction. If you intend to use this interceptor without transactions, consider
052 * changing the default flush mode (through the
053 * {@link #setFlushMode(int) "flushMode"} property).
054 *
055 * <p>In contrast to {@link OpenSessionInViewFilter}, this interceptor is
056 * configured in a Spring application context and can thus take advantage of bean
057 * wiring. It inherits common Hibernate configuration properties from
058 * {@link org.springframework.orm.hibernate3.HibernateAccessor},
059 * to be configured in a bean definition.
060 *
061 * <p><b>WARNING:</b> Applying this interceptor to existing logic can cause issues
062 * that have not appeared before, through the use of a single Hibernate
063 * {@code Session} for the processing of an entire request. In particular, the
064 * reassociation of persistent objects with a Hibernate {@code Session} has to
065 * occur at the very beginning of request processing, to avoid clashes with already
066 * loaded instances of the same objects.
067 *
068 * <p>Alternatively, turn this interceptor into deferred close mode, by specifying
069 * "singleSession"="false": It will not use a single session per request then,
070 * but rather let each data access operation or transaction use its own session
071 * (as would be the case without Open Session in View). Each of those sessions will
072 * be registered for deferred close though, which will actually be processed at
073 * request completion.
074 *
075 * <p>A single session per request allows for the most efficient first-level caching,
076 * but can cause side effects, for example on {@code saveOrUpdate} or when
077 * continuing after a rolled-back transaction. The deferred close strategy is as safe
078 * as no Open Session in View in that respect, while still allowing for lazy loading
079 * in views (but not providing a first-level cache for the entire request).
080 *
081 * @author Juergen Hoeller
082 * @since 1.2
083 * @see #setSingleSession
084 * @see #setFlushMode
085 * @see OpenSessionInViewFilter
086 * @see OpenSessionInterceptor
087 * @see org.springframework.orm.hibernate3.HibernateTransactionManager
088 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
089 * @see org.springframework.transaction.support.TransactionSynchronizationManager
090 * @see org.hibernate.SessionFactory#getCurrentSession()
091 * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x
092 */
093@Deprecated
094public class OpenSessionInViewInterceptor extends org.springframework.orm.hibernate3.HibernateAccessor implements AsyncWebRequestInterceptor {
095
096        /**
097         * Suffix that gets appended to the {@code SessionFactory}
098         * {@code toString()} representation for the "participate in existing
099         * session handling" request attribute.
100         * @see #getParticipateAttributeName
101         */
102        public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
103
104
105        private boolean singleSession = true;
106
107
108        /**
109         * Create a new {@code OpenSessionInViewInterceptor},
110         * turning the default flushMode to {@code FLUSH_NEVER}.
111         * @see #setFlushMode
112         */
113        public OpenSessionInViewInterceptor() {
114                setFlushMode(FLUSH_NEVER);
115        }
116
117        /**
118         * Set whether to use a single session for each request. Default is "true".
119         * <p>If set to false, each data access operation or transaction will use
120         * its own session (like without Open Session in View). Each of those
121         * sessions will be registered for deferred close, though, actually
122         * processed at request completion.
123         * @see org.springframework.orm.hibernate3.SessionFactoryUtils#initDeferredClose
124         * @see org.springframework.orm.hibernate3.SessionFactoryUtils#processDeferredClose
125         */
126        public void setSingleSession(boolean singleSession) {
127                this.singleSession = singleSession;
128        }
129
130        /**
131         * Return whether to use a single session for each request.
132         */
133        protected boolean isSingleSession() {
134                return singleSession;
135        }
136
137
138        /**
139         * Open a new Hibernate {@code Session} according to the settings of this
140         * {@code HibernateAccessor} and bind it to the thread via the
141         * {@link TransactionSynchronizationManager}.
142         * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
143         */
144        @Override
145        public void preHandle(WebRequest request) throws DataAccessException {
146                String participateAttributeName = getParticipateAttributeName();
147
148                WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
149                if (asyncManager.hasConcurrentResult()) {
150                        if (applySessionBindingInterceptor(asyncManager, participateAttributeName)) {
151                                return;
152                        }
153                }
154
155                if ((isSingleSession() && TransactionSynchronizationManager.hasResource(getSessionFactory())) ||
156                                org.springframework.orm.hibernate3.SessionFactoryUtils.isDeferredCloseActive(getSessionFactory())) {
157                        // Do not modify the Session: just mark the request accordingly.
158                        Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
159                        int newCount = (count != null ? count + 1 : 1);
160                        request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
161                }
162                else {
163                        if (isSingleSession()) {
164                                // single session mode
165                                logger.debug("Opening single Hibernate Session in OpenSessionInViewInterceptor");
166                                Session session = org.springframework.orm.hibernate3.SessionFactoryUtils.getSession(
167                                                getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());
168                                applyFlushMode(session, false);
169                                org.springframework.orm.hibernate3.SessionHolder sessionHolder = new org.springframework.orm.hibernate3.SessionHolder(session);
170                                TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
171
172                                AsyncRequestInterceptor asyncRequestInterceptor =
173                                                new AsyncRequestInterceptor(getSessionFactory(), sessionHolder);
174                                asyncManager.registerCallableInterceptor(participateAttributeName, asyncRequestInterceptor);
175                                asyncManager.registerDeferredResultInterceptor(participateAttributeName, asyncRequestInterceptor);
176                        }
177                        else {
178                                // deferred close mode
179                                org.springframework.orm.hibernate3.SessionFactoryUtils.initDeferredClose(getSessionFactory());
180                        }
181                }
182        }
183
184        /**
185         * Flush the Hibernate {@code Session} before view rendering, if necessary.
186         * <p>Note that this just applies in {@link #isSingleSession() single session mode}!
187         * <p>The default is {@code FLUSH_NEVER} to avoid this extra flushing,
188         * assuming that service layer transactions have flushed their changes on commit.
189         * @see #setFlushMode
190         */
191        @Override
192        public void postHandle(WebRequest request, ModelMap model) throws DataAccessException {
193                if (isSingleSession()) {
194                        // Only potentially flush in single session mode.
195                        org.springframework.orm.hibernate3.SessionHolder sessionHolder =
196                                        (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
197                        logger.debug("Flushing single Hibernate Session in OpenSessionInViewInterceptor");
198                        try {
199                                flushIfNecessary(sessionHolder.getSession(), false);
200                        }
201                        catch (HibernateException ex) {
202                                throw convertHibernateAccessException(ex);
203                        }
204                }
205        }
206
207        /**
208         * Unbind the Hibernate {@code Session} from the thread and close it (in
209         * single session mode), or process deferred close for all sessions that have
210         * been opened during the current request (in deferred close mode).
211         * @see org.springframework.transaction.support.TransactionSynchronizationManager
212         */
213        @Override
214        public void afterCompletion(WebRequest request, Exception ex) throws DataAccessException {
215                if (!decrementParticipateCount(request)) {
216                        if (isSingleSession()) {
217                                // single session mode
218                                org.springframework.orm.hibernate3.SessionHolder sessionHolder =
219                                                (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
220                                logger.debug("Closing single Hibernate Session in OpenSessionInViewInterceptor");
221                                org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(sessionHolder.getSession());
222                        }
223                        else {
224                                // deferred close mode
225                                org.springframework.orm.hibernate3.SessionFactoryUtils.processDeferredClose(getSessionFactory());
226                        }
227                }
228        }
229
230        @Override
231        public void afterConcurrentHandlingStarted(WebRequest request) {
232                if (!decrementParticipateCount(request)) {
233                        if (isSingleSession()) {
234                                TransactionSynchronizationManager.unbindResource(getSessionFactory());
235                        }
236                        else {
237                                throw new IllegalStateException("Deferred close mode is not supported with async requests.");
238                        }
239
240                }
241        }
242
243        private boolean decrementParticipateCount(WebRequest request) {
244                String participateAttributeName = getParticipateAttributeName();
245                Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
246                if (count == null) {
247                        return false;
248                }
249                // Do not modify the Session: just clear the marker.
250                if (count > 1) {
251                        request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST);
252                }
253                else {
254                        request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
255                }
256                return true;
257        }
258
259        /**
260         * Return the name of the request attribute that identifies that a request is
261         * already intercepted.
262         * <p>The default implementation takes the {@code toString()} representation
263         * of the {@code SessionFactory} instance and appends {@link #PARTICIPATE_SUFFIX}.
264         */
265        protected String getParticipateAttributeName() {
266                return getSessionFactory().toString() + PARTICIPATE_SUFFIX;
267        }
268
269        private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, String key) {
270                if (asyncManager.getCallableInterceptor(key) == null) {
271                        return false;
272                }
273                ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession();
274                return true;
275        }
276
277}