org.eclipse.persistence.queries.QueryByExamplePolicy Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* 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;
/**
* Purpose:
* This policy defines the configuration options for a Query By Example query.
*
*
Description:
* A Query By Example query is an ObjectLevelReadQuery
where the
* selection criteria is built from an example domain object passed in via setExampleObject
.
*
* If no policy is specified the selection criteria is built from the example
* object in the following way:
*
* - Attributes of the example object set to
null
are ignored.
*
* - Attributes set to the default value for their primitive type (such as
*
0
for int
) are ignored.
*
* - Unmapped attributes are ignored.
*
*
- A domain object is returned by the query only if its values for all the
* included attributes equal those set in the example object.
*
* A policy can be set on the query to:
*
* - Always consider an attribute even if set to
null
* or the default value for its type. See {@link #alwaysIncludeAttribute alwaysIncludeAttribute}.
*
* - Ignore attributes set to some other special value. See
* {@link #excludeValue(Object) excludeValue}.
*
*
- Match a
null
attribute on the example object with domain objects that have
* either null
for that attribute also, or have set a meaningful (notNull
) value
* for that attribute. See {@link #setShouldUseEqualityForNulls}.
*
* - 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}.
*
*
* 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.
*
* Note: setExampleObject
is different from setSelectionObject
in
* ReadObjectQuery
which reads a single object by first extracting
* the primary key from the selection object.
*
* Restrictions:
*
* - 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 undefined.
*
- 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.
* - For OneToMany and ManyToMany mappings the elements within the collections and the references attribute values will be added to the expression as disjuncts (OR)
*
*
*
*
* Example:
*
* // 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);
*
* @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.
*
* Sets the default values to be excluded,
* (that includes 0, false, empty String, etc).
* By default if an attribute is null
, and yet has to be included at all times, equality (isNull
)
* is used for the comparison. This is used for searching for an object with a null
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 Expression.equal
to be used
* for comparisons.
*
* For example if an attribute of type int
is
* set to x
in the example object, normally the query will be on all objects
* whose attributes are also equal to x
. The query could however be all
* objects whose attributes are not x
, greater than x
, or even less than or
* equal to x
.
*
* 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.
*
* Note: A special operation can not be used for attributes set to null
. 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
* Integer
, to apply to. Note for int
attributes the
* class is Integer.class
not int.class
. This is not
* the Class
of the example object the attribute is an instance variable of.
* @param operation Name of method in Expression
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.
*
* Required to override the normal behavior which is to ignore an
* attribute of the example object if set to null
, or an excluded value
* like 0
.
*
* Example: To find all projects without a budget set budget
to 0 in the
* example object and call alwaysIncludeAttribute(Project.class, "budget")
* 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.
*
* For instance 0
is used as null
for deciding
* which int
attributes of the example object can be ignored in a
* query by example.
*
* 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.
* The default excluded value for byte
is 0
.
*/
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.
* The default excluded value for char
is '\u0000'
.
*/
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.
* The default excluded value for double
is 0.0
.
*/
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.
* The default excluded value for float
is 0.0f
.
*/
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.
* The default excluded value for int
is 0
.
*/
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.
* The default excluded value for long
is 0
.
*/
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.
* The default excluded value for String
is ""
.
* Note: null
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.
* The default excluded value for short
is 0
.
*/
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.
* The default excluded value for boolean
is false
.
*/
public void excludeValue(boolean value) {
excludeValue(Boolean.valueOf(value));
}
/**
* PUBLIC:
* Attributes to always consider even if set to null
or an excluded
* value like 0
or false
.
* @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 equal
.
* @return A hashtable where the keys are Class
objects and the values
* are the names of operations to use for attributes of that Class
.
* @see #addSpecialOperation
*/
public Map getSpecialOperations() {
return specialOperations;
}
/**
* PUBLIC:
* Decides which attributes to ignore based on the values they are set to.
*
* 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.
*
* 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 int 0
will be stored as Integer(0)
.
* @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.
* Note: Even attributes of the example object that are
* not set, and therefore zero or empty by default, will be included.
* 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.
*
* Primitive values to be removed must first be wrapped inside an Object.
*
* @param value No attributes set to value
will be excluded from a Query By Example.
* value.getClass()
is a key of the Hashtable returned by {@link #getValuesToExclude}.
*
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.
*
Note: null
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 null
attribute in the example object
* either to objects with that attribute also set to null
or to any
* value other than null
.
*
* Set to false
to only select objects where certain attributes have been set.
*
* Example: to find all Employees with an assigned address
, set
* attribute address
to null
in the example object,
* call alwaysIncludeAttribute(Employee.class, "address")
and then
* call setShouldUseEqualityForNulls(false)
.
*
* Note: Unless an attribute set to null
is specifically included, it
* will not be considered at all in the Query By Example.
* @param shouldUseEqualityForNulls If true (by default) uses isNull
else notNull
.
* @see #addSpecialOperation addSpecialOperation
* @see #alwaysIncludeAttribute alwaysIncludeAttribute
*/
public void setShouldUseEqualityForNulls(boolean shouldUseEqualityForNulls) {
this.shouldUseEqualityForNulls = shouldUseEqualityForNulls;
}
/**
* PUBLIC:
* The special operations to use in place of equal
.
* @param newOperations A hashtable where the keys are Class
objects and the values
* are the names of operations to use for attributes of that Class
.
* @see #addSpecialOperation
*/
public void setSpecialOperations(Map newOperations) {
specialOperations = newOperations;
}
/**
* PUBLIC:
* When set to true
the example object will be validated for unsupported mapping types.
* If you wish these mapping types to be ignored either set this flag to false
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.
*
* An attribute of the example domain object set to one of these values will
* be ignored, and not considered in the query.
*
* 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 int 0
will be stored as Integer(0)
.
* @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 null
attribute in the example object
* either to objects with that attribute also set to null
or to any
* value other than null
.
*
* Set to false
to only select objects where certain attributes have been set.
*
* Example: to find all Employees with an assigned address
, set
* attribute address
to null
in the example object,
* call alwaysIncludeAttribute(Employee.class, "address")
and then
* call setShouldUseEqualityForNulls(false)
.
*
* @return If true (by default) uses isNull
else notNull
.
* @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;
}
}