1 /*
2  * Copyright 2002-2008 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.orm.jpa.support;
18 
19 import javax.persistence.EntityManager;
20 import javax.persistence.PersistenceException;
21 
22 import org.springframework.dao.DataAccessException;
23 import org.springframework.dao.DataAccessResourceFailureException;
24 import org.springframework.orm.jpa.EntityManagerFactoryAccessor;
25 import org.springframework.orm.jpa.EntityManagerHolder;
26 import org.springframework.orm.jpa.EntityManagerFactoryUtils;
27 import org.springframework.transaction.support.TransactionSynchronizationManager;
28 import org.springframework.ui.ModelMap;
29 import org.springframework.web.context.request.WebRequest;
30 import org.springframework.web.context.request.WebRequestInterceptor;
31 
32 /**
33  * Spring web request interceptor that binds a JPA EntityManager to the
34  * thread for the entire processing of the request. Intended for the "Open
35  * EntityManager in View" pattern, i.e. to allow for lazy loading in
36  * web views despite the original transactions already being completed.
37  *
38  * <p>This interceptor makes JPA EntityManagers available via the current thread,
39  * which will be autodetected by transaction managers. It is suitable for service
40  * layer transactions via {@link org.springframework.orm.jpa.JpaTransactionManager}
41  * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
42  * as for non-transactional read-only execution.
43  *
44  * <p>In contrast to {@link OpenEntityManagerInViewFilter}, this interceptor
45  * is set up in a Spring application context and can thus take advantage of
46  * bean wiring. It inherits common JPA configuration properties from
47  * {@link org.springframework.orm.jpa.JpaAccessor}, to be configured in a
48  * bean definition.
49  *
50  * @author Juergen Hoeller
51  * @since 2.0
52  * @see OpenEntityManagerInViewFilter
53  * @see org.springframework.orm.jpa.JpaInterceptor
54  * @see org.springframework.orm.jpa.JpaTransactionManager
55  * @see org.springframework.orm.jpa.JpaTemplate#execute
56  * @see org.springframework.orm.jpa.SharedEntityManagerCreator
57  * @see org.springframework.transaction.support.TransactionSynchronizationManager
58  */
59 public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements WebRequestInterceptor {
60 
61 	/**
62 	 * Suffix that gets appended to the EntityManagerFactory toString
63 	 * representation for the "participate in existing entity manager
64 	 * handling" request attribute.
65 	 * @see #getParticipateAttributeName
66 	 */
67 	public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
68 
69 
preHandle(WebRequest request)70 	public void preHandle(WebRequest request) throws DataAccessException {
71 		if (TransactionSynchronizationManager.hasResource(getEntityManagerFactory())) {
72 			// do not modify the EntityManager: just mark the request accordingly
73 			String participateAttributeName = getParticipateAttributeName();
74 			Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
75 			int newCount = (count != null ? count + 1 : 1);
76 			request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST);
77 		}
78 		else {
79 			logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
80 			try {
81 				EntityManager em = createEntityManager();
82 				TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), new EntityManagerHolder(em));
83 			}
84 			catch (PersistenceException ex) {
85 				throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
86 			}
87 		}
88 	}
89 
postHandle(WebRequest request, ModelMap model)90 	public void postHandle(WebRequest request, ModelMap model) {
91 	}
92 
afterCompletion(WebRequest request, Exception ex)93 	public void afterCompletion(WebRequest request, Exception ex) throws DataAccessException {
94 		String participateAttributeName = getParticipateAttributeName();
95 		Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
96 		if (count != null) {
97 			// Do not modify the EntityManager: just clear the marker.
98 			if (count > 1) {
99 				request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST);
100 			}
101 			else {
102 				request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST);
103 			}
104 		}
105 		else {
106 			EntityManagerHolder emHolder = (EntityManagerHolder)
107 					TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
108 			logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor");
109 			EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
110 		}
111 	}
112 
113 	/**
114 	 * Return the name of the request attribute that identifies that a request is
115 	 * already filtered. Default implementation takes the toString representation
116 	 * of the EntityManagerFactory instance and appends ".FILTERED".
117 	 * @see #PARTICIPATE_SUFFIX
118 	 */
getParticipateAttributeName()119 	protected String getParticipateAttributeName() {
120 		return getEntityManagerFactory().toString() + PARTICIPATE_SUFFIX;
121 	}
122 
123 }
124