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