org.hibernate.criterion.Example.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.criterion.Example.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.criterion;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;

/**
 * Support for query by example.
 *
 * <pre>
 * List results = session.createCriteria(Parent.class)
 *     .add( Example.create(parent).ignoreCase() )
 *     .createCriteria("child")
 *         .add( Example.create( parent.getChild() ) )
 *     .list();
 * </pre>
 *
 * "Examples" may be mixed and matched with "Expressions" in the same Criteria.
 *
 * @see org.hibernate.Criteria
 * @author Gavin King
 */

public class Example implements Criterion {
    private final Object exampleEntity;
    private PropertySelector selector;

    private boolean isLikeEnabled;
    private Character escapeCharacter;
    private boolean isIgnoreCaseEnabled;
    private MatchMode matchMode;

    private final Set<String> excludedProperties = new HashSet<String>();

    /**
     * Create a new Example criterion instance, which includes all non-null properties by default
     *
     * @param exampleEntity The example bean to use.
     *
     * @return a new instance of Example
     */
    public static Example create(Object exampleEntity) {
        if (exampleEntity == null) {
            throw new NullPointerException("null example entity");
        }
        return new Example(exampleEntity, NotNullPropertySelector.INSTANCE);
    }

    /**
     * Allow subclasses to instantiate as needed.
     *
     * @param exampleEntity The example bean
     * @param selector The property selector to use
     */
    protected Example(Object exampleEntity, PropertySelector selector) {
        this.exampleEntity = exampleEntity;
        this.selector = selector;
    }

    /**
     * Set escape character for "like" clause if like matching was enabled
     *
     * @param escapeCharacter The escape character
     *
     * @return {@code this}, for method chaining
     *
     * @see #enableLike
     */
    public Example setEscapeCharacter(Character escapeCharacter) {
        this.escapeCharacter = escapeCharacter;
        return this;
    }

    /**
     * Use the "like" operator for all string-valued properties.  This form implicitly uses {@link MatchMode#EXACT}
     *
     * @return {@code this}, for method chaining
     */
    public Example enableLike() {
        return enableLike(MatchMode.EXACT);
    }

    /**
     * Use the "like" operator for all string-valued properties
     *
     * @param matchMode The match mode to use.
     *
     * @return {@code this}, for method chaining
     */
    public Example enableLike(MatchMode matchMode) {
        this.isLikeEnabled = true;
        this.matchMode = matchMode;
        return this;
    }

    /**
     * Ignore case for all string-valued properties
     *
     * @return {@code this}, for method chaining
     */
    public Example ignoreCase() {
        this.isIgnoreCaseEnabled = true;
        return this;
    }

    /**
     * Set the property selector to use.
     *
     * The property selector operates separate from excluding a property.
     *
     * @param selector The selector to use
     *
     * @return {@code this}, for method chaining
     *
     * @see #excludeProperty
     */
    public Example setPropertySelector(PropertySelector selector) {
        this.selector = selector;
        return this;
    }

    /**
     * Exclude zero-valued properties.
     *
     * Equivalent to calling {@link #setPropertySelector} passing in {@link NotNullOrZeroPropertySelector#INSTANCE}
     *
     * @return {@code this}, for method chaining
     *
     * @see #setPropertySelector
     */
    public Example excludeZeroes() {
        setPropertySelector(NotNullOrZeroPropertySelector.INSTANCE);
        return this;
    }

    /**
     * Include all properties.
     *
     * Equivalent to calling {@link #setPropertySelector} passing in {@link AllPropertySelector#INSTANCE}
     *
     * @return {@code this}, for method chaining
     *
     * @see #setPropertySelector
     */
    public Example excludeNone() {
        setPropertySelector(AllPropertySelector.INSTANCE);
        return this;
    }

    /**
     * Exclude a particular property by name.
     *
     * @param name The name of the property to exclude
     *
     * @return {@code this}, for method chaining
     *
     * @see #setPropertySelector
     */
    public Example excludeProperty(String name) {
        excludedProperties.add(name);
        return this;
    }

