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.lang.reflect.InvocationHandler;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Proxy;
025
026import javax.jdo.PersistenceManager;
027import javax.jdo.PersistenceManagerFactory;
028import javax.jdo.Query;
029
030import org.springframework.beans.factory.FactoryBean;
031import org.springframework.beans.factory.InitializingBean;
032import org.springframework.util.Assert;
033
034import org.apache.isis.persistence.jdo.spring.integration.DefaultJdoDialect;
035import org.apache.isis.persistence.jdo.spring.integration.JdoDialect;
036import org.apache.isis.persistence.jdo.spring.integration.PersistenceManagerFactoryUtils;
037
038/**
039 * Proxy that implements the {@link javax.jdo.PersistenceManager} interface,
040 * delegating to the current thread-bound PersistenceManager (the Spring-managed
041 * transactional PersistenceManager or the single OpenPersistenceManagerInView
042 * PersistenceManager, if any) on each invocation. This class makes such a
043 * Spring-style PersistenceManager proxy available for bean references.
044 *
045 * <p>The main advantage of this proxy is that it allows DAOs to work with a
046 * plain JDO PersistenceManager reference in JDO 3.0 style
047 * (see {@link javax.jdo.PersistenceManagerFactory#getPersistenceManagerProxy()}),
048 * while still participating in Spring's resource and transaction management.
049 *
050 * <p>The behavior of this proxy matches the behavior that the JDO 3.0 spec
051 * defines for a PersistenceManager proxy. Hence, DAOs could seamlessly switch
052 * between {@link StandardPersistenceManagerProxyBean} and this Spring-style proxy,
053 * receiving the reference through Dependency Injection. This will work without
054 * any Spring API dependencies in the DAO code!
055 *
056 * @see StandardPersistenceManagerProxyBean
057 * @see javax.jdo.PersistenceManagerFactory#getPersistenceManagerProxy()
058 * @see org.apache.isis.persistence.jdo.spring.integration.PersistenceManagerFactoryUtils#getPersistenceManager
059 * @see org.apache.isis.persistence.jdo.spring.integration.PersistenceManagerFactoryUtils#releasePersistenceManager
060 */
061public class SpringPersistenceManagerProxyBean implements FactoryBean<PersistenceManager>, InitializingBean {
062
063        private PersistenceManagerFactory persistenceManagerFactory;
064
065        private JdoDialect jdoDialect;
066
067        private Class<? extends PersistenceManager> persistenceManagerInterface = PersistenceManager.class;
068
069        private boolean allowCreate = true;
070
071        private PersistenceManager proxy;
072
073
074        /**
075         * Set the target PersistenceManagerFactory for this proxy.
076         */
077        public void setPersistenceManagerFactory(PersistenceManagerFactory persistenceManagerFactory) {
078                this.persistenceManagerFactory = persistenceManagerFactory;
079        }
080
081        /**
082         * Return the target PersistenceManagerFactory for this proxy.
083         */
084        protected PersistenceManagerFactory getPersistenceManagerFactory() {
085                return this.persistenceManagerFactory;
086        }
087
088        /**
089         * Set the JDO dialect to use for this proxy.
090         * <p>Default is a DefaultJdoDialect based on the PersistenceManagerFactory's
091         * underlying DataSource, if any.
092         */
093        public void setJdoDialect(JdoDialect jdoDialect) {
094                this.jdoDialect = jdoDialect;
095        }
096
097        /**
098         * Return the JDO dialect to use for this proxy.
099         */
100        protected JdoDialect getJdoDialect() {
101                return this.jdoDialect;
102        }
103
104        /**
105         * Specify the PersistenceManager interface to expose,
106         * possibly including vendor extensions.
107         * <p>Default is the standard {@code javax.jdo.PersistenceManager} interface.
108         */
109        public void setPersistenceManagerInterface(Class<? extends PersistenceManager> persistenceManagerInterface) {
110                this.persistenceManagerInterface = persistenceManagerInterface;
111                Assert.notNull(persistenceManagerInterface, "persistenceManagerInterface must not be null");
112                Assert.isAssignable(PersistenceManager.class, persistenceManagerInterface);
113        }
114
115        /**
116         * Return the PersistenceManager interface to expose.
117         */
118        protected Class<? extends PersistenceManager> getPersistenceManagerInterface() {
119                return this.persistenceManagerInterface;
120        }
121
122        /**
123         * Set whether the PersistenceManagerFactory proxy is allowed to create
124         * a non-transactional PersistenceManager when no transactional
125         * PersistenceManager can be found for the current thread.
126         * <p>Default is "true". Can be turned off to enforce access to
127         * transactional PersistenceManagers, which safely allows for DAOs
128         * written to get a PersistenceManager without explicit closing
129         * (i.e. a {@code PersistenceManagerFactory.getPersistenceManager()}
130         * call without corresponding {@code PersistenceManager.close()} call).
131         * @see org.apache.isis.persistence.jdo.spring.integration.PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
132         */
133        public void setAllowCreate(boolean allowCreate) {
134                this.allowCreate = allowCreate;
135        }
136
137        /**
138         * Return whether the PersistenceManagerFactory proxy is allowed to create
139         * a non-transactional PersistenceManager when no transactional
140         * PersistenceManager can be found for the current thread.
141         */
142        protected boolean isAllowCreate() {
143                return this.allowCreate;
144        }
145
146        @Override
147        public void afterPropertiesSet() {
148                if (getPersistenceManagerFactory() == null) {
149                        throw new IllegalArgumentException("Property 'persistenceManagerFactory' is required");
150                }
151                // Build default JdoDialect if none explicitly specified.
152                if (this.jdoDialect == null) {
153                        this.jdoDialect = new DefaultJdoDialect(getPersistenceManagerFactory().getConnectionFactory());
154                }
155                this.proxy = (PersistenceManager) Proxy.newProxyInstance(
156                                getPersistenceManagerFactory().getClass().getClassLoader(),
157                                new Class<?>[] {getPersistenceManagerInterface()}, new PersistenceManagerInvocationHandler());
158        }
159
160
161        @Override
162        public PersistenceManager getObject() {
163                return this.proxy;
164        }
165
166        @Override
167        public Class<? extends PersistenceManager> getObjectType() {
168                return getPersistenceManagerInterface();
169        }
170
171        @Override
172        public boolean isSingleton() {
173                return true;
174        }
175
176
177        /**
178         * Invocation handler that delegates close calls on PersistenceManagers to
179         * PersistenceManagerFactoryUtils for being aware of thread-bound transactions.
180         */
181        private class PersistenceManagerInvocationHandler implements InvocationHandler {
182
183                @Override
184                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
185                        // Invocation on PersistenceManager interface coming in...
186
187                        if (method.getName().equals("equals")) {
188                                // Only consider equal when proxies are identical.
189                                return (proxy == args[0]);
190                        }
191                        else if (method.getName().equals("hashCode")) {
192                                // Use hashCode of PersistenceManager proxy.
193                                return System.identityHashCode(proxy);
194                        }
195                        else if (method.getName().equals("toString")) {
196                                // Deliver toString without touching a target EntityManager.
197                                return "Spring PersistenceManager proxy for target factory [" + getPersistenceManagerFactory() + "]";
198                        }
199                        else if (method.getName().equals("getPersistenceManagerFactory")) {
200                                // Return PersistenceManagerFactory without creating a PersistenceManager.
201                                return getPersistenceManagerFactory();
202                        }
203                        else if (method.getName().equals("isClosed")) {
204                                // Proxy is always usable.
205                                return false;
206                        }
207                        else if (method.getName().equals("close")) {
208                                // Suppress close method.
209                                return null;
210                        }
211
212                        // Invoke method on target PersistenceManager.
213                        PersistenceManager pm = PersistenceManagerFactoryUtils.doGetPersistenceManager(
214                                        getPersistenceManagerFactory(), isAllowCreate());
215                        try {
216                                Object retVal = method.invoke(pm, args);
217                                if (retVal instanceof Query) {
218                                        PersistenceManagerFactoryUtils.applyTransactionTimeout(
219                                                        (Query<?>) retVal, getPersistenceManagerFactory());
220                                }
221                                return retVal;
222                        }
223                        catch (InvocationTargetException ex) {
224                                throw ex.getTargetException();
225                        }
226                        finally {
227                                PersistenceManagerFactoryUtils.doReleasePersistenceManager(pm, getPersistenceManagerFactory());
228                        }
229                }
230        }
231
232}