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}