/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.queries; import java.security.AccessController; import java.security.PrivilegedActionException; import java.util.HashMap; import java.util.Map; import java.util.Vector; import org.eclipse.persistence.exceptions.QueryException; import org.eclipse.persistence.expressions.Expression; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker; /** * <p><b>Purpose</b>: * This policy defines the configuration options for a Query By Example query. * * <p><b>Description</b>: * A Query By Example query is an <code>ObjectLevelReadQuery</code> where the * selection criteria is built from an example domain object passed in via <code>setExampleObject</code>. * <p> * If no policy is specified the selection criteria is built from the example * object in the following way: * <ul> * <li>Attributes of the example object set to <code>null</code> are ignored. * * <li>Attributes set to the default value for their primitive type (such as * <code>0</code> for <code>int</code>) are ignored. * * <li>Unmapped attributes are ignored. * * <li>A domain object is returned by the query only if its values for all the * included attributes equal those set in the example object. * </ul><p> * A policy can be set on the query to: * <ul> * <li>Always consider an attribute even if set to <code>null</code> * or the default value for its type. See {@link #alwaysIncludeAttribute alwaysIncludeAttribute}. * * <li>Ignore attributes set to some other special value. See * {@link #excludeValue(Object) excludeValue}. * * <li>Match a <code>null</code> attribute on the example object with domain objects that have * either <code>null</code> for that attribute also, or have set a meaningful (<code>notNull</code>) value * for that attribute. See {@link #setShouldUseEqualityForNulls}. * * <li>Use specialized operations when comparing attributes set in the example object * with those set in the domain objects. Matching attributes can be those with * values greater than, less than, like, or not equal to that set in the example * object. See {@link #addSpecialOperation}. * </ul> * <p> * Note: When setting an attribute on the example object which is itself a java * object with an ObjectReferenceMapping, the mapped components of that * attribute will be considered, not the entire object. There is no limit to * how many mapped objects can be nested inside the example object. * <p> * Note: <code>setExampleObject</code> is different from <code>setSelectionObject</code> in * <code>ReadObjectQuery</code> which reads a single object by first extracting * the primary key from the selection object. * <p> * <b>Restrictions</b>: * <ul> * <li>Only attributes whose mappings are DirectToField, Aggregate (Embeddable), ObjectReference * (OneToOne) or Collection type OneToMany/ManyToMany are considered in a Query By Example object. The behaviour when an example object has attribute values for other mappings types is <b>undefined</b>. * <ul><li>To ensure the example does not include any unsupported mappings the flag {@link #setValidateExample} * should be set to true on the corresponding QueryByExamplePolicy to ensure no unsupported relationship types are used in the example.</li> * <li> For OneToMany and ManyToMany mappings the elements within the collections and the references attribute values will be added to the expression as disjuncts (OR)</li> * </ul> * </li> * </ul> * <p> * <b>Example</b>: * <BLOCKQUOTE><PRE> * // This example uses like for Strings and the salary must be greater * // than zero. * ReadAllQuery query = new ReadAllQuery(); * Employee employee = new Employee(); * employee.setFirstName("B%"); * employee.setLastName("S%"); * employee.setSalary(0); * query.setExampleObject(employee); * QueryByExamplePolicy policy = new QueryByExamplePolicy(); * policy.addSpecialOperation(String.class, "like"); * policy.addSpecialOperation(Integer.class, "greaterThan"); * policy.alwaysIncludeAttribute(Employee.class, "salary"); * query.setQueryByExamplePolicy(policy); * Vector results = (Vector) session.executeQuery(query); * </PRE></BLOCKQUOTE> * @see ObjectLevelReadQuery#setExampleObject * @see ObjectLevelReadQuery#setQueryByExamplePolicy * * @since TOPLink/Java 3.0 */ public class QueryByExamplePolicy implements java.io.Serializable { //CR3400 Make Serializable public Map valuesToExclude = new HashMap(); public Map attributesToAlwaysInclude = new HashMap(); public Map specialOperations = new HashMap(); public boolean shouldUseEqualityForNulls; protected boolean validateExample; /** * PUBLIC: * Constructs a default policy equal to that used when no policy is specified. * <p> * Sets the default values to be excluded, * (that includes 0, false, empty String, etc).<p> * By default if an attribute is <code>null</code>, and yet has to be included at all times, equality (<code>isNull</code>) * is used for the comparison. This is used for searching for an object with a <code>null</code> in a certain field. * @see #excludeDefaultPrimitiveValues * @see #setShouldUseEqualityForNulls setShouldUseEqualityForNulls(true) */ public QueryByExamplePolicy() { this.valuesToExclude = new HashMap(10); this.attributesToAlwaysInclude = new HashMap(5); this.specialOperations = new HashMap(5); this.shouldUseEqualityForNulls = true; this.excludeDefaultPrimitiveValues(); } /** * PUBLIC: * Allows operations other than <code>Expression.equal</code> to be used * for comparisons. * <p> * For example if an attribute of type <code>int</code> is * set to <code>x</code> in the example object, normally the query will be on all objects * whose attributes are also equal to <code>x</code>. The query could however be all * objects whose attributes are not <code>x</code>, greater than <code>x</code>, or even less than or * equal to <code>x</code>. * <p> * Any comparison operation in {@link org.eclipse.persistence.expressions.Expression Expression} which takes the example attribute as a parameter * can be used. A list of supported operations is provided below. * <p> * Note: A special operation can not be used for attributes set to <code>null</code>. The only * options are {@link org.eclipse.persistence.expressions.Expression#isNull() isNull} (default) and * {@link org.eclipse.persistence.expressions.Expression#notNull() notNull}. See * {@link #setShouldUseEqualityForNulls}. * @param attributeValueClass Attribute values of which type, for instance * <code>Integer</code>, to apply to. Note for <code>int</code> attributes the * class is <code>Integer.class</code> not <code>int.class</code>. This is not * the <code>Class</code> of the example object the attribute is an instance variable of. * @param operation Name of method in <code>Expression</code> used * @see org.eclipse.persistence.expressions.Expression#equal equal (default) * @see org.eclipse.persistence.expressions.Expression#notEqual notEqual * @see org.eclipse.persistence.expressions.Expression#equalsIgnoreCase equalsIgnoreCase * @see org.eclipse.persistence.expressions.Expression#lessThan lessThan * @see org.eclipse.persistence.expressions.Expression#lessThanEqual lessThanEqual * @see org.eclipse.persistence.expressions.Expression#greaterThan greaterThan * @see org.eclipse.persistence.expressions.Expression#greaterThanEqual greaterThanEqual * @see org.eclipse.persistence.expressions.Expression#like like * @see org.eclipse.persistence.expressions.Expression#likeIgnoreCase likeIgnoreCase * @see org.eclipse.persistence.expressions.Expression#containsAllKeyWords containsAllKeyWords * @see org.eclipse.persistence.expressions.Expression#containsAnyKeyWords containsAnyKeyWords * @see org.eclipse.persistence.expressions.Expression#containsSubstring(java.lang.String) containsSubstring * @see org.eclipse.persistence.expressions.Expression#containsSubstringIgnoringCase(java.lang.String) containsSubstringIgnoringCase */ public void addSpecialOperation(Class attributeValueClass, String operation) { this.getSpecialOperations().put(attributeValueClass, operation); } /** * PUBLIC: * Always considers the value for a particular attribute as meaningful in a * query by example. * <p> * Required to override the normal behavior which is to ignore an * attribute of the example object if set to <code>null</code>, or an excluded value * like <code>0</code>. * <p> * Example: To find all projects without a budget set <code>budget</code> to 0 in the * example object and call <code>alwaysIncludeAttribute(Project.class, "budget")</code> * on the policy. * <p> * @param exampleClass The class that the attribute belongs to, normally this is the example class unless using nested QBE. * @param attributeName The name of a mapped attribute. */ public void alwaysIncludeAttribute(Class exampleClass, String attributeName) { Vector included = (Vector)getAttributesToAlwaysInclude().get(exampleClass); if (included == null) { included = new Vector(3); } included.addElement(attributeName); getAttributesToAlwaysInclude().put(exampleClass, included); } /** * INTERNAL: * This method is used to determine which operation to use for comparison (equal, or a special operation). */ public Expression completeExpression(Expression expression, Object attributeValue, Class attributeValueClass) { String operation = this.getOperation(attributeValue.getClass()); if (operation == null) { //it means no special operation used. Use equal. return expression.equal(attributeValue); } Class[] argTypes = { attributeValueClass }; Object[] args = { attributeValue }; try { java.lang.reflect.Method anOperator = Helper.getDeclaredMethod(ClassConstants.Expression_Class, operation, argTypes); if (anOperator == null) { throw QueryException.methodDoesNotExistOnExpression(operation, argTypes); } if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ try{ expression = (Expression)AccessController.doPrivileged(new PrivilegedMethodInvoker(anOperator, expression, args)); }catch (PrivilegedActionException ex){ throw (RuntimeException) ex.getCause(); } }else{ expression = (Expression)PrivilegedAccessHelper.invokeMethod(anOperator, expression, args); } } catch (NoSuchMethodException nsme) { Class superClass = attributeValueClass.getSuperclass(); if (superClass != null) { return completeExpression(expression, attributeValue, superClass); } else { throw QueryException.methodDoesNotExistOnExpression(operation, argTypes); } } catch (IllegalAccessException iae) { throw QueryException.methodDoesNotExistOnExpression(operation, argTypes); } catch (java.lang.reflect.InvocationTargetException ite) { throw QueryException.methodDoesNotExistOnExpression(operation, argTypes); } return expression; } /** * INTERNAL: * This method is used when the attribute value is null, but it has * to be included at all times. It determines whether to use isNull, or notNull. */ public Expression completeExpressionForNull(Expression expression) { if (shouldUseEqualityForNulls()) { return expression.isNull(); } else { return expression.notNull(); } } /** * PUBLIC: * Ignores attributes set to the default value for their primitive type. * <p> * For instance <code>0</code> is used as <code>null</code> for deciding * which <code>int</code> attributes of the example object can be ignored in a * query by example. * <p> * Called by the constructor. */ public void excludeDefaultPrimitiveValues() { excludeValue(0); excludeValue(0.0); excludeValue(false); excludeValue((short)0); excludeValue('\u0000'); excludeValue((long)0); excludeValue((byte)0); excludeValue(0.0f); excludeValue(""); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>byte</code> is <code>0</code>. */ public void excludeValue(byte value) { excludeValue(Byte.valueOf(value)); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>char</code> is <code>'\u0000'</code>. */ public void excludeValue(char value) { excludeValue(Character.valueOf(value)); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>double</code> is <code>0.0</code>. */ public void excludeValue(double value) { excludeValue(Double.valueOf(value)); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>float</code> is <code>0.0f</code>. */ public void excludeValue(float value) { excludeValue(Float.valueOf(value)); } /** * PUBLIC: * An attribute in the example object set to be an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>int</code> is <code>0</code>. */ public void excludeValue(int value) { excludeValue(Integer.valueOf(value)); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>long</code> is <code>0</code>. */ public void excludeValue(long value) { excludeValue(Long.valueOf(value)); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>String</code> is <code>""</code>.<p> * Note: <code>null</code> is special and always considered an excluded value. */ public void excludeValue(Object value) { this.valuesToExclude.put(value, value); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>short</code> is <code>0</code>. */ public void excludeValue(short value) { excludeValue(Short.valueOf(value)); } /** * PUBLIC: * An attribute in the example object set to an excluded value will be * ignored in a Query By Example.<p> * The default excluded value for <code>boolean</code> is <code>false</code>. */ public void excludeValue(boolean value) { excludeValue(Boolean.valueOf(value)); } /** * PUBLIC: * Attributes to always consider even if set to <code>null</code> or an excluded * value like <code>0</code> or <code>false</code>. * @see #alwaysIncludeAttribute */ public Map getAttributesToAlwaysInclude() { return attributesToAlwaysInclude; } /** * INTERNAL: * determines which operation to use for comparison. */ public String getOperation(Class aClass) { String operation = (String)this.getSpecialOperations().get(aClass); if (operation != null) { if (!operation.equals("equal")) { return operation; } } return null; } /** * PUBLIC: * The special operations to use in place of <code>equal</code>. * @return A hashtable where the keys are <code>Class</code> objects and the values * are the names of operations to use for attributes of that <code>Class</code>. * @see #addSpecialOperation */ public Map getSpecialOperations() { return specialOperations; } /** * PUBLIC: * Decides which attributes to ignore based on the values they are set to. * <p> * If an attribute of the example domain object is set to one of these values it will * be ignored, and not considered in the query. * <p> * Attributes set to excluded values are not always ignored. * See {@link #alwaysIncludeAttribute alwaysIncludeAttribute}. * @return valuesToExclude The keys and values are values to exclude (key == value). Primitives are * wrapped, so <code>int 0</code> will be stored as <code>Integer(0)</code>. * @see #excludeValue * @see #excludeDefaultPrimitiveValues * @see #includeAllValues */ public Map getValuesToExclude() { return valuesToExclude; } /** * PUBLIC: * Considers all mapped attributes in the example object as meaningful in a * Query By Example.<p> * Note: Even attributes of the example object that are * not set, and therefore zero or empty by default, will be included.<p> * Reverses a previous call to {@link #excludeDefaultPrimitiveValues}. */ public void includeAllValues() { setValuesToExclude(new HashMap(5)); } /** * INTERNAL: * returns whether the attributeName is to be always included. */ public boolean isAlwaysIncluded(Class theClass, String attributeName) { Vector values = (Vector)this.getAttributesToAlwaysInclude().get(theClass); if (values != null) { return (values.contains(attributeName)); } return false; } /** * INTERNAL: * returns if the value is in the values to be excluded automatically. */ public boolean isExcludedValue(Object value) { return this.getValuesToExclude().containsKey(value); } /** * PUBLIC: * Considers all attributes set to a previously excluded value on the example object. * <p> * Primitive values to be removed must first be wrapped inside an Object. * <p> * @param value No attributes set to <code>value</code> will be excluded from a Query By Example. * <code>value.getClass()</code> is a key of the Hashtable returned by {@link #getValuesToExclude}. * <p>Note: There is a distinction between an attribute and the value * it is set to. An attribute can be included independently of its value with * {@link #alwaysIncludeAttribute alwaysIncludeAttribute} (recommended). It can also be included * by making the value it is set to no longer excluded. * <p>Note: <code>null</code> values are special and will always be excluded. * @see #excludeDefaultPrimitiveValues * @see #includeAllValues * @see #excludeValue(Object) */ public void removeFromValuesToExclude(Object value) { getValuesToExclude().remove(value); } /** * INTERNAL: * It is possible to generate a Hashtable (keys are the Class, and values the attribute names) * of the attributes to be included at all times (even if the value is null, or the value * belongs to the values to be excluced automatically). */ public void setAttributesToAlwaysInclude(Map newAttributesToAlwaysInclude) { attributesToAlwaysInclude = newAttributesToAlwaysInclude; } /** * PUBLIC: * Matches an included <code>null</code> attribute in the example object * either to objects with that attribute also set to <code>null</code> or to any * value other than <code>null</code>. * <p> * Set to <code>false</code> to only select objects where certain attributes have been set. * <p> * Example: to find all Employees with an assigned <code>address</code>, set * attribute <code>address</code> to <code>null</code> in the example object, * call <code>alwaysIncludeAttribute(Employee.class, "address")</code> and then * call <code>setShouldUseEqualityForNulls(false)</code>. * <p> * Note: Unless an attribute set to <code>null</code> is specifically included, it * will not be considered at all in the Query By Example. * @param shouldUseEqualityForNulls If true (by default) uses <code>isNull</code> else <code>notNull</code>. * @see #addSpecialOperation addSpecialOperation * @see #alwaysIncludeAttribute alwaysIncludeAttribute */ public void setShouldUseEqualityForNulls(boolean shouldUseEqualityForNulls) { this.shouldUseEqualityForNulls = shouldUseEqualityForNulls; } /** * PUBLIC: * The special operations to use in place of <code>equal</code>.<p> * @param newOperations A hashtable where the keys are <code>Class</code> objects and the values * are the names of operations to use for attributes of that <code>Class</code>. * @see #addSpecialOperation */ public void setSpecialOperations(Map newOperations) { specialOperations = newOperations; } /** * PUBLIC: * When set to <code>true</code> the example object will be validated for unsupported mapping types. * If you wish these mapping types to be ignored either set this flag to <code>false</code> or add the attribute * to the list of ignored attributes in this policy */ public void setValidateExample(boolean validate){ this.validateExample = validate; } /** * PUBLIC: * Decides which attributes to ignore based on the values they are set to. * <p> * An attribute of the example domain object set to one of these values will * be ignored, and not considered in the query. * <p> * Attributes set to excluded values are not always ignored. * See {@link #alwaysIncludeAttribute alwaysIncludeAttribute}. * @param newValuesToExclude The keys and values are values to exclude (key == value). Primitives are * wrapped, so <code>int 0</code> will be stored as <code>Integer(0)</code>. * @see #excludeValue * @see #excludeDefaultPrimitiveValues * @see #includeAllValues */ public void setValuesToExclude(Map newValuesToExclude) { valuesToExclude = newValuesToExclude; } /** * INTERNAL: * This method determines whether an attribute pair is be included in the query. */ public boolean shouldIncludeInQuery(Class aClass, String attributeName, Object attributeValue) { if (attributeValue == null) { if (this.isAlwaysIncluded(aClass, attributeName)) { //this attribute is to be included always, even if its value is null. return true; } else { return false; } } if (this.isExcludedValue(attributeValue)) { if (this.isAlwaysIncluded(aClass, attributeName)) { //this attribute is to be included always, even if its value belongs to the list of values to be excluded. return true; } else { return false; } } return true; } /** * PUBLIC: * Matches an included <code>null</code> attribute in the example object * either to objects with that attribute also set to <code>null</code> or to any * value other than <code>null</code>. * <p> * Set to <code>false</code> to only select objects where certain attributes have been set. * <p> * Example: to find all Employees with an assigned <code>address</code>, set * attribute <code>address</code> to <code>null</code> in the example object, * call <code>alwaysIncludeAttribute(Employee.class, "address")</code> and then * call <code>setShouldUseEqualityForNulls(false)</code>. * <p> * @return If true (by default) uses <code>isNull</code> else <code>notNull</code>. * @see #addSpecialOperation addSpecialOperation * @see #alwaysIncludeAttribute alwaysIncludeAttribute */ public boolean shouldUseEqualityForNulls() { return shouldUseEqualityForNulls; } /** * PUBLIC: * Returns true if the example object used with this policy should be validated for attributes * with unsupported mappings. */ public boolean shouldValidateExample(){ return this.validateExample; } }