001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019package org.apache.isis.persistence.jdo.spring.support;
020
021import java.io.IOException;
022import javax.jdo.PersistenceManager;
023import javax.jdo.PersistenceManagerFactory;
024import javax.servlet.FilterChain;
025import javax.servlet.ServletException;
026import javax.servlet.http.HttpServletRequest;
027import javax.servlet.http.HttpServletResponse;
028
029import org.springframework.transaction.support.TransactionSynchronizationManager;
030import org.springframework.web.context.WebApplicationContext;
031import org.springframework.web.context.support.WebApplicationContextUtils;
032import org.springframework.web.filter.OncePerRequestFilter;
033
034import org.apache.isis.persistence.jdo.spring.integration.PersistenceManagerFactoryUtils;
035import org.apache.isis.persistence.jdo.spring.integration.PersistenceManagerHolder;
036
037/**
038 * Servlet Filter that binds a JDO PersistenceManager to the thread for the
039 * entire processing of the request. Intended for the "Open PersistenceManager in
040 * View" pattern, i.e. to allow for lazy loading in web views despite the
041 * original transactions already being completed.
042 *
043 * <p>This filter makes JDO PersistenceManagers available via the current thread,
044 * which will be autodetected by transaction managers. It is suitable for service
045 * layer transactions via {@link org.apache.isis.persistence.jdo.spring.integration.JdoTransactionManager}
046 * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
047 * as for non-transactional read-only execution.
048 *
049 * <p>Looks up the PersistenceManagerFactory in Spring's root web application context.
050 * Supports a "persistenceManagerFactoryBeanName" filter init-param in {@code web.xml};
051 * the default bean name is "persistenceManagerFactory".
052 *
053 * @see OpenPersistenceManagerInViewInterceptor
054 * @see org.apache.isis.persistence.jdo.spring.integration.JdoTransactionManager
055 * @see org.apache.isis.persistence.jdo.spring.integration.PersistenceManagerFactoryUtils#getPersistenceManager
056 * @see org.springframework.transaction.support.TransactionSynchronizationManager
057 */
058public class OpenPersistenceManagerInViewFilter extends OncePerRequestFilter {
059
060        public static final String DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME = "persistenceManagerFactory";
061
062        private String persistenceManagerFactoryBeanName = DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME;
063
064
065        /**
066         * Set the bean name of the PersistenceManagerFactory to fetch from Spring's
067         * root application context. Default is "persistenceManagerFactory".
068         * @see #DEFAULT_PERSISTENCE_MANAGER_FACTORY_BEAN_NAME
069         */
070        public void setPersistenceManagerFactoryBeanName(String persistenceManagerFactoryBeanName) {
071                this.persistenceManagerFactoryBeanName = persistenceManagerFactoryBeanName;
072        }
073
074        /**
075         * Return the bean name of the PersistenceManagerFactory to fetch from Spring's
076         * root application context.
077         */
078        protected String getPersistenceManagerFactoryBeanName() {
079                return this.persistenceManagerFactoryBeanName;
080        }
081
082
083        /**
084         * Returns "false" so that the filter may re-bind the opened {@code PersistenceManager}
085         * to each asynchronously dispatched thread and postpone closing it until the very
086         * last asynchronous dispatch.
087         */
088        @Override
089        protected boolean shouldNotFilterAsyncDispatch() {
090                return false;
091        }
092
093        /**
094         * Returns "false" so that the filter may provide an {@code PersistenceManager}
095         * to each error dispatches.
096         */
097        @Override
098        protected boolean shouldNotFilterErrorDispatch() {
099                return false;
100        }
101
102        @Override
103        protected void doFilterInternal(
104                        HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
105                        throws ServletException, IOException {
106
107                PersistenceManagerFactory pmf = lookupPersistenceManagerFactory(request);
108                boolean participate = false;
109
110                if (TransactionSynchronizationManager.hasResource(pmf)) {
111                        // Do not modify the PersistenceManager: just set the participate flag.
112                        participate = true;
113                }
114                else {
115                        logger.debug("Opening JDO PersistenceManager in OpenPersistenceManagerInViewFilter");
116                        PersistenceManager pm = PersistenceManagerFactoryUtils.getPersistenceManager(pmf, true);
117                        TransactionSynchronizationManager.bindResource(pmf, new PersistenceManagerHolder(pm));
118                }
119
120                try {
121                        filterChain.doFilter(request, response);
122                }
123
124                finally {
125                        if (!participate) {
126                                PersistenceManagerHolder pmHolder = (PersistenceManagerHolder)
127                                                TransactionSynchronizationManager.unbindResource(pmf);
128                                logger.debug("Closing JDO PersistenceManager in OpenPersistenceManagerInViewFilter");
129                                PersistenceManagerFactoryUtils.releasePersistenceManager(pmHolder.getPersistenceManager(), pmf);
130                        }
131                }
132        }
133
134        /**
135         * Look up the PersistenceManagerFactory that this filter should use,
136         * taking the current HTTP request as argument.
137         * <p>Default implementation delegates to the {@code lookupPersistenceManagerFactory}
138         * without arguments.
139         * @return the PersistenceManagerFactory to use
140         * @see #lookupPersistenceManagerFactory()
141         */
142        protected PersistenceManagerFactory lookupPersistenceManagerFactory(HttpServletRequest request) {
143                return lookupPersistenceManagerFactory();
144        }
145
146        /**
147         * Look up the PersistenceManagerFactory that this filter should use.
148         * The default implementation looks for a bean with the specified name
149         * in Spring's root application context.
150         * @return the PersistenceManagerFactory to use
151         * @see #getPersistenceManagerFactoryBeanName
152         */
153        protected PersistenceManagerFactory lookupPersistenceManagerFactory() {
154                if (logger.isDebugEnabled()) {
155                        logger.debug("Using PersistenceManagerFactory '" + getPersistenceManagerFactoryBeanName() +
156                                        "' for OpenPersistenceManagerInViewFilter");
157                }
158                WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
159                return wac.getBean(getPersistenceManagerFactoryBeanName(), PersistenceManagerFactory.class);
160        }
161
162}