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