    @Override
    public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
        final StringBuilder buf = new StringBuilder().append('(');
        final EntityPersister meta = criteriaQuery.getFactory()
                .getEntityPersister(criteriaQuery.getEntityName(criteria));
        final String[] propertyNames = meta.getPropertyNames();
        final Type[] propertyTypes = meta.getPropertyTypes();

        final Object[] propertyValues = meta.getPropertyValues(exampleEntity);
        for (int i = 0; i < propertyNames.length; i++) {
            final Object propertyValue = propertyValues[i];
            final String propertyName = propertyNames[i];

            final boolean isVersionProperty = i == meta.getVersionProperty();
            if (!isVersionProperty && isPropertyIncluded(propertyValue, propertyName, propertyTypes[i])) {
                if (propertyTypes[i].isComponentType()) {
                    appendComponentCondition(propertyName, propertyValue, (CompositeType) propertyTypes[i],
                            criteria, criteriaQuery, buf);
                } else {
                    appendPropertyCondition(propertyName, propertyValue, criteria, criteriaQuery, buf);
                }
            }
        }

        if (buf.length() == 1) {
            buf.append("1=1");
        }

        return buf.append(')').toString();
    }

    @SuppressWarnings("SimplifiableIfStatement")
    private boolean isPropertyIncluded(Object value, String name, Type type) {
        if (excludedProperties.contains(name)) {
            // was explicitly excluded
            return false;
        }

        if (type.isAssociationType()) {
            // associations are implicitly excluded
            return false;
        }

        return selector.include(value, name, type);
    }

    @Override
    public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) {
        final EntityPersister meta = criteriaQuery.getFactory()
                .getEntityPersister(criteriaQuery.getEntityName(criteria));
        final String[] propertyNames = meta.getPropertyNames();
        final Type[] propertyTypes = meta.getPropertyTypes();

        final Object[] values = meta.getPropertyValues(exampleEntity);
        final List<TypedValue> list = new ArrayList<TypedValue>();
        for (int i = 0; i < propertyNames.length; i++) {
            final Object value = values[i];
            final Type type = propertyTypes[i];
            final String name = propertyNames[i];

            final boolean isVersionProperty = i == meta.getVersionProperty();

            if (!isVersionProperty && isPropertyIncluded(value, name, type)) {
                if (propertyTypes[i].isComponentType()) {
                    addComponentTypedValues(name, value, (CompositeType) type, list, criteria, criteriaQuery);
                } else {
                    addPropertyTypedValue(value, type, list);
                }
            }
        }

        return list.toArray(new TypedValue[list.size()]);
    }

    protected void addPropertyTypedValue(Object value, Type type, List<TypedValue> list) {
        if (value != null) {
            if (value instanceof String) {
                String string = (String) value;
                if (isIgnoreCaseEnabled) {
                    string = string.toLowerCase(Locale.ROOT);
                }
                if (isLikeEnabled) {
                    string = matchMode.toMatchString(string);
                }
                value = string;
            }
            list.add(new TypedValue(type, value));
        }
    }

    protected void addComponentTypedValues(String path, Object component, CompositeType type, List<TypedValue> list,
            Criteria criteria, CriteriaQuery criteriaQuery) {
        if (component != null) {
            final String[] propertyNames = type.getPropertyNames();
            final Type[] subtypes = type.getSubtypes();
            final Object[] values = type.getPropertyValues(component, getEntityMode(criteria, criteriaQuery));
            for (int i = 0; i < propertyNames.length; i++) {
                final Object value = values[i];
                final Type subtype = subtypes[i];
                final String subpath = StringHelper.qualify(path, propertyNames[i]);
                if (isPropertyIncluded(value, subpath, subtype)) {
                    if (subtype.isComponentType()) {
                        addComponentTypedValues(subpath, value, (CompositeType) subtype, list, criteria,
                                criteriaQuery);
                    } else {
                        addPropertyTypedValue(value, subtype, list);
                    }
                }
            }
        }
    }

    private EntityMode getEntityMode(Criteria criteria, CriteriaQuery criteriaQuery) {
        final EntityPersister meta = criteriaQuery.getFactory()
                .getEntityPersister(criteriaQuery.getEntityName(criteria));
        final EntityMode result = meta.getEntityMode();
        if (!meta.getEntityMetamodel().getTuplizer().isInstance(exampleEntity)) {
            throw new ClassCastException(exampleEntity.getClass().getName());
        }
        return result;
    }

    protected void appendPropertyCondition(String propertyName, Object propertyValue, Criteria criteria,
            CriteriaQuery cq, StringBuilder buf) {
        final Criterion condition;
        if (propertyValue != null) {
            final boolean isString = propertyValue instanceof String;
            if (isLikeEnabled && isString) {
                condition = new LikeExpression(propertyName, (String) propertyValue, matchMode, escapeCharacter,
                        isIgnoreCaseEnabled);
            } else {
                condition = new SimpleExpression(propertyName, propertyValue, "=", isIgnoreCaseEnabled && isString);
            }
        } else {
            condition = new NullExpression(propertyName);
        }

        final String conditionFragment = condition.toSqlString(criteria, cq);
        if (conditionFragment.trim().length() > 0) {
            if (buf.length() > 1) {
                buf.append(" and ");
            }
            buf.append(conditionFragment);
        }
    }

    protected void appendComponentCondition(String path, Object component, CompositeType type, Criteria criteria,
            CriteriaQuery criteriaQuery, StringBuilder buf) {
        if (component != null) {
            final String[] propertyNames = type.getPropertyNames();
            final Object[] values = type.getPropertyValues(component, getEntityMode(criteria, criteriaQuery));
            final Type[] subtypes = type.getSubtypes();
            for (int i = 0; i < propertyNames.length; i++) {
                final String subPath = StringHelper.qualify(path, propertyNames[i]);
                final Object value = values[i];
                if (isPropertyIncluded(value, subPath, subtypes[i])) {
                    final Type subtype = subtypes[i];
                    if (subtype.isComponentType()) {
                        appendComponentCondition(subPath, value, (CompositeType) subtype, criteria, criteriaQuery,
                                buf);
                    } else {
                        appendPropertyCondition(subPath, value, criteria, criteriaQuery, buf);
                    }
                }
            }
        }
    }

    @Override
    public String toString() {
        return "example (" + exampleEntity + ')';
    }

    // PropertySelector definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /**
     * A strategy for choosing property values for inclusion in the query criteria.  Note that
     * property selection (for inclusion) operates separately from excluding a property.  Excluded
     * properties are not even passed in to the PropertySelector for consideration.
     */
    public static interface PropertySelector extends Serializable {
        /**
         * Determine whether the given property should be used in the criteria.
         *
         * @param propertyValue The property value (from the example bean)
         * @param propertyName The name of the property
         * @param type The type of the property
         *
         * @return {@code true} indicates the property should be included; {@code false} indiates it should not.
         */
        public boolean include(Object propertyValue, String propertyName, Type type);
    }

    /**
     * Property selector that includes all properties
     */
    public static final class AllPropertySelector implements PropertySelector {
        /**
         * Singleton access
         */
        public static final AllPropertySelector INSTANCE = new AllPropertySelector();

        @Override
        public boolean include(Object object, String propertyName, Type type) {
            return true;
        }

        private Object readResolve() {
            return INSTANCE;
        }
    }

    /**
     * Property selector that includes only properties that are not {@code null}
     */
    public static final class NotNullPropertySelector implements PropertySelector {
        /**
         * Singleton access
         */
        public static final NotNullPropertySelector INSTANCE = new NotNullPropertySelector();

        @Override
        public boolean include(Object object, String propertyName, Type type) {
            return object != null;
        }

        private Object readResolve() {
            return INSTANCE;
        }
    }

    /**
     * Property selector that includes only properties that are not {@code null} and non-zero (if numeric)
     */
    public static final class NotNullOrZeroPropertySelector implements PropertySelector {
        /**
         * Singleton access
         */
        public static final NotNullOrZeroPropertySelector INSTANCE = new NotNullOrZeroPropertySelector();

        @Override
        public boolean include(Object object, String propertyName, Type type) {
            return object != null && (!(object instanceof Number) || ((Number) object).longValue() != 0);
        }

        private Object readResolve() {
            return INSTANCE;
        }
    }
}