001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.orm.jdo;
018:
019: import java.lang.reflect.InvocationHandler;
020: import java.lang.reflect.InvocationTargetException;
021: import java.lang.reflect.Method;
022: import java.lang.reflect.Proxy;
023:
024: import javax.jdo.PersistenceManager;
025: import javax.jdo.PersistenceManagerFactory;
026:
027: import org.springframework.beans.factory.FactoryBean;
028: import 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 a the single OpenPersistenceManagerInView
034: * PersistenceManager) on <code>getPersistenceManager()</code>, if any.
035: *
036: * <p>Essentially, <code>getPersistenceManager()</code> calls get seamlessly
037: * forwarded to {@link PersistenceManagerFactoryUtils#getPersistenceManager}.
038: * Furthermore, <code>PersistenceManager.close</code> 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>It is usually preferable to write your JDO-based DAOs with Spring's
054: * {@link JdoTemplate}, offering benefits such as consistent data access
055: * exceptions instead of JDOExceptions at the DAO layer. However, Spring's
056: * resource and transaction management (and Dependency Injection) will work
057: * for DAOs written against the plain JDO API as well.
058: *
059: * <p>Of course, you can still access the target PersistenceManagerFactory
060: * even when your DAOs go through this proxy, by defining a bean reference
061: * that points directly at your target PersistenceManagerFactory bean.
062: *
063: * @author Juergen Hoeller
064: * @since 1.2
065: * @see javax.jdo.PersistenceManagerFactory#getPersistenceManager()
066: * @see javax.jdo.PersistenceManager#close()
067: * @see PersistenceManagerFactoryUtils#getPersistenceManager
068: * @see PersistenceManagerFactoryUtils#releasePersistenceManager
069: */
070: public class TransactionAwarePersistenceManagerFactoryProxy implements
071: FactoryBean {
072:
073: private PersistenceManagerFactory target;
074:
075: private boolean allowCreate = true;
076:
077: private PersistenceManagerFactory proxy;
078:
079: /**
080: * Set the target JDO PersistenceManagerFactory that this proxy should
081: * delegate to. This should be the raw PersistenceManagerFactory, as
082: * accessed by JdoTransactionManager.
083: * @see org.springframework.orm.jdo.JdoTransactionManager
084: */
085: public void setTargetPersistenceManagerFactory(
086: PersistenceManagerFactory target) {
087: this .target = target;
088: Class[] ifcs = ClassUtils.getAllInterfaces(target);
089: this .proxy = (PersistenceManagerFactory) Proxy
090: .newProxyInstance(getClass().getClassLoader(), ifcs,
091: new TransactionAwareFactoryInvocationHandler());
092: }
093:
094: /**
095: * Return the target JDO PersistenceManagerFactory that this proxy delegates to.
096: */
097: public PersistenceManagerFactory getTargetPersistenceManagerFactory() {
098: return this .target;
099: }
100:
101: /**
102: * Set whether the PersistenceManagerFactory proxy is allowed to create
103: * a non-transactional PersistenceManager when no transactional
104: * PersistenceManager can be found for the current thread.
105: * <p>Default is "true". Can be turned off to enforce access to
106: * transactional PersistenceManagers, which safely allows for DAOs
107: * written to get a PersistenceManager without explicit closing
108: * (i.e. a <code>PersistenceManagerFactory.getPersistenceManager()</code>
109: * call without corresponding <code>PersistenceManager.close()</code> call).
110: * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
111: */
112: public void setAllowCreate(boolean allowCreate) {
113: this .allowCreate = allowCreate;
114: }
115:
116: /**
117: * Return whether the PersistenceManagerFactory proxy is allowed to create
118: * a non-transactional PersistenceManager when no transactional
119: * PersistenceManager can be found for the current thread.
120: */
121: protected boolean isAllowCreate() {
122: return allowCreate;
123: }
124:
125: public Object getObject() {
126: return this .proxy;
127: }
128:
129: public Class getObjectType() {
130: return PersistenceManagerFactory.class;
131: }
132:
133: public boolean isSingleton() {
134: return true;
135: }
136:
137: /**
138: * Invocation handler that delegates getPersistenceManager calls on the
139: * PersistenceManagerFactory proxy to PersistenceManagerFactoryUtils
140: * for being aware of thread-bound transactions.
141: */
142: private class TransactionAwareFactoryInvocationHandler implements
143: InvocationHandler {
144:
145: public Object invoke(Object proxy, Method method, Object[] args)
146: throws Throwable {
147: // Invocation on PersistenceManagerFactory interface coming in...
148: PersistenceManagerFactory target = getTargetPersistenceManagerFactory();
149:
150: if (method.getName().equals("getPersistenceManager")) {
151: PersistenceManager pm = PersistenceManagerFactoryUtils
152: .doGetPersistenceManager(target,
153: isAllowCreate());
154: Class[] ifcs = ClassUtils.getAllInterfaces(pm);
155: return (PersistenceManager) Proxy.newProxyInstance(
156: getClass().getClassLoader(), ifcs,
157: new TransactionAwareInvocationHandler(pm,
158: target));
159: } else if (method.getName().equals("equals")) {
160: // Only consider equal when proxies are identical.
161: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
162: } else if (method.getName().equals("hashCode")) {
163: // Use hashCode of PersistenceManagerFactory proxy.
164: return new Integer(hashCode());
165: }
166:
167: // Invoke method on target PersistenceManagerFactory.
168: try {
169: return method.invoke(target, args);
170: } catch (InvocationTargetException ex) {
171: throw ex.getTargetException();
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 TransactionAwareInvocationHandler implements
181: InvocationHandler {
182:
183: private final PersistenceManager target;
184:
185: private final PersistenceManagerFactory persistenceManagerFactory;
186:
187: public TransactionAwareInvocationHandler(
188: PersistenceManager target, PersistenceManagerFactory pmf) {
189: this .target = target;
190: this .persistenceManagerFactory = pmf;
191: }
192:
193: public Object invoke(Object proxy, Method method, Object[] args)
194: throws Throwable {
195: // Invocation on PersistenceManager interface coming in...
196:
197: if (method.getName().equals("equals")) {
198: // Only consider equal when proxies are identical.
199: return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
200: } else if (method.getName().equals("hashCode")) {
201: // Use hashCode of PersistenceManager proxy.
202: return new Integer(hashCode());
203: } else if (method.getName().equals("close")) {
204: // Handle close method: only close if not within a transaction.
205: if (this .persistenceManagerFactory != null) {
206: PersistenceManagerFactoryUtils
207: .doReleasePersistenceManager(this .target,
208: this .persistenceManagerFactory);
209: }
210: return null;
211: }
212:
213: // Invoke method on target PersistenceManager.
214: try {
215: return method.invoke(this .target, args);
216: } catch (InvocationTargetException ex) {
217: throw ex.getTargetException();
218: }
219: }
220: }
221:
222: }
|