001/*
002 * Copyright 2002-2013 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.jdo;
018
019import java.lang.reflect.InvocationHandler;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.lang.reflect.Proxy;
023import javax.jdo.PersistenceManager;
024import javax.jdo.PersistenceManagerFactory;
025
026import org.springframework.beans.factory.FactoryBean;
027import org.springframework.util.Assert;
028import org.springframework.util.ClassUtils;
029
030/**
031 * Proxy for a target JDO {@link javax.jdo.PersistenceManagerFactory},
032 * returning the current thread-bound PersistenceManager (the Spring-managed
033 * transactional PersistenceManager or the single OpenPersistenceManagerInView
034 * PersistenceManager) on {@code getPersistenceManager()}, if any.
035 *
036 * <p>Essentially, {@code getPersistenceManager()} calls get seamlessly
037 * forwarded to {@link PersistenceManagerFactoryUtils#getPersistenceManager}.
038 * Furthermore, {@code PersistenceManager.close} calls get forwarded to
039 * {@link PersistenceManagerFactoryUtils#releasePersistenceManager}.
040 *
041 * <p>The main advantage of this proxy is that it allows DAOs to work with a
042 * plain JDO PersistenceManagerFactory reference, while still participating in
043 * Spring's (or a J2EE server's) resource and transaction management. DAOs will
044 * only rely on the JDO API in such a scenario, without any Spring dependencies.
045 *
046 * <p>Note that the behavior of this proxy matches the behavior that the JDO spec
047 * defines for a PersistenceManagerFactory as exposed by a JCA connector, when
048 * deployed in a J2EE server. Hence, DAOs could seamlessly switch between a JNDI
049 * PersistenceManagerFactory and this proxy for a local PersistenceManagerFactory,
050 * receiving the reference through Dependency Injection. This will work without
051 * any Spring API dependencies in the DAO code!
052 *
053 * <p>Of course, you can still access the target PersistenceManagerFactory
054 * even when your DAOs go through this proxy, by defining a bean reference
055 * that points directly at your target PersistenceManagerFactory bean.
056 *
057 * @author Juergen Hoeller
058 * @since 1.2
059 * @see javax.jdo.PersistenceManagerFactory#getPersistenceManager()
060 * @see javax.jdo.PersistenceManager#close()
061 * @see PersistenceManagerFactoryUtils#getPersistenceManager
062 * @see PersistenceManagerFactoryUtils#releasePersistenceManager
063 */
064public class TransactionAwarePersistenceManagerFactoryProxy implements FactoryBean<PersistenceManagerFactory> {
065
066        private PersistenceManagerFactory target;
067
068        private boolean allowCreate = true;
069
070        private PersistenceManagerFactory proxy;
071
072
073        /**
074         * Set the target JDO PersistenceManagerFactory that this proxy should
075         * delegate to. This should be the raw PersistenceManagerFactory, as
076         * accessed by JdoTransactionManager.
077         * @see org.springframework.orm.jdo.JdoTransactionManager
078         */
079        public void setTargetPersistenceManagerFactory(PersistenceManagerFactory target) {
080                Assert.notNull(target, "Target PersistenceManagerFactory must not be null");
081                this.target = target;
082                Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(target.getClass(), target.getClass().getClassLoader());
083                this.proxy = (PersistenceManagerFactory) Proxy.newProxyInstance(
084                                target.getClass().getClassLoader(), ifcs, new PersistenceManagerFactoryInvocationHandler());
085        }
086
087        /**
088         * Return the target JDO PersistenceManagerFactory that this proxy delegates to.
089         */
090        public PersistenceManagerFactory getTargetPersistenceManagerFactory() {
091                return this.target;
092        }
093
094        /**
095         * Set whether the PersistenceManagerFactory proxy is allowed to create
096         * a non-transactional PersistenceManager when no transactional
097         * PersistenceManager can be found for the current thread.
098         * <p>Default is "true". Can be turned off to enforce access to
099         * transactional PersistenceManagers, which safely allows for DAOs
100         * written to get a PersistenceManager without explicit closing
101         * (i.e. a {@code PersistenceManagerFactory.getPersistenceManager()}
102         * call without corresponding {@code PersistenceManager.close()} call).
103         * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
104         */
105        public void setAllowCreate(boolean allowCreate) {
106                this.allowCreate = allowCreate;
107        }
108
109        /**
110         * Return whether the PersistenceManagerFactory proxy is allowed to create
111         * a non-transactional PersistenceManager when no transactional
112         * PersistenceManager can be found for the current thread.
113         */
114        protected boolean isAllowCreate() {
115                return this.allowCreate;
116        }
117
118
119        @Override
120        public PersistenceManagerFactory getObject() {
121                return this.proxy;
122        }
123
124        @Override
125        public Class<? extends PersistenceManagerFactory> getObjectType() {
126                return PersistenceManagerFactory.class;
127        }
128
129        @Override
130        public boolean isSingleton() {
131                return true;
132        }
133
134
135        /**
136         * Invocation handler that delegates getPersistenceManager calls on the
137         * PersistenceManagerFactory proxy to PersistenceManagerFactoryUtils
138         * for being aware of thread-bound transactions.
139         */
140        private class PersistenceManagerFactoryInvocationHandler implements InvocationHandler {
141
142                @Override
143                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
144                        // Invocation on PersistenceManagerFactory interface coming in...
145
146                        if (method.getName().equals("equals")) {
147                                // Only consider equal when proxies are identical.
148                                return (proxy == args[0]);
149                        }
150                        else if (method.getName().equals("hashCode")) {
151                                // Use hashCode of PersistenceManagerFactory proxy.
152                                return System.identityHashCode(proxy);
153                        }
154                        else if (method.getName().equals("getPersistenceManager")) {
155                                PersistenceManagerFactory target = getTargetPersistenceManagerFactory();
156                                PersistenceManager pm =
157                                                PersistenceManagerFactoryUtils.doGetPersistenceManager(target, isAllowCreate());
158                                Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(pm.getClass(), pm.getClass().getClassLoader());
159                                return Proxy.newProxyInstance(
160                                                pm.getClass().getClassLoader(), ifcs, new PersistenceManagerInvocationHandler(pm, target));
161                        }
162
163                        // Invoke method on target PersistenceManagerFactory.
164                        try {
165                                return method.invoke(getTargetPersistenceManagerFactory(), args);
166                        }
167                        catch (InvocationTargetException ex) {
168                                throw ex.getTargetException();
169                        }
170                }
171        }
172
173
174        /**
175         * Invocation handler that delegates close calls on PersistenceManagers to
176         * PersistenceManagerFactoryUtils for being aware of thread-bound transactions.
177         */
178        private static class PersistenceManagerInvocationHandler implements InvocationHandler {
179
180                private final PersistenceManager target;
181
182                private final PersistenceManagerFactory persistenceManagerFactory;
183
184                public PersistenceManagerInvocationHandler(PersistenceManager target, PersistenceManagerFactory pmf) {
185                        this.target = target;
186                        this.persistenceManagerFactory = pmf;
187                }
188
189                @Override
190                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
191                        // Invocation on PersistenceManager interface coming in...
192
193                        if (method.getName().equals("equals")) {
194                                // Only consider equal when proxies are identical.
195                                return (proxy == args[0]);
196                        }
197                        else if (method.getName().equals("hashCode")) {
198                                // Use hashCode of PersistenceManager proxy.
199                                return System.identityHashCode(proxy);
200                        }
201                        else if (method.getName().equals("close")) {
202                                // Handle close method: only close if not within a transaction.
203                                PersistenceManagerFactoryUtils.doReleasePersistenceManager(
204                                                this.target, this.persistenceManagerFactory);
205                                return null;
206                        }
207
208                        // Invoke method on target PersistenceManager.
209                        try {
210                                return method.invoke(this.target, args);
211                        }
212                        catch (InvocationTargetException ex) {
213                                throw ex.getTargetException();
214                        }
215                }
216        }
217
218}