org.apache.cxf.jaxrs.ext.search.SimpleSearchCondition Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cxf.jaxrs.ext.search;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.cxf.jaxrs.ext.search.Beanspector.TypeInfo;
import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheckCondition;
import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheckInfo;
/**
* Simple search condition comparing primitive objects or complex object by its getters. For details see
* {@link #isMet(Object)} description.
*
* @param type of search condition.
*
*/
public class SimpleSearchCondition implements SearchCondition {
private static Set supportedTypes = new HashSet();
static {
supportedTypes.add(ConditionType.EQUALS);
supportedTypes.add(ConditionType.NOT_EQUALS);
supportedTypes.add(ConditionType.GREATER_THAN);
supportedTypes.add(ConditionType.GREATER_OR_EQUALS);
supportedTypes.add(ConditionType.LESS_THAN);
supportedTypes.add(ConditionType.LESS_OR_EQUALS);
}
private ConditionType joiningType = ConditionType.AND;
private T condition;
private List> scts;
/**
* Creates search condition with same operator (equality, inequality) applied in all comparison; see
* {@link #isMet(Object)} for details of comparison.
*
* @param cType shared condition type
* @param condition template object
*/
public SimpleSearchCondition(ConditionType cType, T condition) {
if (cType == null) {
throw new IllegalArgumentException("cType is null");
}
if (condition == null) {
throw new IllegalArgumentException("condition is null");
}
if (!supportedTypes.contains(cType)) {
throw new IllegalArgumentException("unsupported condition type: " + cType.name());
}
this.condition = condition;
scts = createConditions(null, null, null, cType);
}
/**
* Creates search condition with different operators (equality, inequality etc) specified for each getter;
* see {@link #isMet(Object)} for details of comparison. Cannot be used for primitive T type due to
* per-getter comparison strategy.
*
* @param getters2operators getters names and operators to be used with them during comparison
* @param condition template object
*/
public SimpleSearchCondition(Map getters2operators,
Map realGetters,
Map propertyTypeInfo,
T condition) {
if (getters2operators == null) {
throw new IllegalArgumentException("getters2operators is null");
}
if (condition == null) {
throw new IllegalArgumentException("condition is null");
}
if (isBuiltIn(condition)) {
throw new IllegalArgumentException("mapped operators strategy is "
+ "not supported for primitive type "
+ condition.getClass().getName());
}
this.condition = condition;
for (ConditionType ct : getters2operators.values()) {
if (!supportedTypes.contains(ct)) {
throw new IllegalArgumentException("unsupported condition type: " + ct.name());
}
}
scts = createConditions(getters2operators, realGetters, propertyTypeInfo, null);
}
public SimpleSearchCondition(Map getters2operators,
T condition) {
this(getters2operators, null, null, condition);
}
public T getCondition() {
return condition;
}
/**
* {@inheritDoc}
*
* When constructor with map is used it returns null.
*/
public ConditionType getConditionType() {
if (scts.size() > 1) {
return joiningType;
} else {
return scts.get(0).getStatement().getCondition();
}
}
public List> getSearchConditions() {
if (scts.size() > 1) {
return Collections.unmodifiableList(scts);
} else {
return null;
}
}
private List> createConditions(Map getters2operators,
Map realGetters,
Map propertyTypeInfo,
ConditionType sharedType) {
if (isBuiltIn(condition)) {
return Collections.singletonList(
(SearchCondition)new PrimitiveSearchCondition(null, condition, null, sharedType, condition));
} else {
List> list = new ArrayList>();
Map get2val = getGettersAndValues();
Set keySet = get2val != null ? get2val.keySet()
: ((SearchBean)condition).getKeySet();
for (String getter : keySet) {
ConditionType ct = getters2operators == null ? sharedType
: getters2operators.get(getter.toLowerCase());
if (ct == null) {
continue;
}
Object rval = get2val != null
? get2val.get(getter) : ((SearchBean)condition).get(getter);
if (rval == null) {
continue;
}
String realGetter = realGetters != null && realGetters.containsKey(getter)
? realGetters.get(getter) : getter;
TypeInfo tInfo = propertyTypeInfo != null ? propertyTypeInfo.get(getter) : null;
Type genType = tInfo != null ? tInfo.getGenericType() : rval.getClass();
CollectionCheckInfo checkInfo = tInfo != null ? tInfo.getCollectionCheckInfo() : null;
PrimitiveSearchCondition pc = checkInfo == null
? new PrimitiveSearchCondition(realGetter, rval, genType, ct, condition)
: new CollectionCheckCondition(realGetter, rval, genType, ct, condition, checkInfo);
list.add(pc);
}
if (list.isEmpty()) {
throw new IllegalStateException("This search condition is empty and can not be used");
}
return list;
}
}
/**
* Compares given object against template condition object.
*
* For built-in type T like String, Number (precisely, from type T located in subpackage of "java.lang.*")
* given object is directly compared with template object. Comparison for {@link ConditionType#EQUALS}
* requires correct implementation of {@link Object#equals(Object)}, using inequalities requires type T
* implementing {@link Comparable}.
*
* For other types the comparison of given object against template object is done using its
* getters; Value returned by {@linkplain #isMet(Object)} operation is conjunction ('and'
* operator) of comparisons of each getter accessible in object of type T. Getters of template object
* that return null or throw exception are not used in comparison. Finally, if all getters
* return nulls (are excluded) it is interpreted as no filter (match every pojo).
*
* If {@link #SimpleSearchCondition(ConditionType, Object) constructor with shared operator} was used,
* then getters are compared using the same operator. If {@link #SimpleSearchCondition(Map, Object)
* constructor with map of operators} was used then for every getter specified operator is used (getters
* for missing mapping are ignored). The way that comparison per-getter is done depending on operator type
* per getter - comparison for {@link ConditionType#EQUALS} requires correct implementation of
* {@link Object#equals(Object)}, using inequalities requires that getter type implements
* {@link Comparable}.
*
* For equality comparison and String type in template object (either being built-in or getter from client
* provided type) it is allowed to used asterisk at the beginning or at the end of text as wild card (zero
* or more of any characters) e.g. "foo*", "*foo" or "*foo*". Inner asterisks are not interpreted as wild
* cards.
*
* Example:
*
*
* SimpleSearchCondition<Integer> ssc = new SimpleSearchCondition<Integer>(
* ConditionType.GREATER_THAN, 10);
* ssc.isMet(20);
* // true since 20>10
*
* class Entity {
* public String getName() {...
* public int getLevel() {...
* public String getMessage() {...
* }
*
* Entity template = new Entity("bbb", 10, null);
* ssc = new SimpleSearchCondition<Entity>(
* ConditionType.GREATER_THAN, template);
*
* ssc.isMet(new Entity("aaa", 20, "some mesage"));
* // false: is not met, expression '"aaa">"bbb" and 20>10' is not true
* // since "aaa" is not greater than "bbb"; not that message is null in template hence ingored
*
* ssc.isMet(new Entity("ccc", 30, "other message"));
* // true: is met, expression '"ccc">"bbb" and 30>10' is true
*
* Map<String,ConditionType> map;
* map.put("name", ConditionType.EQUALS);
* map.put("level", ConditionType.GREATER_THAN);
* ssc = new SimpleSearchCondition<Entity>(
* ConditionType.GREATER_THAN, template);
*
* ssc.isMet(new Entity("ccc", 30, "other message"));
* // false due to expression '"aaa"=="ccc" and 30>10"' (note different operators)
*
*
*
* @throws IllegalAccessException when security manager disallows reflective call of getters.
*/
public boolean isMet(T pojo) {
for (SearchCondition sc : scts) {
if (!sc.isMet(pojo)) {
return false;
}
}
return true;
}
/**
* Creates cache of getters from template (condition) object and its values returned during one-pass
* invocation. Method isMet() will use its keys to introspect getters of passed pojo object, and values
* from map in comparison.
*
* @return template (condition) object getters mapped to their non-null values
*/
private Map getGettersAndValues() {
if (!SearchBean.class.isAssignableFrom(condition.getClass())) {
Map getters2values = new HashMap();
Beanspector beanspector = new Beanspector(condition);
for (String getter : beanspector.getGettersNames()) {
Object value = getValue(beanspector, getter, condition);
getters2values.put(getter, value);
}
//we do not need compare class objects
getters2values.keySet().remove("class");
return getters2values;
} else {
return null;
}
}
private Object getValue(Beanspector beanspector, String getter, T pojo) {
try {
return beanspector.swap(pojo).getValue(getter);
} catch (Throwable e) {
return null;
}
}
private boolean isBuiltIn(T pojo) {
return pojo.getClass().getName().startsWith("java.lang");
}
public List findAll(Collection pojos) {
List result = new ArrayList();
for (T pojo : pojos) {
if (isMet(pojo)) {
result.add(pojo);
}
}
return result;
}
public String toSQL(String table, String... columns) {
return SearchUtils.toSQL(this, table, columns);
}
public PrimitiveStatement getStatement() {
if (scts.size() == 1) {
return scts.get(0).getStatement();
} else {
return null;
}
}
public void accept(SearchConditionVisitor visitor) {
visitor.visit(this);
}
}