uk.co.unclealex.persistence.jdo.spring.TransactionAwarePersistenceManagerFactoryProxy.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.unclealex.persistence.jdo.spring.TransactionAwarePersistenceManagerFactoryProxy.java

Source

/*
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.unclealex.persistence.jdo.spring;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.datanucleus.api.jdo.JDOPersistenceManagerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.orm.jdo.JdoTemplate;
import org.springframework.orm.jdo.PersistenceManagerFactoryUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * Note that this class has been altered so that the
 * {@link PersistenceManagerFactory} it creates returns concrete subclasses of
 * the {@link PersistenceManager} it returns and not just a class that
 * implements the {@link PersistenceManager} interface.
 * <p>
 * Proxy for a target JDO {@link javax.jdo.PersistenceManagerFactory}, returning
 * the current thread-bound PersistenceManager (the Spring-managed transactional
 * PersistenceManager or the single OpenPersistenceManagerInView
 * PersistenceManager) on <code>getPersistenceManager()</code>, if any.
 * </p>
 * 
 * <p>
 * Essentially, <code>getPersistenceManager()</code> calls get seamlessly
 * forwarded to {@link PersistenceManagerFactoryUtils#getPersistenceManager}.
 * Furthermore, <code>PersistenceManager.close</code> calls get forwarded to
 * {@link PersistenceManagerFactoryUtils#releasePersistenceManager}.
 * 
 * <p>
 * The main advantage of this proxy is that it allows DAOs to work with a plain
 * JDO PersistenceManagerFactory reference, while still participating in
 * Spring's (or a J2EE server's) resource and transaction management. DAOs will
 * only rely on the JDO API in such a scenario, without any Spring dependencies.
 * 
 * <p>
 * Note that the behavior of this proxy matches the behavior that the JDO spec
 * defines for a PersistenceManagerFactory as exposed by a JCA connector, when
 * deployed in a J2EE server. Hence, DAOs could seamlessly switch between a JNDI
 * PersistenceManagerFactory and this proxy for a local
 * PersistenceManagerFactory, receiving the reference through Dependency
 * Injection. This will work without any Spring API dependencies in the DAO
 * code!
 * 
 * <p>
 * It is usually preferable to write your JDO-based DAOs with Spring's
 * {@link JdoTemplate}, offering benefits such as consistent data access
 * exceptions instead of JDOExceptions at the DAO layer. However, Spring's
 * resource and transaction management (and Dependency Injection) will work for
 * DAOs written against the plain JDO API as well.
 * 
 * <p>
 * Of course, you can still access the target PersistenceManagerFactory even
 * when your DAOs go through this proxy, by defining a bean reference that
 * points directly at your target PersistenceManagerFactory bean.
 * 
 * @author Juergen Hoeller
 * @since 1.2
 * @see javax.jdo.PersistenceManagerFactory#getPersistenceManager()
 * @see javax.jdo.PersistenceManager#close()
 * @see PersistenceManagerFactoryUtils#getPersistenceManager
 * @see PersistenceManagerFactoryUtils#releasePersistenceManager
 */
public class TransactionAwarePersistenceManagerFactoryProxy implements FactoryBean<PersistenceManagerFactory> {

    private JDOPersistenceManagerFactory target;

    private boolean allowCreate = true;

    private PersistenceManagerFactory proxy;

    /**
     * Set the target JDO PersistenceManagerFactory that this proxy should
     * delegate to. This should be the raw PersistenceManagerFactory, as accessed
     * by JdoTransactionManager.
     * 
     * @see org.springframework.orm.jdo.JdoTransactionManager
     */
    public void setTargetPersistenceManagerFactory(JDOPersistenceManagerFactory target) {
        Assert.notNull(target, "Target PersistenceManagerFactory must not be null");
        this.target = target;
        Class[] ifcs = ClassUtils.getAllInterfacesForClass(target.getClass(), target.getClass().getClassLoader());
        this.proxy = (PersistenceManagerFactory) Proxy.newProxyInstance(target.getClass().getClassLoader(), ifcs,
                new PersistenceManagerFactoryInvocationHandler());
    }

