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 java.io.IOException;
020import javax.persistence.EntityManager;
021import javax.persistence.EntityManagerFactory;
022import javax.persistence.PersistenceException;
023import javax.servlet.FilterChain;
024import javax.servlet.ServletException;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027
028import org.springframework.dao.DataAccessResourceFailureException;
029import org.springframework.orm.jpa.EntityManagerFactoryUtils;
030import org.springframework.orm.jpa.EntityManagerHolder;
031import org.springframework.transaction.support.TransactionSynchronizationManager;
032import org.springframework.util.StringUtils;
033import org.springframework.web.context.WebApplicationContext;
034import org.springframework.web.context.request.async.WebAsyncManager;
035import org.springframework.web.context.request.async.WebAsyncUtils;
036import org.springframework.web.context.support.WebApplicationContextUtils;
037import org.springframework.web.filter.OncePerRequestFilter;
038
039/**
040 * Servlet Filter that binds a JPA EntityManager to the thread for the
041 * entire processing of the request. Intended for the "Open EntityManager in
042 * View" pattern, i.e. to allow for lazy loading in web views despite the
043 * original transactions already being completed.
044 *
045 * <p>This filter makes JPA EntityManagers available via the current thread,
046 * which will be autodetected by transaction managers. It is suitable for service
047 * layer transactions via {@link org.springframework.orm.jpa.JpaTransactionManager}
048 * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
049 * as for non-transactional read-only execution.
050 *
051 * <p>Looks up the EntityManagerFactory in Spring's root web application context.
052 * Supports an "entityManagerFactoryBeanName" filter init-param in {@code web.xml};
053 * the default bean name is "entityManagerFactory". As an alternative, the
054 * "persistenceUnitName" init-param allows for retrieval by logical unit name
055 * (as specified in {@code persistence.xml}).
056 *
057 * @author Juergen Hoeller
058 * @since 2.0
059 * @see OpenEntityManagerInViewInterceptor
060 * @see org.springframework.orm.jpa.JpaTransactionManager
061 * @see org.springframework.orm.jpa.SharedEntityManagerCreator
062 * @see org.springframework.transaction.support.TransactionSynchronizationManager
063 */
064public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
065
066        /**
067         * Default EntityManagerFactory bean name: "entityManagerFactory".
068         * Only applies when no "persistenceUnitName" param has been specified.
069         * @see #setEntityManagerFactoryBeanName
070         * @see #setPersistenceUnitName
071         */
072        public static final String DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME = "entityManagerFactory";
073
074
075        private String entityManagerFactoryBeanName;
076
077        private String persistenceUnitName;
078
079        private volatile EntityManagerFactory entityManagerFactory;
080
081
082        /**
083         * Set the bean name of the EntityManagerFactory to fetch from Spring's
084         * root application context.
085         * <p>Default is "entityManagerFactory". Note that this default only applies
086         * when no "persistenceUnitName" param has been specified.
087         * @see #setPersistenceUnitName
088         * @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
089         */
090        public void setEntityManagerFactoryBeanName(String entityManagerFactoryBeanName) {
091                this.entityManagerFactoryBeanName = entityManagerFactoryBeanName;
092        }
093
094        /**
095         * Return the bean name of the EntityManagerFactory to fetch from Spring's
096         * root application context.
097         */
098        protected String getEntityManagerFactoryBeanName() {
099                return this.entityManagerFactoryBeanName;
100        }
101
102        /**
103         * Set the name of the persistence unit to access the EntityManagerFactory for.
104         * <p>This is an alternative to specifying the EntityManagerFactory by bean name,
105         * resolving it by its persistence unit name instead. If no bean name and no persistence
106         * unit name have been specified, we'll check whether a bean exists for the default
107         * bean name "entityManagerFactory"; if not, a default EntityManagerFactory will
108         * be retrieved through finding a single unique bean of type EntityManagerFactory.
109         * @see #setEntityManagerFactoryBeanName
110         * @see #DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME
111         */
112        public void setPersistenceUnitName(String persistenceUnitName) {
113                this.persistenceUnitName = persistenceUnitName;
114        }
115
116        /**
117         * Return the name of the persistence unit to access the EntityManagerFactory for, if any.
118         */
119        protected String getPersistenceUnitName() {
120                return this.persistenceUnitName;
121        }
122
123
124        /**
125         * Returns "false" so that the filter may re-bind the opened {@code EntityManager}
126         * to each asynchronously dispatched thread and postpone closing it until the very
127         * last asynchronous dispatch.
128         */
129        @Override
130        protected boolean shouldNotFilterAsyncDispatch() {
131                return false;
132        }
133
134        /**
135         * Returns "false" so that the filter may provide an {@code EntityManager}
136         * to each error dispatches.
137         */
138        @Override
139        protected boolean shouldNotFilterErrorDispatch() {
140                return false;
141        }
142
143        @Override
144        protected void doFilterInternal(
145                        HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
146                        throws ServletException, IOException {
147
148                EntityManagerFactory emf = lookupEntityManagerFactory(request);
149                boolean participate = false;
150
151                WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
152                String key = getAlreadyFilteredAttributeName();
153
154                if (TransactionSynchronizationManager.hasResource(emf)) {
155                        // Do not modify the EntityManager: just set the participate flag.
156                        participate = true;
157                }
158                else {
159                        boolean isFirstRequest = !isAsyncDispatch(request);
160                        if (isFirstRequest || !applyEntityManagerBindingInterceptor(asyncManager, key)) {
161                                logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
162                                try {
163                                        EntityManager em = createEntityManager(emf);
164                                        EntityManagerHolder emHolder = new EntityManagerHolder(em);
165                                        TransactionSynchronizationManager.bindResource(emf, emHolder);
166
167                                        AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder);
168                                        asyncManager.registerCallableInterceptor(key, interceptor);
169                                        asyncManager.registerDeferredResultInterceptor(key, interceptor);
170                                }
171                                catch (PersistenceException ex) {
172                                        throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
173                                }
174                        }
175                }
176
177                try {
178                        filterChain.doFilter(request, response);
179                }
180
181                finally {
182                        if (!participate) {
183                                EntityManagerHolder emHolder = (EntityManagerHolder)
184                                                TransactionSynchronizationManager.unbindResource(emf);
185                                if (!isAsyncStarted(request)) {
186                                        logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
187                                        EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
188                                }
189                        }
190                }
191        }
192
193        /**
194         * Look up the EntityManagerFactory that this filter should use,
195         * taking the current HTTP request as argument.
196         * <p>The default implementation delegates to the {@code lookupEntityManagerFactory}
197         * without arguments, caching the EntityManagerFactory reference once obtained.
198         * @return the EntityManagerFactory to use
199         * @see #lookupEntityManagerFactory()
200         */
201        protected EntityManagerFactory lookupEntityManagerFactory(HttpServletRequest request) {
202                if (this.entityManagerFactory == null) {
203                        this.entityManagerFactory = lookupEntityManagerFactory();
204                }
205                return this.entityManagerFactory;
206        }
207
208        /**
209         * Look up the EntityManagerFactory that this filter should use.
210         * <p>The default implementation looks for a bean with the specified name
211         * in Spring's root application context.
212         * @return the EntityManagerFactory to use
213         * @see #getEntityManagerFactoryBeanName
214         */
215        protected EntityManagerFactory lookupEntityManagerFactory() {
216                WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
217                String emfBeanName = getEntityManagerFactoryBeanName();
218                String puName = getPersistenceUnitName();
219                if (StringUtils.hasLength(emfBeanName)) {
220                        return wac.getBean(emfBeanName, EntityManagerFactory.class);
221                }
222                else if (!StringUtils.hasLength(puName) && wac.containsBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME)) {
223                        return wac.getBean(DEFAULT_ENTITY_MANAGER_FACTORY_BEAN_NAME, EntityManagerFactory.class);
224                }
225                else {
226                        // Includes fallback search for single EntityManagerFactory bean by type.
227                        return EntityManagerFactoryUtils.findEntityManagerFactory(wac, puName);
228                }
229        }
230
231        /**
232         * Create a JPA EntityManager to be bound to a request.
233         * <p>Can be overridden in subclasses.
234         * @param emf the EntityManagerFactory to use
235         * @see javax.persistence.EntityManagerFactory#createEntityManager()
236         */
237        protected EntityManager createEntityManager(EntityManagerFactory emf) {
238                return emf.createEntityManager();
239        }
240
241        private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManager, String key) {
242                if (asyncManager.getCallableInterceptor(key) == null) {
243                        return false;
244                }
245                ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession();
246                return true;
247        }
248
249}