org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod.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.codehaus.groovy.grails.orm.hibernate.metaclass;

import grails.validation.DeferredBindingActions;
import grails.validation.ValidationException;
import groovy.lang.GroovyObject;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClass;

import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.PropertyUtils;
import org.codehaus.groovy.grails.commons.*;
import org.codehaus.groovy.grails.lifecycle.ShutdownOperations;
import org.codehaus.groovy.grails.orm.hibernate.HibernateDatastore;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil;
import org.codehaus.groovy.grails.orm.hibernate.validation.AbstractPersistentConstraint;
import org.codehaus.groovy.grails.validation.CascadingValidator;
import org.grails.datastore.mapping.engine.event.ValidationEvent;
import org.hibernate.SessionFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

/**
 * Abstract class for different implementations that perform saving to implement.
 *
 * @author Graeme Rocher
 * @since 0.3
 */
public abstract class AbstractSavePersistentMethod extends AbstractDynamicPersistentMethod {

    private HibernateDatastore datastore;
    private static final String ARGUMENT_VALIDATE = "validate";
    private static final String ARGUMENT_DEEP_VALIDATE = "deepValidate";
    private static final String ARGUMENT_FLUSH = "flush";
    private static final String ARGUMENT_INSERT = "insert";
    private static final String ARGUMENT_FAIL_ON_ERROR = "failOnError";
    private static final String FAIL_ON_ERROR_CONFIG_PROPERTY = "grails.gorm.failOnError";
    private static final String AUTO_FLUSH_CONFIG_PROPERTY = "grails.gorm.autoFlush";

    /**
     * When a domain instance is saved without validation, we put it
     * into this thread local variable. Any code that needs to know
     * whether the domain instance should be validated can just check
     * the value. Note that this only works because the session is
     * flushed when a domain instance is saved without validation.
     */
    private static ThreadLocal<Set<Integer>> disableAutoValidationFor = new ThreadLocal<Set<Integer>>() {
        @Override
        protected Set<Integer> initialValue() {
            return new HashSet<Integer>();
        }
    };

    static {
        ShutdownOperations.addOperation(new Runnable() {
            public void run() {
                disableAutoValidationFor.remove();
            }
        });
    }

    public static boolean isAutoValidationDisabled(Object obj) {
        Set<Integer> identifiers = disableAutoValidationFor.get();
        return obj != null && identifiers.contains(System.identityHashCode(obj));
    }

    public static void clearDisabledValidations(Object obj) {
        disableAutoValidationFor.get().remove(System.identityHashCode(obj));
    }

    public static void clearDisabledValidations() {
        disableAutoValidationFor.get().clear();
    }

    public AbstractSavePersistentMethod(Pattern pattern, SessionFactory sessionFactory, ClassLoader classLoader,
            GrailsApplication application, @SuppressWarnings("unused") GrailsDomainClass domainClass,
            HibernateDatastore datastore) {
        super(pattern, sessionFactory, classLoader, application);

        this.datastore = datastore;
    }

    public AbstractSavePersistentMethod(Pattern pattern, SessionFactory sessionFactory, ClassLoader classLoader,
            GrailsApplication application, HibernateDatastore datastore) {
        this(pattern, sessionFactory, classLoader, application, null, datastore);
    }

    @SuppressWarnings("rawtypes")
    private boolean shouldFail(GrailsApplication grailsApplication, GrailsDomainClass domainClass) {
        boolean shouldFail = false;
        final Map config = grailsApplication.getFlatConfig();
        if (config.containsKey(FAIL_ON_ERROR_CONFIG_PROPERTY)) {
            Object configProperty = config.get(FAIL_ON_ERROR_CONFIG_PROPERTY);
            if (configProperty instanceof Boolean) {
                shouldFail = Boolean.TRUE == configProperty;
            } else if (configProperty instanceof List) {
                if (domainClass != null) {
                    final Class theClass = domainClass.getClazz();
                    List packageList = (List) configProperty;
                    shouldFail = GrailsClassUtils.isClassBelowPackage(theClass, packageList);
                }
            }
        }
        return shouldFail;
    }