    /**
     * Return the target JDO PersistenceManagerFactory that this proxy delegates
     * to.
     */
    public JDOPersistenceManagerFactory getTargetPersistenceManagerFactory() {
        return this.target;
    }

    /**
     * Set whether the PersistenceManagerFactory proxy is allowed to create a
     * non-transactional PersistenceManager when no transactional
     * PersistenceManager can be found for the current thread.
     * <p>
     * Default is "true". Can be turned off to enforce access to transactional
     * PersistenceManagers, which safely allows for DAOs written to get a
     * PersistenceManager without explicit closing (i.e. a
     * <code>PersistenceManagerFactory.getPersistenceManager()</code> call without
     * corresponding <code>PersistenceManager.close()</code> call).
     * 
     * @see PersistenceManagerFactoryUtils#getPersistenceManager(javax.jdo.PersistenceManagerFactory,
     *      boolean)
     */
    public void setAllowCreate(boolean allowCreate) {
        this.allowCreate = allowCreate;
    }

    /**
     * Return whether the PersistenceManagerFactory proxy is allowed to create a
     * non-transactional PersistenceManager when no transactional
     * PersistenceManager can be found for the current thread.
     */
    protected boolean isAllowCreate() {
        return this.allowCreate;
    }

    public PersistenceManagerFactory getObject() {
        return this.proxy;
    }

    public Class<? extends PersistenceManagerFactory> getObjectType() {
        return PersistenceManagerFactory.class;
    }

    public boolean isSingleton() {
        return true;
    }

    /**
     * Invocation handler that delegates getPersistenceManager calls on the
     * PersistenceManagerFactory proxy to PersistenceManagerFactoryUtils for being
     * aware of thread-bound transactions.
     */
    private class PersistenceManagerFactoryInvocationHandler implements InvocationHandler {

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Invocation on PersistenceManagerFactory interface coming in...

            if (method.getName().equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0]);
            } else if (method.getName().equals("hashCode")) {
                // Use hashCode of PersistenceManagerFactory proxy.
                return System.identityHashCode(proxy);
            } else if (method.getName().equals("getPersistenceManager")) {
                JDOPersistenceManagerFactory target = getTargetPersistenceManagerFactory();
                PersistenceManager pm = PersistenceManagerFactoryUtils.doGetPersistenceManager(target,
                        isAllowCreate());
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(pm.getClass());
                enhancer.setCallback(new PersistenceManagerMethodInterceptor(pm, target));
                return enhancer.create(new Class[] { target.getClass(), String.class, String.class },
                        new Object[] { target, target.getConnectionUserName(), target.getConnectionPassword() });
            }

            // Invoke method on target PersistenceManagerFactory.
            try {
                return method.invoke(getTargetPersistenceManagerFactory(), args);
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }

    /**
     * Invocation handler that delegates close calls on PersistenceManagers to
     * PersistenceManagerFactoryUtils for being aware of thread-bound
     * transactions.
     */
    private static class PersistenceManagerMethodInterceptor implements MethodInterceptor {

        private final PersistenceManager target;

        private final PersistenceManagerFactory persistenceManagerFactory;

        public PersistenceManagerMethodInterceptor(PersistenceManager target, PersistenceManagerFactory pmf) {
            this.target = target;
            this.persistenceManagerFactory = pmf;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            // Invocation on PersistenceManager interface coming in...

            if (method.getName().equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0]);
            } else if (method.getName().equals("hashCode")) {
                // Use hashCode of PersistenceManager proxy.
                return System.identityHashCode(proxy);
            } else if (method.getName().equals("close")) {
                // Handle close method: only close if not within a transaction.
                PersistenceManagerFactoryUtils.doReleasePersistenceManager(this.target,
                        this.persistenceManagerFactory);
                return null;
            }

            // Invoke method on target PersistenceManager.
            try {
                return method.invoke(this.target, args);
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }

}