001// Generated by delombok at Mon Oct 03 06:59:56 BST 2022 002/* 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 */ 020package org.apache.isis.persistence.jdo.spring.integration; 021 022import java.lang.reflect.InvocationHandler; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.lang.reflect.Proxy; 026import javax.jdo.PersistenceManager; 027import javax.jdo.PersistenceManagerFactory; 028import org.springframework.beans.factory.FactoryBean; 029import org.springframework.util.Assert; 030import org.springframework.util.ClassUtils; 031import org.apache.isis.core.metamodel.context.MetaModelContext; 032import lombok.NonNull; 033 034/** 035 * Proxy for a target JDO {@link javax.jdo.PersistenceManagerFactory}, 036 * returning the current thread-bound PersistenceManager (the Spring-managed 037 * transactional PersistenceManager or the single OpenPersistenceManagerInView 038 * PersistenceManager) on {@code getPersistenceManager()}, if any. 039 * 040 * <p>Essentially, {@code getPersistenceManager()} calls get seamlessly 041 * forwarded to {@link PersistenceManagerFactoryUtils#getPersistenceManager}. 042 * Furthermore, {@code PersistenceManager.close} calls get forwarded to 043 * {@link PersistenceManagerFactoryUtils#releasePersistenceManager}. 044 * 045 * <p>The main advantage of this proxy is that it allows DAOs to work with a 046 * plain JDO PersistenceManagerFactory reference, while still participating in 047 * Spring's (or a J2EE server's) resource and transaction management. DAOs will 048 * only rely on the JDO API in such a scenario, without any Spring dependencies. 049 * 050 * <p>Note that the behavior of this proxy matches the behavior that the JDO spec 051 * defines for a PersistenceManagerFactory as exposed by a JCA connector, when 052 * deployed in a J2EE server. Hence, DAOs could seamlessly switch between a JNDI 053 * PersistenceManagerFactory and this proxy for a local PersistenceManagerFactory, 054 * receiving the reference through Dependency Injection. This will work without 055 * any Spring API dependencies in the DAO code! 056 * 057 * <p>Of course, you can still access the target PersistenceManagerFactory 058 * even when your DAOs go through this proxy, by defining a bean reference 059 * that points directly at your target PersistenceManagerFactory bean. 060 * 061 * @see javax.jdo.PersistenceManagerFactory#getPersistenceManager() 062 * @see javax.jdo.PersistenceManager#close() 063 * @see PersistenceManagerFactoryUtils#getPersistenceManager 064 * @see PersistenceManagerFactoryUtils#releasePersistenceManager 065 */ 066public class TransactionAwarePersistenceManagerFactoryProxy implements FactoryBean<PersistenceManagerFactory> { 067 /** 068 * Key of the key-value pair into the map of the PM's user objects, 069 * we store the Isis MetaModelContext to. 070 * @see PersistenceManager#putUserObject(Object, Object) 071 */ 072 public static final String MMC_USER_OBJECT_KEY = "isis.mmc"; 073 private PersistenceManagerFactory target; 074 private boolean allowCreate = true; 075 private PersistenceManagerFactory proxy; 076 private final MetaModelContext metaModelContext; 077 078 public TransactionAwarePersistenceManagerFactoryProxy(@NonNull final MetaModelContext metaModelContext) { 079 if (metaModelContext == null) { 080 throw new java.lang.NullPointerException("metaModelContext is marked non-null but is null"); 081 } 082 this.metaModelContext = metaModelContext; 083 } 084 085 /** 086 * Set the target JDO PersistenceManagerFactory that this proxy should 087 * delegate to. This should be the raw PersistenceManagerFactory, as 088 * accessed by JdoTransactionManager. 089 * @see org.apache.isis.persistence.jdo.spring.integration.JdoTransactionManager 090 */ 091 public void setTargetPersistenceManagerFactory(final PersistenceManagerFactory target) { 092 Assert.notNull(target, "Target PersistenceManagerFactory must not be null"); 093 this.target = target; 094 Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(target.getClass(), target.getClass().getClassLoader()); 095 this.proxy = (PersistenceManagerFactory) Proxy.newProxyInstance(target.getClass().getClassLoader(), ifcs, new PersistenceManagerFactoryInvocationHandler()); 096 } 097 098 /** 099 * Return the target JDO PersistenceManagerFactory that this proxy delegates to. 100 */ 101 public PersistenceManagerFactory getTargetPersistenceManagerFactory() { 102 return this.target; 103 } 104 105 /** 106 * Set whether the PersistenceManagerFactory proxy is allowed to create 107 * a non-transactional PersistenceManager when no transactional 108 * PersistenceManager can be found for the current thread. 109 * <p>Default is "true". Can be turned off to enforce access to 110 * transactional PersistenceManagers, which safely allows for DAOs 111 * written to get a PersistenceManager without explicit closing 112 * (i.e. a {@code PersistenceManagerFactory.getPersistenceManager()} 113 * call without corresponding {@code PersistenceManager.close()} call). 114 * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean) 115 */ 116 public void setAllowCreate(final boolean allowCreate) { 117 this.allowCreate = allowCreate; 118 } 119 120 /** 121 * Return 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 */ 125 protected boolean isAllowCreate() { 126 return this.allowCreate; 127 } 128 129 @Override 130 public PersistenceManagerFactory getObject() { 131 return this.proxy; 132 } 133 134 @Override 135 public Class<? extends PersistenceManagerFactory> getObjectType() { 136 return PersistenceManagerFactory.class; 137 } 138 139 @Override 140 public boolean isSingleton() { 141 return true; 142 } 143 144 145 /** 146 * Invocation handler that delegates getPersistenceManager calls on the 147 * PersistenceManagerFactory proxy to PersistenceManagerFactoryUtils 148 * for being aware of thread-bound transactions. 149 */ 150 private class PersistenceManagerFactoryInvocationHandler implements InvocationHandler { 151 @Override 152 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 153 // Invocation on PersistenceManagerFactory interface coming in... 154 if (method.getName().equals("equals")) { 155 // Only consider equal when proxies are identical. 156 return (proxy == args[0]); 157 } else if (method.getName().equals("hashCode")) { 158 // Use hashCode of PersistenceManagerFactory proxy. 159 return System.identityHashCode(proxy); 160 } else if (method.getName().equals("getPersistenceManager")) { 161 PersistenceManagerFactory pmf = getTargetPersistenceManagerFactory(); 162 PersistenceManager pm = PersistenceManagerFactoryUtils.doGetPersistenceManager(pmf, isAllowCreate()); 163 pm.putUserObject(MMC_USER_OBJECT_KEY, metaModelContext); 164 Class<?>[] ifcs = ClassUtils.getAllInterfacesForClass(pm.getClass(), pm.getClass().getClassLoader()); 165 return Proxy.newProxyInstance(pm.getClass().getClassLoader(), ifcs, new PersistenceManagerInvocationHandler(pm, pmf)); 166 } 167 // Invoke method on target PersistenceManagerFactory. 168 try { 169 return method.invoke(getTargetPersistenceManagerFactory(), args); 170 } catch (InvocationTargetException ex) { 171 throw ex.getTargetException(); 172 } 173 } 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 static class PersistenceManagerInvocationHandler implements InvocationHandler { 182 private final PersistenceManager pm; 183 private final PersistenceManagerFactory persistenceManagerFactory; 184 185 public PersistenceManagerInvocationHandler(@NonNull final PersistenceManager pm, @NonNull final PersistenceManagerFactory pmf) { 186 if (pm == null) { 187 throw new java.lang.NullPointerException("pm is marked non-null but is null"); 188 } 189 if (pmf == null) { 190 throw new java.lang.NullPointerException("pmf is marked non-null but is null"); 191 } 192 this.pm = pm; 193 this.persistenceManagerFactory = pmf; 194 } 195 196 @Override 197 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 198 // Invocation on PersistenceManager interface coming in... 199 if (method.getName().equals("equals")) { 200 // Only consider equal when proxies are identical. 201 return (proxy == args[0]); 202 } else if (method.getName().equals("hashCode")) { 203 // Use hashCode of PersistenceManager proxy. 204 return System.identityHashCode(proxy); 205 } else if (method.getName().equals("close")) { 206 // Handle close method: only close if not within a transaction. 207 PersistenceManagerFactoryUtils.doReleasePersistenceManager(this.pm, this.persistenceManagerFactory); 208 return null; 209 } 210 // Invoke method on target PersistenceManager. 211 try { 212 return method.invoke(this.pm, args); 213 } catch (InvocationTargetException ex) { 214 throw ex.getTargetException(); 215 } 216 } 217 } 218 219 public PersistenceManagerFactory getPersistenceManagerFactory() { 220 return getObject(); 221 } 222}