org.grails.orm.hibernate.validation.UniqueConstraint.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.orm.hibernate.validation.UniqueConstraint.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.grails.orm.hibernate.validation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import grails.core.GrailsApplication;
import grails.core.GrailsDomainClass;
import grails.core.GrailsDomainClassProperty;
import grails.util.GrailsClassUtils;
import grails.validation.ConstrainedProperty;
import groovy.lang.Closure;
import org.grails.datastore.gorm.GormEnhancer;
import org.grails.orm.hibernate.*;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.grails.core.artefact.DomainClassArtefactHandler;
import org.grails.core.exceptions.GrailsRuntimeException;
import org.grails.core.lifecycle.ShutdownOperations;
import org.hibernate.*;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.validation.Errors;

/**
 * A constraint that validates the uniqueness of a property (will query the
 * database during validation process).
 *
 * @author Graeme Rocher
 * @author Sergey Nebolsin
 * @since 0.4
 */
public class UniqueConstraint extends AbstractPersistentConstraint {

    private static final String DEFAULT_NOT_UNIQUE_MESSAGE_CODE = "default.not.unique.message";
    private static final String TARGET_DOMAIN_CLASS_ALIAS = "domain_";

    public static final String UNIQUE_CONSTRAINT = "unique";

    private boolean unique;
    private List<String> uniquenessGroup = new ArrayList<String>();

    public UniqueConstraint() {
        ShutdownOperations.addOperation(new Runnable() {
            public void run() {
                ConstrainedProperty.removeConstraint(UNIQUE_CONSTRAINT, PersistentConstraintFactory.class);
            }
        });
    }

    /**
     * @return Returns the unique.
     */
    public boolean isUnique() {
        return unique;
    }

    /**
     * @return Whether the property is unique within a group
     */
    public boolean isUniqueWithinGroup() {
        return !uniquenessGroup.isEmpty();
    }

    /* (non-Javadoc)
     * @see org.codehaus.groovy.grails.validation.ConstrainedProperty.AbstractConstraint#setParameter(java.lang.Object)
     */
    @Override
    public void setParameter(Object constraintParameter) {
        if (!(constraintParameter instanceof Boolean || constraintParameter instanceof String
                || constraintParameter instanceof CharSequence || constraintParameter instanceof List<?>)) {
            throw new IllegalArgumentException(
                    "Parameter for constraint [" + UNIQUE_CONSTRAINT + "] of property [" + constraintPropertyName
                            + "] of class [" + constraintOwningClass + "] must be a boolean or string value");
        }

        if (constraintParameter instanceof List<?>) {
            for (Object parameter : ((List<?>) constraintParameter)) {
                if (!(parameter instanceof String || parameter instanceof CharSequence)) {
                    throw new IllegalArgumentException("Parameter for constraint [" + UNIQUE_CONSTRAINT
                            + "] of property [" + constraintPropertyName + "] of class [" + constraintOwningClass
                            + "] must be a boolean or string value");
                }
                uniquenessGroup.add(parameter.toString());
            }
        } else if (constraintParameter instanceof String || constraintParameter instanceof CharSequence) {
            uniquenessGroup.add(constraintParameter.toString());
            unique = true;
        } else {
            unique = (Boolean) constraintParameter;
        }

        if (!uniquenessGroup.isEmpty()) {
            unique = true;
            for (Object anUniquenessGroup : uniquenessGroup) {
                String propertyName = (String) anUniquenessGroup;
                if (GrailsClassUtils.getPropertyType(constraintOwningClass, propertyName) == null) {
                    throw new IllegalArgumentException("Scope for constraint [" + UNIQUE_CONSTRAINT
                            + "] of property [" + constraintPropertyName + "] of class [" + constraintOwningClass
                            + "] must be a valid property name of same class");
                }
            }
        }

        super.setParameter(constraintParameter);
    }

    public String getName() {
        return UNIQUE_CONSTRAINT;
    }

