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