blob: 0c1b6dd00bf356bcbeb7dffede95efa5b74a36d0 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// 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.
*
* @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 = 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.
*
* @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>.
* @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>.
*
* @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;
}
}