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