    /* (non-Javadoc)
     * @see org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod#doInvokeInternal(java.lang.Object, java.lang.Object[])
     */
    @SuppressWarnings("rawtypes")
    @Override
    protected Object doInvokeInternal(final Object target, Object[] arguments) {
        GrailsDomainClass domainClass = (GrailsDomainClass) application.getArtefact(DomainClassArtefactHandler.TYPE,
                target.getClass().getName());

        DeferredBindingActions.runActions();
        boolean shouldFlush = shouldFlush(arguments);
        boolean shouldValidate = shouldValidate(arguments, domainClass);
        if (shouldValidate) {
            Validator validator = domainClass.getValidator();

            if (domainClass instanceof DefaultGrailsDomainClass) {
                GrailsHibernateUtil.autoAssociateBidirectionalOneToOnes((DefaultGrailsDomainClass) domainClass,
                        target);
            }
            Errors errors = setupErrorsProperty(target);

            if (validator != null) {
                application.getMainContext().publishEvent(new ValidationEvent(datastore, target));

                boolean deepValidate = true;
                Map argsMap = null;
                if (arguments.length > 0 && arguments[0] instanceof Map) {
                    argsMap = (Map) arguments[0];
                }
                if (argsMap != null && argsMap.containsKey(ARGUMENT_DEEP_VALIDATE)) {
                    deepValidate = GrailsClassUtils.getBooleanFromMap(ARGUMENT_DEEP_VALIDATE, argsMap);
                }

                AbstractPersistentConstraint.sessionFactory.set(datastore.getSessionFactory());
                try {
                    if (deepValidate && (validator instanceof CascadingValidator)) {
                        ((CascadingValidator) validator).validate(target, errors, deepValidate);
                    } else {
                        validator.validate(target, errors);
                    }
                } finally {
                    AbstractPersistentConstraint.sessionFactory.remove();
                }

                if (errors.hasErrors()) {
                    handleValidationError(domainClass, target, errors);
                    boolean shouldFail = shouldFail(application, domainClass);
                    if (argsMap != null && argsMap.containsKey(ARGUMENT_FAIL_ON_ERROR)) {
                        shouldFail = GrailsClassUtils.getBooleanFromMap(ARGUMENT_FAIL_ON_ERROR, argsMap);
                    }
                    if (shouldFail) {
                        throw new ValidationException("Validation Error(s) occurred during save()", errors);
                    }
                    return null;
                }

                setObjectToReadWrite(target);
            }
        }

        // this piece of code will retrieve a persistent instant
        // of a domain class property is only the id is set thus
        // relieving this burden off the developer
        if (domainClass != null) {
            autoRetrieveAssocations(domainClass, target);
        }

        if (!shouldValidate) {
            Set<Integer> identifiers = disableAutoValidationFor.get();
            identifiers.add(System.identityHashCode(target));
        }

        if (shouldInsert(arguments)) {
            return performInsert(target, shouldFlush);
        }

        return performSave(target, shouldFlush);
    }

    @SuppressWarnings("rawtypes")
    private boolean shouldInsert(Object[] arguments) {
        return arguments.length > 0 && arguments[0] instanceof Map
                && GrailsClassUtils.getBooleanFromMap(ARGUMENT_INSERT, (Map) arguments[0]);
    }

    @SuppressWarnings("rawtypes")
    private boolean shouldFlush(Object[] arguments) {
        final boolean shouldFlush;
        if (arguments.length > 0 && arguments[0] instanceof Boolean) {
            shouldFlush = ((Boolean) arguments[0]).booleanValue();
        } else if (arguments.length > 0 && arguments[0] instanceof Map
                && ((Map) arguments[0]).containsKey(ARGUMENT_FLUSH)) {
            shouldFlush = GrailsClassUtils.getBooleanFromMap(ARGUMENT_FLUSH, ((Map) arguments[0]));
        } else {
            final Map config = application.getFlatConfig();
            shouldFlush = Boolean.TRUE == config.get(AUTO_FLUSH_CONFIG_PROPERTY);
        }
        return shouldFlush;
    }

