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}