org.synyx.hades.dao.orm.GenericDaoFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.synyx.hades.dao.orm.GenericDaoFactory.java

Source

/*
 * Copyright 2008-2010 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 org.synyx.hades.dao.orm;

import static org.springframework.util.ReflectionUtils.*;
import static org.synyx.hades.util.ClassUtils.*;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.Assert;
import org.synyx.hades.dao.GenericDao;
import org.synyx.hades.dao.query.HadesQuery;
import org.synyx.hades.dao.query.QueryExtractor;
import org.synyx.hades.dao.query.QueryLookupStrategy;
import org.synyx.hades.dao.query.QueryMethod;
import org.synyx.hades.util.ClassUtils;

/**
 * Factory bean to create instances of a given DAO interface. Creates a proxy
 * implementing the configured DAO interface and apply an advice handing the
 * control to the {@code FinderExecuterMethodInterceptor} when a method
 * beginning with the configured finder prefix is called. This defaults to
 * {@value GenericDaoSupport#DEFAULT_FINDER_PREFIX}. Furthermore finder
 * resolution can be configured by setting {@link #queryLookupStrategy} which
 * defaults to {@link QueryLookupStrategy#getDefault()}
 * 
 * @author Oliver Gierke
 */
public class GenericDaoFactory {

    private EntityManager entityManager;
    private QueryLookupStrategy queryLookupStrategy = QueryLookupStrategy.getDefault();

    private Map<Method, Method> methodCache = new ConcurrentHashMap<Method, Method>();
    private List<DaoProxyPostProcessor> postProcessors = new ArrayList<DaoProxyPostProcessor>();

    /**
     * Protected constructor to prevent simple construction from clients.
     */
    protected GenericDaoFactory() {

    }

    /**
     * Creates a new {@link GenericDaoFactory} with the given
     * {@link EntityManager}.
     * 
     * @param entityManager
     * @return
     */
    public static GenericDaoFactory create(EntityManager entityManager) {

        if (null == entityManager) {

            throw new IllegalArgumentException("EntityManager must not be null!");
        }

        GenericDaoFactory factory = new GenericDaoFactory();
        factory.setEntityManager(entityManager);

        return factory;
    }

    /**
     * Returns the {@link EntityManager}.
     * 
     * @return the entityManager
     */
    protected EntityManager getEntityManager() {

        return entityManager;
    }

    /**
     * Setter to inject entity manager.
     * 
     * @param entityManager the {@link EntityManager} to set
     */
    @PersistenceContext
    public void setEntityManager(final EntityManager entityManager) {

        this.entityManager = entityManager;
    }

    /**
     * Returns the configured {@link QueryLookupStrategy}.
     * 
     * @return the queryLookupStrategy
     */
    QueryLookupStrategy getQueryLookupStrategy() {

        return queryLookupStrategy;
    }

    /**
     * Sets the strategy of how to lookup a query to execute finders.
     * 
     * @param queryLookupStrategy the createFinderQueries to set
     */
    public void setQueryLookupStrategy(final QueryLookupStrategy queryLookupStrategy) {

        this.queryLookupStrategy = null == queryLookupStrategy ? QueryLookupStrategy.getDefault()
                : queryLookupStrategy;
    }

    /**
     * Adds {@link DaoProxyPostProcessor}s to the factory to allow manipulation
     * of the {@link ProxyFactory} before the proxy gets created. Note that the
     * {@link QueryExecuterMethodInterceptor} will be added to the proxy
     * <em>after</em> the {@link DaoProxyPostProcessor}s are considered.
     * 
     * @param processor
     */
    void addDaoProxyPostProcessor(DaoProxyPostProcessor processor) {

        Assert.notNull(processor);
        this.postProcessors.add(processor);
    }

    /**
     * Returns a DAO instance for the given interface.
     * 
     * @param <T>
     * @param daoInterface
     * @return
     */
    public <T extends GenericDao<?, ?>> T getDao(Class<T> daoInterface) {

        return getDao(daoInterface, null);
    }