    /**
     * Performs automatic association retrieval
     * @param domainClass The domain class to retrieve associations for
     * @param target The target object
     */
    @SuppressWarnings("unchecked")
    private void autoRetrieveAssocations(GrailsDomainClass domainClass, Object target) {
        BeanWrapper bean = new BeanWrapperImpl(target);
        HibernateTemplate t = getHibernateTemplate();
        GrailsDomainClassProperty[] props = domainClass.getPersistentProperties();
        for (int i = 0; i < props.length; i++) {
            GrailsDomainClassProperty prop = props[i];
            if (!prop.isManyToOne() && !prop.isOneToOne()) {
                continue;
            }

            Object propValue = bean.getPropertyValue(prop.getName());
            if (propValue == null || t.contains(propValue)) {
                continue;
            }

            GrailsDomainClass otherSide = (GrailsDomainClass) application
                    .getArtefact(DomainClassArtefactHandler.TYPE, prop.getType().getName());
            if (otherSide == null) {
                continue;
            }

            BeanWrapper propBean = new BeanWrapperImpl(propValue);
            try {
                Serializable id = (Serializable) propBean.getPropertyValue(otherSide.getIdentifier().getName());
                if (id != null) {
                    final Object propVal = t.get(prop.getType(), id);
                    if (propVal != null) {
                        bean.setPropertyValue(prop.getName(), propVal);
                    }
                }
            } catch (InvalidPropertyException ipe) {
                // property is not accessable
            }
        }
    }

    /**
     * Sets the flush mode to manual. which ensures that the database changes are not persisted to the database
     * if a validation error occurs. If save() is called again and validation passes the code will check if there
     * is a manual flush mode and flush manually if necessary
     *
     * @param domainClass The domain class
     * @param target The target object that failed validation
     * @param errors The Errors instance  @return This method will return null signaling a validation failure
     */
    protected Object handleValidationError(GrailsDomainClass domainClass, final Object target, Errors errors) {
        // if a validation error occurs set the object to read-only to prevent a flush
        setObjectToReadOnly(target);
        if (domainClass instanceof DefaultGrailsDomainClass) {
            DefaultGrailsDomainClass dgdc = (DefaultGrailsDomainClass) domainClass;
            List<GrailsDomainClassProperty> associations = dgdc.getAssociations();
            for (GrailsDomainClassProperty association : associations) {
                if (association.isOneToOne() || association.isManyToOne()) {
                    BeanWrapper bean = new BeanWrapperImpl(target);
                    Object propertyValue = bean.getPropertyValue(association.getName());
                    if (propertyValue != null) {
                        setObjectToReadOnly(propertyValue);
                    }

                }
            }
        }
        setErrorsOnInstance(target, errors);
        return null;
    }

    /**
     * Associates the Errors object on the instance
     *
     * @param target The target instance
     * @param errors The Errors object
     */
    protected void setErrorsOnInstance(Object target, Errors errors) {
        if (target instanceof GroovyObject) {
            ((GroovyObject) target).setProperty(ERRORS_PROPERTY, errors);
        } else {
            MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(target.getClass());
            metaClass.setProperty(target.getClass(), target, ERRORS_PROPERTY, errors, false, false);
        }
    }

    /**
     * Checks whether validation should be performed
     * @return True if the domain class should be validated
     * @param arguments  The arguments to the validate method
     * @param domainClass The domain class
     */
    @SuppressWarnings("rawtypes")
    private boolean shouldValidate(Object[] arguments, GrailsDomainClass domainClass) {
        if (domainClass == null) {
            return false;
        }

        if (arguments.length == 0) {
            return true;
        }

        if (arguments[0] instanceof Boolean) {
            return ((Boolean) arguments[0]).booleanValue();
        }

        if (arguments[0] instanceof Map) {
            Map argsMap = (Map) arguments[0];
            if (argsMap.containsKey(ARGUMENT_VALIDATE)) {
                return GrailsClassUtils.getBooleanFromMap(ARGUMENT_VALIDATE, argsMap);
            }

            return true;
        }

        return true;
    }

    /**
     * Subclasses should override and perform a save operation, flushing the session if the second argument is true
     *
     * @param target The target object to save
     * @param shouldFlush Whether to flush
     * @return The target object
     */
    abstract protected Object performSave(Object target, boolean shouldFlush);

    /**
     * Subclasses should override and perform an insert operation, flushing the session if the second argument is true
     *
     * @param target The target object to save
     * @param shouldFlush Whether to flush
     * @return The target object
     */
    protected abstract Object performInsert(Object target, boolean shouldFlush);
}