org.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration.java

Source

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

import grails.core.*;
import groovy.lang.Closure;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.grails.datastore.mapping.model.MappingContext;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.orm.hibernate.EventListenerIntegrator;
import org.grails.orm.hibernate.GrailsSessionContext;
import org.grails.orm.hibernate.HibernateEventListeners;
import org.grails.orm.hibernate.proxy.GroovyAwarePojoEntityTuplizer;
import org.grails.core.artefact.AnnotationDomainClassArtefactHandler;
import org.grails.core.artefact.DomainClassArtefactHandler;
import org.hibernate.*;
import org.hibernate.boot.registry.BootstrapServiceRegistry;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.*;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.type.Type;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;

import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import java.io.IOException;
import java.lang.InstantiationException;
import java.util.*;

/**
 * Allows configuring Grails' hibernate support to work in conjuntion with Hibernate's annotation
 * support.
 *
 * @author Graeme Rocher
 * @since 18-Feb-2006
 */
public class GrailsAnnotationConfiguration extends Configuration
        implements GrailsDomainConfiguration, InitializingBean, ApplicationContextAware {

    private static final long serialVersionUID = -7115087342689305517L;

    private static final Log LOG = LogFactory.getLog(GrailsAnnotationConfiguration.class);

    private GrailsApplication grailsApplication;
    private Set<GrailsDomainClass> domainClasses = new HashSet<GrailsDomainClass>();
    private boolean configLocked;
    private String sessionFactoryBeanName = "sessionFactory";
    private String dataSourceName = Mapping.DEFAULT_DATA_SOURCE;

    private static final String RESOURCE_PATTERN = "/**/*.class";

    private static final TypeFilter[] ENTITY_TYPE_FILTERS = new TypeFilter[] {
            new AnnotationTypeFilter(Entity.class, false), new AnnotationTypeFilter(Embeddable.class, false),
            new AnnotationTypeFilter(MappedSuperclass.class, false) };

    private ResourcePatternResolver resourcePatternResolver;
    private ServiceRegistry serviceRegistry;
    private HibernateEventListeners hibernateEventListeners;
    private Map<String, Object> eventListeners;

    protected GrailsDomainBinder binder = new GrailsDomainBinder();
    protected ApplicationContext applicationContext;
    private boolean subclassForeignKeysCreated = false;

    /* (non-Javadoc)
     * @see org.grails.orm.hibernate.cfg.GrailsDomainConfiguration#addDomainClass(org.codehaus.groovy.grails.commons.GrailsDomainClass)
     */
    public GrailsDomainConfiguration addDomainClass(GrailsDomainClass domainClass) {
        if (shouldMapWithGorm(domainClass)) {
            domainClasses.add(domainClass);
        } else {
            addAnnotatedClass(domainClass.getClazz());
        }

        return this;
    }

    private boolean shouldMapWithGorm(GrailsDomainClass domainClass) {
        return !AnnotationDomainClassArtefactHandler.isJPADomainClass(domainClass.getClazz())
                && domainClass.getMappingStrategy().equalsIgnoreCase(GrailsDomainClass.GORM);
    }

    /* (non-Javadoc)
     * @see org.grails.orm.hibernate.cfg.GrailsDomainConfiguration#setGrailsApplication(org.codehaus.groovy.grails.commons.GrailsApplication)
     */
    public void setGrailsApplication(GrailsApplication application) {
        grailsApplication = application;
        if (grailsApplication == null) {
            return;
        }

        GrailsClass[] existingDomainClasses = grailsApplication.getArtefacts(DomainClassArtefactHandler.TYPE);
        for (GrailsClass existingDomainClass : existingDomainClasses) {
            addDomainClass((GrailsDomainClass) existingDomainClass);
        }
    }

    public void setSessionFactoryBeanName(String name) {
        sessionFactoryBeanName = name;
    }

    public void setDataSourceName(String name) {
        dataSourceName = name;
    }

    /**
     * Overrides the default behaviour to including binding of Grails domain classes.
     */
    @Override
    protected void secondPassCompile() throws MappingException {
        final Thread currentThread = Thread.currentThread();
        final ClassLoader originalContextLoader = currentThread.getContextClassLoader();
        try {
            if (grailsApplication != null) {
                currentThread.setContextClassLoader(grailsApplication.getClassLoader());
            }

            final Mappings mappings = super.createMappings();
            if (!configLocked) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("[GrailsAnnotationConfiguration] [" + domainClasses.size()
                            + "] Grails domain classes to bind to persistence runtime");
                }

                // do Grails class configuration
                Closure defaultMapping = grailsApplication.getConfig()
                        .getProperty(GrailsDomainConfiguration.DEFAULT_MAPPING, Closure.class);
                MappingContext mappingContext = new HibernateMappingContext(defaultMapping);

                for (GrailsDomainClass domainClass : domainClasses) {
                    final PersistentEntity entity = mappingContext.addPersistentEntity(domainClass.getClazz());
                    mappingContext.addEntityValidator(entity, domainClass.getValidator());
                    binder.evaluateMapping(entity);
                }

                for (PersistentEntity domainClass : mappingContext.getPersistentEntities()) {

                    final String fullClassName = domainClass.getName();

                    String hibernateConfig = fullClassName.replace('.', '/') + ".hbm.xml";
                    final ClassLoader loader = originalContextLoader;
                    // don't configure Hibernate mapped classes
                    if (loader.getResource(hibernateConfig) != null)
                        continue;

                    if (!GrailsHibernateUtil.usesDatasource(domainClass, dataSourceName)) {
                        continue;
                    }

                    LOG.debug("[GrailsAnnotationConfiguration] Binding persistent class [" + fullClassName + "]");

                    Mapping m = binder.getMapping(domainClass);
                    mappings.setAutoImport(m == null || m.getAutoImport());
                    binder.bindClass(domainClass, mappings, sessionFactoryBeanName);
                }
            }

            super.secondPassCompile();
            createSubclassForeignKeys();
        } finally {
            currentThread.setContextClassLoader(originalContextLoader);
        }

        configLocked = true;
    }

    /**
     * Creates foreign keys for subclass tables that are mapped using table per subclass. Further information is
     * available in the <a href="http://jira.grails.org/browse/GRAILS-7729">JIRA ticket</a>
     */
    private void createSubclassForeignKeys() {
        if (subclassForeignKeysCreated) {
            return;
        }

        for (PersistentClass persistentClass : classes.values()) {
            if (persistentClass instanceof RootClass) {
                RootClass rootClass = (RootClass) persistentClass;

                if (rootClass.hasSubclasses()) {
                    Iterator subclasses = rootClass.getSubclassIterator();

                    while (subclasses.hasNext()) {

                        Object subclass = subclasses.next();

                        // This test ensures that foreign keys will only be created for subclasses that are
                        // mapped using "table per subclass"
                        if (subclass instanceof JoinedSubclass) {
                            JoinedSubclass joinedSubclass = (JoinedSubclass) subclass;
                            joinedSubclass.createForeignKey();
                        }
                    }
                }
            }
        }

        subclassForeignKeysCreated = true;
    }

    /**
     * Sets custom naming strategy specified in configuration or the default {@link ImprovedNamingStrategy}.
     */
    private void configureNamingStrategy() {
        NamingStrategy strategy = null;
        Object customStrategy = grailsApplication.getFlatConfig().get("hibernate.naming_strategy");
        if (customStrategy != null) {
            Class<?> namingStrategyClass = null;
            if (customStrategy instanceof Class<?>) {
                namingStrategyClass = (Class<?>) customStrategy;
            } else {
                try {
                    namingStrategyClass = grailsApplication.getClassLoader().loadClass(customStrategy.toString());
                } catch (ClassNotFoundException e) {
                    // ignore
                }
            }

            if (namingStrategyClass != null) {
                try {
                    strategy = (NamingStrategy) namingStrategyClass.newInstance();
                } catch (InstantiationException e) {
                    // ignore
                } catch (IllegalAccessException e) {
                    // ignore
                }
            }
        }

        if (strategy == null) {
            strategy = ImprovedNamingStrategy.INSTANCE;
        }

        setNamingStrategy(strategy);
    }

    /**
     * Add the given annotated classes in a batch.
     * @see #addAnnotatedClass
     * @see #scanPackages
     */
    public void addAnnotatedClasses(Class<?>... annotatedClasses) {
        for (Class<?> annotatedClass : annotatedClasses) {
            addAnnotatedClass(annotatedClass);
        }
    }

    /**
     * Add the given annotated packages in a batch.
     * @see #addPackage
     * @see #scanPackages
     */
    public void addPackages(String... annotatedPackages) {
        for (String annotatedPackage : annotatedPackages) {
            addPackage(annotatedPackage);
        }
    }

    /**
     * Default listeners.
     * @param listeners the listeners
     */
    public void setEventListeners(Map<String, Object> listeners) {
        eventListeners = listeners;
    }

    /**
     * User-specifiable extra listeners.
     * @param listeners the listeners
     */
    public void setHibernateEventListeners(HibernateEventListeners listeners) {
        hibernateEventListeners = listeners;
    }

    /**
     * Perform Spring-based scanning for entity classes, registering them
     * as annotated classes with this {@code Configuration}.
     * @param packagesToScan one or more Java package names
     * @throws HibernateException if scanning fails for any reason
     */
    public void scanPackages(String... packagesToScan) throws HibernateException {
        try {
            for (String pkg : packagesToScan) {
                String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                        + ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
                Resource[] resources = resourcePatternResolver.getResources(pattern);
                MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
                for (Resource resource : resources) {
                    if (resource.isReadable()) {
                        MetadataReader reader = readerFactory.getMetadataReader(resource);
                        String className = reader.getClassMetadata().getClassName();
                        if (matchesFilter(reader, readerFactory)) {
                            addAnnotatedClasses(resourcePatternResolver.getClassLoader().loadClass(className));
                        }
                    }
                }
            }
        } catch (IOException ex) {
            throw new MappingException("Failed to scan classpath for unlisted classes", ex);
        } catch (ClassNotFoundException ex) {
            throw new MappingException("Failed to load annotated classes from classpath", ex);
        }
    }

    /**
     * Check whether any of the configured entity type filters matches
     * the current class descriptor contained in the metadata reader.
     */
    protected boolean matchesFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException {
        for (TypeFilter filter : ENTITY_TYPE_FILTERS) {
            if (filter.match(reader, readerFactory)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Settings buildSettings(ServiceRegistry serviceRegistry) {
        Settings settings = super.buildSettings(serviceRegistry);
        settings.getEntityTuplizerFactory().registerDefaultTuplizerClass(EntityMode.POJO,
                GroovyAwarePojoEntityTuplizer.class);
        return settings;
    }

    @Override
    public Settings buildSettings(Properties props, ServiceRegistry serviceRegistry) throws HibernateException {
        Settings settings = super.buildSettings(props, serviceRegistry);
        settings.getEntityTuplizerFactory().registerDefaultTuplizerClass(EntityMode.POJO,
                GroovyAwarePojoEntityTuplizer.class);
        return settings;
    }

    @Override
    public SessionFactory buildSessionFactory() throws HibernateException {

        // set the class loader to load Groovy classes

        // work around for HHH-2624
        Map<String, Type> empty = new HashMap<String, Type>();
        addFilterDefinition(new FilterDefinition("dynamicFilterEnabler", "1=1", empty));

        SessionFactory sessionFactory = null;

        ClassLoader appClassLoader = (ClassLoader) getProperties().get(AvailableSettings.APP_CLASSLOADER);
        Thread currentThread = Thread.currentThread();
        ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
        boolean overrideClassLoader = (appClassLoader != null && !appClassLoader.equals(threadContextClassLoader));
        if (overrideClassLoader) {
            currentThread.setContextClassLoader(appClassLoader);
        }

        try {
            ConfigurationHelper.resolvePlaceHolders(getProperties());

            EventListenerIntegrator eventListenerIntegrator = new EventListenerIntegrator(hibernateEventListeners,
                    eventListeners);
            BootstrapServiceRegistry bootstrapServiceRegistry = new BootstrapServiceRegistryBuilder()
                    .with(eventListenerIntegrator).build();

            setSessionFactoryObserver(new SessionFactoryObserver() {
                private static final long serialVersionUID = 1;

                public void sessionFactoryCreated(SessionFactory factory) {
                }

                public void sessionFactoryClosed(SessionFactory factory) {
                    ((ServiceRegistryImplementor) serviceRegistry).destroy();
                }
            });

            StandardServiceRegistryBuilder standardServiceRegistryBuilder = new StandardServiceRegistryBuilder(
                    bootstrapServiceRegistry).applySettings(getProperties());
            sessionFactory = super.buildSessionFactory(standardServiceRegistryBuilder.build());
            serviceRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry();
        } finally {
            if (overrideClassLoader) {
                currentThread.setContextClassLoader(threadContextClassLoader);
            }
        }

        return sessionFactory;
    }

    public ServiceRegistry getServiceRegistry() {
        return serviceRegistry;
    }

    public void afterPropertiesSet() throws Exception {
        if (grailsApplication == null) {
            return;
        }

        String dsName = Mapping.DEFAULT_DATA_SOURCE.equals(dataSourceName) ? "dataSource"
                : "dataSource_" + dataSourceName;
        getProperties().put(Environment.DATASOURCE, applicationContext.getBean(dsName));
        getProperties().put(Environment.CURRENT_SESSION_CONTEXT_CLASS, GrailsSessionContext.class.getName());
        getProperties().put(AvailableSettings.CLASSLOADERS, grailsApplication.getClassLoader());
        resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(applicationContext);

        configureNamingStrategy();
        GrailsClass[] existingDomainClasses = grailsApplication.getArtefacts(DomainClassArtefactHandler.TYPE);
        for (GrailsClass existingDomainClass : existingDomainClasses) {
            addDomainClass((GrailsDomainClass) existingDomainClass);
        }

        ArtefactHandler handler = grailsApplication.getArtefactHandler(DomainClassArtefactHandler.TYPE);
        if (!(handler instanceof AnnotationDomainClassArtefactHandler)) {
            return;
        }

        Set<String> jpaDomainNames = ((AnnotationDomainClassArtefactHandler) handler).getJpaClassNames();
        if (jpaDomainNames == null) {
            return;
        }

        final ClassLoader loader = grailsApplication.getClassLoader();
        for (String jpaDomainName : jpaDomainNames) {
            try {
                addAnnotatedClass(loader.loadClass(jpaDomainName));
            } catch (ClassNotFoundException e) {
                // impossible condition
            }
        }
    }

    @Override
    protected void reset() {
        super.reset();
        try {
            GrailsIdentifierGeneratorFactory.applyNewInstance(this);
        } catch (Exception e) {
            // ignore exception
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}