    @Override
    protected void processValidate(final Object target, final Object propertyValue, Errors errors) {
        if (!unique) {
            return;
        }

        final Object id;
        try {
            id = InvokerHelper.invokeMethod(target, "ident", null);
        } catch (Exception e) {
            throw new GrailsRuntimeException("Target of [unique] constraints [" + target
                    + "] is not a domain instance. Unique constraint can only be applied to "
                    + "domain classes and not custom user types or embedded instances");
        }

        IHibernateTemplate hibernateTemplate = getHibernateTemplate(target);
        List<?> results = hibernateTemplate.execute(new Closure<List<?>>(this) {
            public List<?> call(Object... args) {
                Session session = (Session) args[0];
                boolean shouldValidate = true;
                Class<?> constraintClass = constraintOwningClass;
                if (propertyValue != null && DomainClassArtefactHandler.isDomainClass(propertyValue.getClass())) {
                    shouldValidate = session.contains(propertyValue);
                }
                if (shouldValidate) {
                    GrailsApplication application = (GrailsApplication) applicationContext
                            .getBean(GrailsApplication.APPLICATION_ID);
                    GrailsDomainClass domainClass = (GrailsDomainClass) application
                            .getArtefact(DomainClassArtefactHandler.TYPE, constraintClass.getName());
                    if (domainClass != null && !domainClass.isRoot()) {
                        GrailsDomainClassProperty property = domainClass.getPropertyByName(constraintPropertyName);
                        while (property.isInherited() && domainClass != null) {
                            domainClass = (GrailsDomainClass) application.getArtefact(
                                    DomainClassArtefactHandler.TYPE,
                                    domainClass.getClazz().getSuperclass().getName());
                            if (domainClass != null) {
                                property = domainClass.getPropertyByName(constraintPropertyName);
                            }
                        }
                        constraintClass = domainClass != null ? domainClass.getClazz() : constraintClass;
                    }
                    Criteria criteria = null;

                    if (domainClass.getPersistentProperty(constraintPropertyName).isOneToOne()) {
                        criteria = session.createCriteria(constraintClass, TARGET_DOMAIN_CLASS_ALIAS);

                        String constraintPropertyAlias = constraintPropertyName + "_";
                        criteria.createAlias(TARGET_DOMAIN_CLASS_ALIAS + "." + constraintPropertyName,
                                constraintPropertyAlias);

                        GrailsDomainClassProperty property = domainClass.getPropertyByName(constraintPropertyName);
                        ClassMetadata classMetadata = session.getSessionFactory()
                                .getClassMetadata(property.getReferencedPropertyType());
                        String identifierPropertyName = classMetadata.getIdentifierPropertyName();

                        BeanWrapper bean = new BeanWrapperImpl(propertyValue);
                        Object identifierPropertyValue = bean.getPropertyValue(identifierPropertyName);

                        criteria.add(Restrictions.eq(constraintPropertyAlias + "." + identifierPropertyName,
                                identifierPropertyValue));
                    } else {
                        criteria = session.createCriteria(constraintClass)
                                .add(Restrictions.eq(constraintPropertyName, propertyValue));
                    }

                    if (uniquenessGroup != null) {
                        for (Object anUniquenessGroup : uniquenessGroup) {
                            String uniquenessGroupPropertyName = (String) anUniquenessGroup;
                            Object uniquenessGroupPropertyValue = GrailsClassUtils
                                    .getPropertyOrStaticPropertyOrFieldValue(target, uniquenessGroupPropertyName);

                            if (uniquenessGroupPropertyValue != null && DomainClassArtefactHandler
                                    .isDomainClass(uniquenessGroupPropertyValue.getClass())) {
                                // We are merely verifying that the object is not transient here
                                shouldValidate = session.contains(uniquenessGroupPropertyValue);
                            }
                            if (shouldValidate) {
                                criteria.add(
                                        Restrictions.eq(uniquenessGroupPropertyName, uniquenessGroupPropertyValue));
                            } else {
                                break; // we aren't validating, so no point continuing
                            }
                        }
                    }

                    if (shouldValidate) {
                        return criteria.list();
                    }
                    return Collections.EMPTY_LIST;
                }
                return Collections.EMPTY_LIST;
            }
        });

        if (results.isEmpty()) {
            return;
        }

        boolean reject = false;
        if (id != null) {
            Object existing = results.get(0);
            Object existingId = null;
            try {
                existingId = InvokerHelper.invokeMethod(existing, "ident", null);
            } catch (Exception e) {
                // result is not a domain class
            }
            if (!id.equals(existingId)) {
                reject = true;
            }
        } else {
            reject = true;
        }
        if (reject) {
            Object[] args = { constraintPropertyName, constraintOwningClass, propertyValue };
            rejectValue(target, errors, UNIQUE_CONSTRAINT, args,
                    getDefaultMessage(DEFAULT_NOT_UNIQUE_MESSAGE_CODE));
        }
    }

    public List<String> getUniquenessGroup() {
        return uniquenessGroup;
    }

    @Override
    public IHibernateTemplate getHibernateTemplate(Object target) {
        AbstractHibernateGormInstanceApi instanceApi = (AbstractHibernateGormInstanceApi) GormEnhancer
                .findInstanceApi(target.getClass());
        sessionFactory = instanceApi.getSessionFactory();
        AbstractHibernateDatastore app = (AbstractHibernateDatastore) instanceApi.getDatastore();
        return app.getHibernateTemplate(AbstractHibernateDatastore.FlushMode.MANUAL.getLevel());
    }
}