org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil.java

Source

/* Copyright 2004-2005 Graeme Rocher
 *
 * 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.codehaus.groovy.grails.orm.hibernate.cfg;

import grails.util.CollectionUtils;
import grails.util.GrailsWebUtil;
import groovy.lang.GroovyObject;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;

import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler;
import org.codehaus.groovy.grails.commons.GrailsApplication;
import org.codehaus.groovy.grails.commons.GrailsClass;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
import org.codehaus.groovy.grails.commons.GrailsDomainClass;
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
import org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateDomainClass;
import org.codehaus.groovy.grails.orm.hibernate.proxy.GroovyAwareJavassistProxyFactory;
import org.codehaus.groovy.grails.orm.hibernate.proxy.HibernateProxyHandler;
import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.FetchMode;
import org.hibernate.FlushMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.Status;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.property.Getter;
import org.hibernate.property.Setter;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.type.CompositeType;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;

/**
 * Utility methods for configuring Hibernate inside Grails.
 *
 * @author Graeme Rocher
 * @since 0.4
 */
public class GrailsHibernateUtil {
    private static final Log LOG = LogFactory.getLog(GrailsHibernateUtil.class);

    private static final String DYNAMIC_FILTER_ENABLER = "dynamicFilterEnabler";

    public static final String ARGUMENT_FETCH_SIZE = "fetchSize";
    public static final String ARGUMENT_TIMEOUT = "timeout";
    public static final String ARGUMENT_READ_ONLY = "readOnly";
    public static final String ARGUMENT_FLUSH_MODE = "flushMode";
    public static final String ARGUMENT_MAX = "max";
    public static final String ARGUMENT_OFFSET = "offset";
    public static final String ARGUMENT_ORDER = "order";
    public static final String ARGUMENT_SORT = "sort";
    public static final String ORDER_DESC = "desc";
    public static final String ORDER_ASC = "asc";
    public static final String ARGUMENT_FETCH = "fetch";
    public static final String ARGUMENT_IGNORE_CASE = "ignoreCase";
    public static final String ARGUMENT_CACHE = "cache";
    public static final String ARGUMENT_LOCK = "lock";
    public static final String CONFIG_PROPERTY_CACHE_QUERIES = "grails.hibernate.cache.queries";
    public static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];

    private static HibernateProxyHandler proxyHandler = new HibernateProxyHandler();

    @SuppressWarnings("rawtypes")
    public static void enableDynamicFilterEnablerIfPresent(SessionFactory sessionFactory, Session session) {
        if (sessionFactory != null && session != null) {
            final Set definedFilterNames = sessionFactory.getDefinedFilterNames();
            if (definedFilterNames != null && definedFilterNames.contains(DYNAMIC_FILTER_ENABLER))
                session.enableFilter(DYNAMIC_FILTER_ENABLER); // work around for HHH-2624
        }
    }

    public static void configureHibernateDomainClasses(SessionFactory sessionFactory,
            GrailsApplication application) {
        Map<String, GrailsDomainClass> hibernateDomainClassMap = new HashMap<String, GrailsDomainClass>();
        for (Object o : sessionFactory.getAllClassMetadata().values()) {
            ClassMetadata classMetadata = (ClassMetadata) o;
            configureDomainClass(sessionFactory, application, classMetadata,
                    classMetadata.getMappedClass(EntityMode.POJO), hibernateDomainClassMap);
        }
        configureInheritanceMappings(hibernateDomainClassMap);
    }

    @SuppressWarnings("rawtypes")
    public static void configureInheritanceMappings(Map hibernateDomainClassMap) {
        // now get through all domainclasses, and add all subclasses to root class
        for (Object o : hibernateDomainClassMap.values()) {
            GrailsDomainClass baseClass = (GrailsDomainClass) o;
            if (!baseClass.isRoot()) {
                Class<?> superClass = baseClass.getClazz().getSuperclass();

                while (!superClass.equals(Object.class) && !superClass.equals(GroovyObject.class)) {
                    GrailsDomainClass gdc = (GrailsDomainClass) hibernateDomainClassMap.get(superClass.getName());

                    if (gdc == null || gdc.getSubClasses() == null) {
                        LOG.debug("did not find superclass names when mapping inheritance....");
                        break;
                    }
                    gdc.getSubClasses().add(baseClass);
                    superClass = superClass.getSuperclass();
                }
            }
        }
    }

    private static void configureDomainClass(SessionFactory sessionFactory, GrailsApplication application,
            ClassMetadata cmd, Class<?> persistentClass, Map<String, GrailsDomainClass> hibernateDomainClassMap) {

        if (Modifier.isAbstract(persistentClass.getModifiers())) {
            return;
        }

        LOG.trace("Configuring domain class [" + persistentClass + "]");
        GrailsDomainClass dc = (GrailsDomainClass) application.getArtefact(DomainClassArtefactHandler.TYPE,
                persistentClass.getName());
        if (dc == null) {
            // a patch to add inheritance to this system
            GrailsHibernateDomainClass ghdc = new GrailsHibernateDomainClass(persistentClass, sessionFactory,
                    application, cmd);

            hibernateDomainClassMap.put(persistentClass.getName(), ghdc);
            dc = (GrailsDomainClass) application.addArtefact(DomainClassArtefactHandler.TYPE, ghdc);
        }
    }

    /**
     * Populates criteria arguments for the given target class and arguments map
     *
     * @param grailsApplication the GrailsApplication instance
     * @param targetClass The target class
     * @param c The criteria instance
     * @param argMap The arguments map
     *
        
     */
    @SuppressWarnings("rawtypes")
    public static void populateArgumentsForCriteria(GrailsApplication grailsApplication, Class<?> targetClass,
            Criteria c, Map argMap) {
        Integer maxParam = null;
        Integer offsetParam = null;
        SimpleTypeConverter converter = new SimpleTypeConverter();
        if (argMap.containsKey(ARGUMENT_MAX)) {
            maxParam = converter.convertIfNecessary(argMap.get(ARGUMENT_MAX), Integer.class);
        }
        if (argMap.containsKey(ARGUMENT_OFFSET)) {
            offsetParam = converter.convertIfNecessary(argMap.get(ARGUMENT_OFFSET), Integer.class);
        }
        if (argMap.containsKey(ARGUMENT_FETCH_SIZE)) {
            c.setFetchSize(converter.convertIfNecessary(argMap.get(ARGUMENT_FETCH_SIZE), Integer.class));
        }
        if (argMap.containsKey(ARGUMENT_TIMEOUT)) {
            c.setTimeout(converter.convertIfNecessary(argMap.get(ARGUMENT_TIMEOUT), Integer.class));
        }
        if (argMap.containsKey(ARGUMENT_FLUSH_MODE)) {
            c.setFlushMode(converter.convertIfNecessary(argMap.get(ARGUMENT_FLUSH_MODE), FlushMode.class));
        }
        if (argMap.containsKey(ARGUMENT_READ_ONLY)) {
            c.setReadOnly(GrailsClassUtils.getBooleanFromMap(ARGUMENT_READ_ONLY, argMap));
        }
        String orderParam = (String) argMap.get(ARGUMENT_ORDER);
        Object fetchObj = argMap.get(ARGUMENT_FETCH);
        if (fetchObj instanceof Map) {
            Map fetch = (Map) fetchObj;
            for (Object o : fetch.keySet()) {
                String associationName = (String) o;
                c.setFetchMode(associationName, getFetchMode(fetch.get(associationName)));
            }
        }

        final String sort = (String) argMap.get(ARGUMENT_SORT);
        final String order = ORDER_DESC.equalsIgnoreCase(orderParam) ? ORDER_DESC : ORDER_ASC;
        final int max = maxParam == null ? -1 : maxParam;
        final int offset = offsetParam == null ? -1 : offsetParam;
        if (max > -1) {
            c.setMaxResults(max);
        }
        if (offset > -1) {
            c.setFirstResult(offset);
        }
        if (GrailsClassUtils.getBooleanFromMap(ARGUMENT_CACHE, argMap)) {
            c.setCacheable(true);
        }
        if (GrailsClassUtils.getBooleanFromMap(ARGUMENT_LOCK, argMap)) {
            c.setLockMode(LockMode.PESSIMISTIC_WRITE);
        } else {
            if (argMap.get(ARGUMENT_CACHE) == null) {
                cacheCriteriaByMapping(targetClass, c);
            }
        }
        if (sort != null) {
            boolean ignoreCase = true;
            Object caseArg = argMap.get(ARGUMENT_IGNORE_CASE);
            if (caseArg instanceof Boolean) {
                ignoreCase = (Boolean) caseArg;
            }
            addOrderPossiblyNested(grailsApplication, c, targetClass, sort, order, ignoreCase);
        } else {
            Mapping m = GrailsDomainBinder.getMapping(targetClass);
            if (m != null && !StringUtils.isBlank(m.getSort())) {
                addOrderPossiblyNested(grailsApplication, c, targetClass, m.getSort(), m.getOrder(), true);
            }
        }
    }

    /**
     * Populates criteria arguments for the given target class and arguments map
     *
     * @param targetClass The target class
     * @param c The criteria instance
     * @param argMap The arguments map
     *
     * @deprecated Use {@link #populateArgumentsForCriteria(org.codehaus.groovy.grails.commons.GrailsApplication, Class, org.hibernate.Criteria, java.util.Map)} instead
     */
    @Deprecated
    @SuppressWarnings("rawtypes")
    public static void populateArgumentsForCriteria(Class<?> targetClass, Criteria c, Map argMap) {
        populateArgumentsForCriteria(null, targetClass, c, argMap);
    }

    /**
     * Add order to criteria, creating necessary subCriteria if nested sort property (ie. sort:'nested.property').
     */
    private static void addOrderPossiblyNested(GrailsApplication grailsApplication, Criteria c,
            Class<?> targetClass, String sort, String order, boolean ignoreCase) {
        int firstDotPos = sort.indexOf(".");
        if (firstDotPos == -1) {
            addOrder(c, sort, order, ignoreCase);
        } else { // nested property
            String sortHead = sort.substring(0, firstDotPos);
            String sortTail = sort.substring(firstDotPos + 1);
            GrailsDomainClassProperty property = getGrailsDomainClassProperty(grailsApplication, targetClass,
                    sortHead);
            if (property.isEmbedded()) {
                // embedded objects cannot reference entities (at time of writing), so no more recursion needed
                addOrder(c, sort, order, ignoreCase);
            } else {
                Criteria subCriteria = c.createCriteria(sortHead);
                Class<?> propertyTargetClass = property.getReferencedDomainClass().getClazz();
                addOrderPossiblyNested(grailsApplication, subCriteria, propertyTargetClass, sortTail, order,
                        ignoreCase); // Recurse on nested sort
            }
        }
    }

    /**
     * Add order directly to criteria.
     */
    private static void addOrder(Criteria c, String sort, String order, boolean ignoreCase) {
        if (ORDER_DESC.equals(order)) {
            c.addOrder(ignoreCase ? Order.desc(sort).ignoreCase() : Order.desc(sort));
        } else {
            c.addOrder(ignoreCase ? Order.asc(sort).ignoreCase() : Order.asc(sort));
        }
    }

    /**
     * Get hold of the GrailsDomainClassProperty represented by the targetClass' propertyName,
     * assuming targetClass corresponds to a GrailsDomainClass.
     */
    private static GrailsDomainClassProperty getGrailsDomainClassProperty(GrailsApplication grailsApplication,
            Class<?> targetClass, String propertyName) {
        GrailsClass grailsClass = grailsApplication != null
                ? grailsApplication.getArtefact(DomainClassArtefactHandler.TYPE, targetClass.getName())
                : null;
        if (!(grailsClass instanceof GrailsDomainClass)) {
            throw new IllegalArgumentException("Unexpected: class is not a domain class:" + targetClass.getName());
        }
        GrailsDomainClass domainClass = (GrailsDomainClass) grailsClass;
        return domainClass.getPropertyByName(propertyName);
    }

    /**
     * Configures the criteria instance to cache based on the configured mapping.
     *
     * @param targetClass The target class
     * @param criteria The criteria
     */
    public static void cacheCriteriaByMapping(Class<?> targetClass, Criteria criteria) {
        Mapping m = GrailsDomainBinder.getMapping(targetClass);
        if (m != null && m.getCache() != null && m.getCache().getEnabled()) {
            criteria.setCacheable(true);
        }
    }

    @SuppressWarnings("rawtypes")
    public static void populateArgumentsForCriteria(Criteria c, Map argMap) {
        populateArgumentsForCriteria(null, null, c, argMap);
    }

    /**
     * Retrieves the fetch mode for the specified instance; otherwise returns the default FetchMode.
     *
     * @param object The object, converted to a string
     * @return The FetchMode
     */
    public static FetchMode getFetchMode(Object object) {
        String name = object != null ? object.toString() : "default";
        if (name.equalsIgnoreCase(FetchMode.JOIN.toString()) || name.equalsIgnoreCase("eager")) {
            return FetchMode.JOIN;
        }
        if (name.equalsIgnoreCase(FetchMode.SELECT.toString()) || name.equalsIgnoreCase("lazy")) {
            return FetchMode.SELECT;
        }
        return FetchMode.DEFAULT;
    }

    /**
     * Sets the target object to read-only using the given SessionFactory instance. This
     * avoids Hibernate performing any dirty checking on the object
     *
     * @see #setObjectToReadWrite(Object, org.hibernate.SessionFactory)
     *
     * @param target The target object
     * @param sessionFactory The SessionFactory instance
     */
    public static void setObjectToReadyOnly(Object target, SessionFactory sessionFactory) {
        Session session = sessionFactory.getCurrentSession();
        if (canModifyReadWriteState(session, target)) {
            if (target instanceof HibernateProxy) {
                target = ((HibernateProxy) target).getHibernateLazyInitializer().getImplementation();
            }
            session.setReadOnly(target, true);
            session.setFlushMode(FlushMode.MANUAL);
        }
    }

    private static boolean canModifyReadWriteState(Session session, Object target) {
        return session.contains(target) && Hibernate.isInitialized(target);
    }

    /**
     * Sets the target object to read-write, allowing Hibernate to dirty check it and auto-flush changes.
     *
     * @see #setObjectToReadyOnly(Object, org.hibernate.SessionFactory)
     *
     * @param target The target object
     * @param sessionFactory The SessionFactory instance
     */
    public static void setObjectToReadWrite(final Object target, SessionFactory sessionFactory) {
        HibernateTemplate template = new HibernateTemplate(sessionFactory);
        template.setExposeNativeSession(true);
        template.execute(new HibernateCallback<Void>() {
            public Void doInHibernate(Session session) throws HibernateException, SQLException {
                if (canModifyReadWriteState(session, target)) {
                    SessionImplementor sessionImpl = (SessionImplementor) session;
                    EntityEntry ee = sessionImpl.getPersistenceContext().getEntry(target);

                    if (ee != null && ee.getStatus() == Status.READ_ONLY) {
                        Object actualTarget = target;
                        if (target instanceof HibernateProxy) {
                            actualTarget = ((HibernateProxy) target).getHibernateLazyInitializer()
                                    .getImplementation();
                        }

                        session.setReadOnly(actualTarget, false);
                        session.setFlushMode(FlushMode.AUTO);
                        incrementVersion(target);
                    }
                }
                return null;
            }
        });
    }

    /**
     * Increments the entities version number in order to force an update
     * @param target The target entity
     */
    public static void incrementVersion(Object target) {
        MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(target.getClass());
        if (metaClass.hasProperty(target, GrailsDomainClassProperty.VERSION) != null) {
            Object version = metaClass.getProperty(target, GrailsDomainClassProperty.VERSION);
            if (version instanceof Long) {
                Long newVersion = (Long) version + 1;
                metaClass.setProperty(target, GrailsDomainClassProperty.VERSION, newVersion);
            }
        }
    }

    /**
     * Ensures the meta class is correct for a given class
     *
     * @param target The GroovyObject
     * @param persistentClass The persistent class
     */
    public static void ensureCorrectGroovyMetaClass(Object target, Class<?> persistentClass) {
        if (target instanceof GroovyObject) {
            GroovyObject go = ((GroovyObject) target);
            if (!go.getMetaClass().getTheClass().equals(persistentClass)) {
                go.setMetaClass(GroovySystem.getMetaClassRegistry().getMetaClass(persistentClass));
            }
        }
    }

    /**
     * Unwraps and initializes a HibernateProxy.
     * @param proxy The proxy
     * @return the unproxied instance
     */
    public static Object unwrapProxy(HibernateProxy proxy) {
        return proxyHandler.unwrapProxy(proxy);
    }

    /**
     * Returns the proxy for a given association or null if it is not proxied
     *
     * @param obj The object
     * @param associationName The named assoication
     * @return A proxy
     */
    public static HibernateProxy getAssociationProxy(Object obj, String associationName) {
        return proxyHandler.getAssociationProxy(obj, associationName);
    }

    /**
     * Checks whether an associated property is initialized and returns true if it is
     *
     * @param obj The name of the object
     * @param associationName The name of the association
     * @return True if is initialized
     */
    public static boolean isInitialized(Object obj, String associationName) {
        return proxyHandler.isInitialized(obj, associationName);
    }

    public static boolean isCacheQueriesByDefault() {
        Object o = GrailsWebUtil.currentFlatConfiguration().get(CONFIG_PROPERTY_CACHE_QUERIES);
        return (o != null && o instanceof Boolean) ? ((Boolean) o).booleanValue() : false;
    }

    public static GroovyAwareJavassistProxyFactory buildProxyFactory(PersistentClass persistentClass) {
        GroovyAwareJavassistProxyFactory proxyFactory = new GroovyAwareJavassistProxyFactory();

        @SuppressWarnings("unchecked")
        Set<Class<HibernateProxy>> proxyInterfaces = CollectionUtils.newSet(HibernateProxy.class);

        final Class<?> javaClass = persistentClass.getMappedClass();
        final Property identifierProperty = persistentClass.getIdentifierProperty();
        final Getter idGetter = identifierProperty != null ? identifierProperty.getGetter(javaClass) : null;
        final Setter idSetter = identifierProperty != null ? identifierProperty.getSetter(javaClass) : null;

        if (idGetter == null || idSetter == null)
            return null;

        try {
            proxyFactory.postInstantiate(persistentClass.getEntityName(), javaClass, proxyInterfaces,
                    idGetter.getMethod(), idSetter.getMethod(),
                    persistentClass.hasEmbeddedIdentifier()
                            ? (CompositeType) persistentClass.getIdentifier().getType()
                            : null);
        } catch (HibernateException e) {
            LOG.warn("Cannot instantiate proxy factory: " + e.getMessage());
            return null;
        }

        return proxyFactory;
    }

    public static Object unwrapIfProxy(Object instance) {
        return proxyHandler.unwrapIfProxy(instance);
    }

    public static boolean usesDatasource(GrailsDomainClass domainClass, String dataSourceName) {
        List<String> names = getDatasourceNames(domainClass);
        return names.contains(dataSourceName) || names.contains(GrailsDomainClassProperty.ALL_DATA_SOURCES);
    }

    /**
     * If a domain class uses more than one datasource, we need to know which one to use
     * when calling a method without a namespace qualifier.
     *
     * @param domainClass the domain class
     * @return the default datasource name
     */
    public static String getDefaultDataSource(GrailsDomainClass domainClass) {
        List<String> names = getDatasourceNames(domainClass);
        if (names.size() == 1 && GrailsDomainClassProperty.ALL_DATA_SOURCES.equals(names.get(0))) {
            return GrailsDomainClassProperty.DEFAULT_DATA_SOURCE;
        }
        return names.get(0);
    }

    public static List<String> getDatasourceNames(GrailsDomainClass domainClass) {
        // Mappings won't have been built yet when this is called from
        // HibernatePluginSupport.doWithSpring  so do a temporary evaluation but don't cache it
        Mapping mapping = GrailsDomainBinder.evaluateMapping(domainClass, null, false);
        if (mapping == null) {
            mapping = new Mapping();
        }
        return mapping.getDatasources();
    }
}