    /**
     * Returns a DAO instance for the given interface backed by an instance
     * providing implementation logic for custom logic.
     * 
     * @param <T>
     * @param daoInterface
     * @param customDaoImplementation
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends GenericDao<?, ?>> T getDao(Class<T> daoInterface, Object customDaoImplementation) {

        validate(daoInterface, customDaoImplementation);

        try {
            // Instantiate generic dao
            @SuppressWarnings("rawtypes")
            GenericDaoSupport genericJpaDao = getDaoClass().newInstance();
            genericJpaDao.setEntityManager(entityManager);
            genericJpaDao.setDomainClass(ClassUtils.getDomainClass(daoInterface));
            genericJpaDao.validate();

            // Create proxy
            ProxyFactory result = new ProxyFactory();
            result.setTarget(genericJpaDao);
            result.setInterfaces(new Class[] { daoInterface });

            for (DaoProxyPostProcessor processor : postProcessors) {
                processor.postProcess(result);
            }

            result.addAdvice(
                    new QueryExecuterMethodInterceptor(daoInterface, customDaoImplementation, genericJpaDao));

            return (T) result.getProxy();
        } catch (InstantiationException e) {
            throw new IllegalStateException(e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Determines the base class for the DAO to be created by checking the
     * {@link EntityManager}'s concrete type. If no well known type can be
     * detected {@link #DEFAULT_DAO_CLASS} will be returned.
     * <p>
     * Protected to eventually customize base class - see #102 for details.
     * 
     * @return
     */
    @SuppressWarnings("rawtypes")
    protected Class<? extends GenericDaoSupport> getDaoClass() {

        return GenericJpaDao.class;
    }

    /**
     * Returns if the configured DAO interface has custom methods, that might
     * have to be delegated to a custom DAO implementation. This is used to
     * verify DAO configuration.
     * 
     * @return
     */
    private boolean hasCustomMethod(Class<? extends GenericDao<?, ?>> daoInterface) {

        boolean hasCustomMethod = false;

        // No detection required if no typing interface was configured
        if (ClassUtils.isGenericDaoInterface(daoInterface)) {
            return false;
        }

        for (Method method : daoInterface.getMethods()) {

            if (isCustomMethod(method, daoInterface) && !isBaseClassMethod(method, daoInterface)) {
                return true;
            }
        }

        return hasCustomMethod;
    }

    /**
     * Returns whether the given method is considered to be a DAO base class
     * method.
     * 
     * @param method
     * @return
     */
    private boolean isBaseClassMethod(Method method, Class<?> daoInterface) {

        Assert.notNull(method);

        if (method.getDeclaringClass().isAssignableFrom(getDaoClass())) {
            return true;
        }

        return !method.equals(getBaseClassMethod(method, daoInterface));
    }

    /**
     * Returns the base class method that is backing the given method. This can
     * be necessary if a DAO interface redeclares a method in {@link GenericDao}
     * (e.g. for transaction behaviour customization). Returns the method itself
     * if the base class does not implement the given method.
     * 
     * @param method
     * @return
     */
    private Method getBaseClassMethod(Method method, Class<?> daoInterface) {

        Assert.notNull(method);

        Method result = methodCache.get(method);

        if (null != result) {
            return result;
        }

        result = getBaseClassMethodFor(method, getDaoClass(), daoInterface);
        methodCache.put(method, result);

        return result;
    }

    /**
     * Returns whether the given method is a custom DAO method.
     * 
     * @param method
     * @param daoInterface
     * @return
     */
    private boolean isCustomMethod(Method method, Class<?> daoInterface) {

        Class<?> declaringClass = method.getDeclaringClass();

        boolean isQueryMethod = declaringClass.equals(daoInterface);
        boolean isHadesDaoInterface = ClassUtils.isGenericDaoInterface(declaringClass);
        boolean isBaseClassMethod = isBaseClassMethod(method, daoInterface);

        return !(isHadesDaoInterface || isBaseClassMethod || isQueryMethod);
    }

    /**
     * Returns all
     * 
     * @param daoInterface
     * @return
     */
    private Iterable<Method> getFinderMethods(Class<?> daoInterface) {

        Set<Method> result = new HashSet<Method>();

        for (Method method : daoInterface.getDeclaredMethods()) {
            if (!isCustomMethod(method, daoInterface) && !isBaseClassMethod(method, daoInterface)) {
                result.add(method);
            }
        }

        return result;
    }

