1 /*
2  * Copyright 2002-2009 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.orm.jdo;
18 
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Proxy;
23 import javax.jdo.PersistenceManager;
24 import javax.jdo.PersistenceManagerFactory;
25 
26 import org.springframework.beans.factory.FactoryBean;
27 import org.springframework.util.Assert;
28 import org.springframework.util.ClassUtils;
29 
30 /**
31  * Proxy for a target JDO {@link javax.jdo.PersistenceManagerFactory},
32  * returning the current thread-bound PersistenceManager (the Spring-managed
33  * transactional PersistenceManager or the single OpenPersistenceManagerInView
34  * PersistenceManager) on <code>getPersistenceManager()</code>, if any.
35  *
36  * <p>Essentially, <code>getPersistenceManager()</code> calls get seamlessly
37  * forwarded to {@link PersistenceManagerFactoryUtils#getPersistenceManager}.
38  * Furthermore, <code>PersistenceManager.close</code> calls get forwarded to
39  * {@link PersistenceManagerFactoryUtils#releasePersistenceManager}.
40  *
41  * <p>The main advantage of this proxy is that it allows DAOs to work with a
42  * plain JDO PersistenceManagerFactory reference, while still participating in
43  * Spring's (or a J2EE server's) resource and transaction management. DAOs will
44  * only rely on the JDO API in such a scenario, without any Spring dependencies.
45  *
46  * <p>Note that the behavior of this proxy matches the behavior that the JDO spec
47  * defines for a PersistenceManagerFactory as exposed by a JCA connector, when
48  * deployed in a J2EE server. Hence, DAOs could seamlessly switch between a JNDI
49  * PersistenceManagerFactory and this proxy for a local PersistenceManagerFactory,
50  * receiving the reference through Dependency Injection. This will work without
51  * any Spring API dependencies in the DAO code!
52  *
53  * <p>It is usually preferable to write your JDO-based DAOs with Spring's
54  * {@link JdoTemplate}, offering benefits such as consistent data access
55  * exceptions instead of JDOExceptions at the DAO layer. However, Spring's
56  * resource and transaction management (and Dependency	Injection) will work
57  * for DAOs written against the plain JDO API as well.
58  *
59  * <p>Of course, you can still access the target PersistenceManagerFactory
60  * even when your DAOs go through this proxy, by defining a bean reference
61  * that points directly at your target PersistenceManagerFactory bean.
62  *
63  * @author Juergen Hoeller
64  * @since 1.2
65  * @see javax.jdo.PersistenceManagerFactory#getPersistenceManager()
66  * @see javax.jdo.PersistenceManager#close()
67  * @see PersistenceManagerFactoryUtils#getPersistenceManager
68  * @see PersistenceManagerFactoryUtils#releasePersistenceManager
69  */
70 public class TransactionAwarePersistenceManagerFactoryProxy implements FactoryBean<PersistenceManagerFactory> {
71 
72 	private PersistenceManagerFactory target;
73 
74 	private boolean allowCreate = true;
75 
76 	private PersistenceManagerFactory proxy;
77 
78 
79 	/**
80 	 * Set the target JDO PersistenceManagerFactory that this proxy should
81 	 * delegate to. This should be the raw PersistenceManagerFactory, as
82 	 * accessed by JdoTransactionManager.
83 	 * @see org.springframework.orm.jdo.JdoTransactionManager
84 	 */
setTargetPersistenceManagerFactory(PersistenceManagerFactory target)85 	public void setTargetPersistenceManagerFactory(PersistenceManagerFactory target) {
86 		Assert.notNull(target, "Target PersistenceManagerFactory must not be null");
87 		this.target = target;
88 		Class[] ifcs = ClassUtils.getAllInterfacesForClass(target.getClass(), target.getClass().getClassLoader());
89 		this.proxy = (PersistenceManagerFactory) Proxy.newProxyInstance(
90 				target.getClass().getClassLoader(), ifcs, new PersistenceManagerFactoryInvocationHandler());
91 	}
92 
93 	/**
94 	 * Return the target JDO PersistenceManagerFactory that this proxy delegates to.
95 	 */
getTargetPersistenceManagerFactory()96 	public PersistenceManagerFactory getTargetPersistenceManagerFactory() {
97 		return this.target;
98 	}
99 
100 	/**
101 	 * Set whether the PersistenceManagerFactory proxy is allowed to create
102 	 * a non-transactional PersistenceManager when no transactional
103 	 * PersistenceManager can be found for the current thread.
104 	 * <p>Default is "true". Can be turned off to enforce access to
105 	 * transactional PersistenceManagers, which safely allows for DAOs
106 	 * written to get a PersistenceManager without explicit closing
107 	 * (i.e. a <code>PersistenceManagerFactory.getPersistenceManager()</code>
108 	 * call without corresponding <code>PersistenceManager.close()</code> call).
109 	 * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
110 	 */
setAllowCreate(boolean allowCreate)111 	public void setAllowCreate(boolean allowCreate) {
112 		this.allowCreate = allowCreate;
113 	}
114 
115 	/**
116 	 * Return whether the PersistenceManagerFactory proxy is allowed to create
117 	 * a non-transactional PersistenceManager when no transactional
118 	 * PersistenceManager can be found for the current thread.
119 	 */
isAllowCreate()120 	protected boolean isAllowCreate() {
121 		return this.allowCreate;
122 	}
123 
124 
getObject()125 	public PersistenceManagerFactory getObject() {
126 		return this.proxy;
127 	}
128 
getObjectType()129 	public Class<? extends PersistenceManagerFactory> getObjectType() {
130 		return PersistenceManagerFactory.class;
131 	}
132 
isSingleton()133 	public boolean isSingleton() {
134 		return true;
135 	}
136 
137 
138 	/**
139 	 * Invocation handler that delegates getPersistenceManager calls on the
140 	 * PersistenceManagerFactory proxy to PersistenceManagerFactoryUtils
141 	 * for being aware of thread-bound transactions.
142 	 */
143 	private class PersistenceManagerFactoryInvocationHandler implements InvocationHandler {
144 
invoke(Object proxy, Method method, Object[] args)145 		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
146 			// Invocation on PersistenceManagerFactory interface coming in...
147 
148 			if (method.getName().equals("equals")) {
149 				// Only consider equal when proxies are identical.
150 				return (proxy == args[0]);
151 			}
152 			else if (method.getName().equals("hashCode")) {
153 				// Use hashCode of PersistenceManagerFactory proxy.
154 				return System.identityHashCode(proxy);
155 			}
156 			else if (method.getName().equals("getPersistenceManager")) {
157 				PersistenceManagerFactory target = getTargetPersistenceManagerFactory();
158 				PersistenceManager pm =
159 						PersistenceManagerFactoryUtils.doGetPersistenceManager(target, isAllowCreate());
160 				Class[] ifcs = ClassUtils.getAllInterfacesForClass(pm.getClass(), pm.getClass().getClassLoader());
161 				return Proxy.newProxyInstance(
162 						pm.getClass().getClassLoader(), ifcs, new PersistenceManagerInvocationHandler(pm, target));
163 			}
164 
165 			// Invoke method on target PersistenceManagerFactory.
166 			try {
167 				return method.invoke(getTargetPersistenceManagerFactory(), args);
168 			}
169 			catch (InvocationTargetException ex) {
170 				throw ex.getTargetException();
171 			}
172 		}
173 	}
174 
175 
176 	/**
177 	 * Invocation handler that delegates close calls on PersistenceManagers to
178 	 * PersistenceManagerFactoryUtils for being aware of thread-bound transactions.
179 	 */
180 	private static class PersistenceManagerInvocationHandler implements InvocationHandler {
181 
182 		private final PersistenceManager target;
183 
184 		private final PersistenceManagerFactory persistenceManagerFactory;
185 
PersistenceManagerInvocationHandler(PersistenceManager target, PersistenceManagerFactory pmf)186 		public PersistenceManagerInvocationHandler(PersistenceManager target, PersistenceManagerFactory pmf) {
187 			this.target = target;
188 			this.persistenceManagerFactory = pmf;
189 		}
190 
invoke(Object proxy, Method method, Object[] args)191 		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
192 			// Invocation on PersistenceManager interface coming in...
193 
194 			if (method.getName().equals("equals")) {
195 				// Only consider equal when proxies are identical.
196 				return (proxy == args[0]);
197 			}
198 			else if (method.getName().equals("hashCode")) {
199 				// Use hashCode of PersistenceManager proxy.
200 				return System.identityHashCode(proxy);
201 			}
202 			else if (method.getName().equals("close")) {
203 				// Handle close method: only close if not within a transaction.
204 				PersistenceManagerFactoryUtils.doReleasePersistenceManager(
205 						this.target, this.persistenceManagerFactory);
206 				return null;
207 			}
208 
209 			// Invoke method on target PersistenceManager.
210 			try {
211 				return method.invoke(this.target, args);
212 			}
213 			catch (InvocationTargetException ex) {
214 				throw ex.getTargetException();
215 			}
216 		}
217 	}
218 
219 }
220