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.jpa.support;
018
019import javax.persistence.EntityManager;
020import javax.persistence.PersistenceException;
021
022import org.springframework.dao.DataAccessException;
023import org.springframework.dao.DataAccessResourceFailureException;
024import org.springframework.orm.jpa.EntityManagerFactoryAccessor;
025import org.springframework.orm.jpa.EntityManagerFactoryUtils;
026import org.springframework.orm.jpa.EntityManagerHolder;
027import org.springframework.transaction.support.TransactionSynchronizationManager;
028import org.springframework.ui.ModelMap;
029import org.springframework.web.context.request.AsyncWebRequestInterceptor;
030import org.springframework.web.context.request.WebRequest;
031import org.springframework.web.context.request.async.WebAsyncManager;
032import org.springframework.web.context.request.async.WebAsyncUtils;
033
034/**
035 * Spring web request interceptor that binds a JPA EntityManager to the
036 * thread for the entire processing of the request. Intended for the "Open
037 * EntityManager in View" pattern, i.e. to allow for lazy loading in
038 * web views despite the original transactions already being completed.
039 *
040 * <p>This interceptor makes JPA EntityManagers available via the current thread,
041 * which will be autodetected by transaction managers. It is suitable for service
042 * layer transactions via {@link org.springframework.orm.jpa.JpaTransactionManager}
043 * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
044 * as for non-transactional read-only execution.
045 *
046 * <p>In contrast to {@link OpenEntityManagerInViewFilter}, this interceptor is set
047 * up in a Spring application context and can thus take advantage of bean wiring.
048 *
049 * @author Juergen Hoeller
050 * @since 2.0
051 * @see OpenEntityManagerInViewFilter
052 * @see org.springframework.orm.jpa.JpaTransactionManager
053 * @see org.springframework.orm.jpa.SharedEntityManagerCreator
054 * @see org.springframework.transaction.support.TransactionSynchronizationManager
055 */
056public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements AsyncWebRequestInterceptor {
057
058        /**
059         * Suffix that gets appended to the EntityManagerFactory toString
060         * representation for the "participate in existing entity manager
061         * handling" request attribute.
062         * @see #getParticipateAttributeName
063         */
064        public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
065
066
067        @Override
068        public void preHandle(WebRequest request) throws DataAccessException {
069                String participateAttributeName = getParticipateAttributeName();
070
071                WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
072                if (asyncManager.hasConcurrentResult()) {
073                        if (applyCallableInterceptor(asyncManager, participateAttributeName)) {
074                                return;
075                        }
076                }
077
078                if (TransactionSynchronizationManager.hasResource(getEntityManagerFactory())) {
079                        // Do not modify the EntityManager: just mark the request accordingly.
080                        Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
081                        int newCount = (count != null ? count + 1 : 1);
082                        request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
083                }
084                else {
085                        logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
086                        try {
087                                EntityManager em = createEntityManager();
088                                EntityManagerHolder emHolder = new EntityManagerHolder(em);
089                                TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), emHolder);
090
091                                AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(getEntityManagerFactory(), emHolder);
092                                asyncManager.registerCallableInterceptor(participateAttributeName, interceptor);
093                                asyncManager.registerDeferredResultInterceptor(participateAttributeName, interceptor);
094                        }
095                        catch (PersistenceException ex) {
096                                throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
097                        }
098                }
099        }
100
101        @Override
102        public void postHandle(WebRequest request, ModelMap model) {
103        }
104
105        @Override
106        public void afterCompletion(WebRequest request, Exception ex) throws DataAccessException {
107                if (!decrementParticipateCount(request)) {
108                        EntityManagerHolder emHolder = (EntityManagerHolder)
109                                        TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
110                        logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor");
111                        EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
112                }
113        }
114
115        private boolean decrementParticipateCount(WebRequest request) {
116                String participateAttributeName = getParticipateAttributeName();
117                Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
118                if (count == null) {
119                        return false;
120                }
121                // Do not modify the Session: just clear the marker.
122                if (count > 1) {
123                        request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST);
124                }
125                else {
126                        request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
127                }
128                return true;
129        }
130
131        @Override
132        public void afterConcurrentHandlingStarted(WebRequest request) {
133                if (!decrementParticipateCount(request)) {
134                        TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
135                }
136        }
137
138        /**
139         * Return the name of the request attribute that identifies that a request is
140         * already filtered. Default implementation takes the toString representation
141         * of the EntityManagerFactory instance and appends ".FILTERED".
142         * @see #PARTICIPATE_SUFFIX
143         */
144        protected String getParticipateAttributeName() {
145                return getEntityManagerFactory().toString() + PARTICIPATE_SUFFIX;
146        }
147
148
149        private boolean applyCallableInterceptor(WebAsyncManager asyncManager, String key) {
150                if (asyncManager.getCallableInterceptor(key) == null) {
151                        return false;
152                }
153                ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession();
154                return true;
155        }
156
157}