    /**
     * Validates the given DAO interface.
     * 
     * @param daoInterface
     */
    private void validate(Class<?> daoInterface) {

        Assert.notNull(daoInterface);

        Class<?> domainClass = ClassUtils.getDomainClass(daoInterface);

        if (null == domainClass) {
            throw new IllegalArgumentException(
                    "Could not retrieve domain class from interface. Make sure it extends GenericDao.");
        }
    }

    /**
     * Validates the given DAO interface as well as the given custom
     * implementation.
     * 
     * @param daoInterface
     * @param customDaoImplementation
     */
    protected void validate(Class<? extends GenericDao<?, ?>> daoInterface, Object customDaoImplementation) {

        validate(daoInterface);

        if (null == customDaoImplementation && hasCustomMethod(daoInterface)) {

            throw new IllegalArgumentException(String.format(
                    "You have custom methods in %s but not provided a custom implementation!", daoInterface));
        }
    }

    /**
     * This {@code MethodInterceptor} intercepts calls to methods of the custom
     * implementation and delegates the to it if configured. Furthermore it
     * resolves method calls to finders and triggers execution of them. You can
     * rely on having a custom dao implementation instance set if this returns
     * true.
     * 
     * @author Oliver Gierke
     */
    private class QueryExecuterMethodInterceptor implements MethodInterceptor {

        private final Map<Method, HadesQuery> queries = new ConcurrentHashMap<Method, HadesQuery>();

        private final Object customDaoImplementation;
        private final Class<?> daoInterface;
        private final GenericDaoSupport<?> dao;

        /**
         * Creates a new {@link QueryExecuterMethodInterceptor}. Builds a model
         * of {@link QueryMethod}s to be invoked on execution of DAO interface
         * methods.
         */
        public QueryExecuterMethodInterceptor(Class<?> daoInterface, Object customDaoImplementation,
                GenericDaoSupport<?> dao) {

            this.daoInterface = daoInterface;
            this.customDaoImplementation = customDaoImplementation;
            this.dao = dao;

            for (Method method : getFinderMethods(daoInterface)) {

                Class<?> domainClass = ClassUtils.getDomainClass(daoInterface);
                QueryExtractor extractor = PersistenceProvider.fromEntityManager(entityManager);

                QueryMethod finder = new QueryMethod(method, domainClass, extractor);

                queries.put(method, queryLookupStrategy.resolveQuery(finder, entityManager));
            }
        }

        /*
         * (non-Javadoc)
         * 
         * @see
         * org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance
         * .intercept.MethodInvocation)
         */
        public Object invoke(final MethodInvocation invocation) throws Throwable {

            Method method = invocation.getMethod();

            if (isCustomMethodInvocation(invocation)) {

                makeAccessible(method);
                return executeMethodOn(customDaoImplementation, method, invocation.getArguments());
            }

            if (hasQueryFor(method)) {
                return queries.get(method).execute(invocation.getArguments());
            }

            // Lookup actual method as it might be redeclared in the interface
            // and we have to use the dao instance nevertheless
            // Method actualMethod = getBaseClassMethod(method);
            Method actualMethod = getBaseClassMethod(method, daoInterface);
            return executeMethodOn(dao, actualMethod, invocation.getArguments());
        }

        /**
         * Executes the given method on the given target. Correctly unwraps
         * exceptions not caused by the reflection magic.
         * 
         * @param target
         * @param method
         * @param parameters
         * @return
         * @throws Throwable
         */
        private Object executeMethodOn(Object target, Method method, Object[] parameters) throws Throwable {

            try {
                return method.invoke(target, parameters);
            } catch (Exception e) {
                ClassUtils.unwrapReflectionException(e);
            }

            throw new IllegalStateException("Should not occur!");
        }

        /**
         * Returns whether we know of a query to execute for the given
         * {@link Method};
         * 
         * @param method
         * @return
         */
        private boolean hasQueryFor(final Method method) {

            return queries.containsKey(method);
        }

        /**
         * Returns whether the given {@link MethodInvocation} is considered to
         * be targeted as an invocation of a custom method.
         * 
         * @param method
         * @return
         */
        private boolean isCustomMethodInvocation(final MethodInvocation invocation) {

            if (null == customDaoImplementation) {
                return false;
            }

            return isCustomMethod(invocation.getMethod(), daoInterface);
        }
    }
}