/** * 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.EnumSet; import java.util.HashMap; 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 <T> type of search condition. * */ public class SimpleSearchCondition<T> implements SearchCondition<T> { protected static final Set<ConditionType> SUPPORTED_TYPES = EnumSet.noneOf(ConditionType.class); static { SUPPORTED_TYPES.add(ConditionType.EQUALS); SUPPORTED_TYPES.add(ConditionType.NOT_EQUALS); SUPPORTED_TYPES.add(ConditionType.GREATER_THAN); SUPPORTED_TYPES.add(ConditionType.GREATER_OR_EQUALS); SUPPORTED_TYPES.add(ConditionType.LESS_THAN); SUPPORTED_TYPES.add(ConditionType.LESS_OR_EQUALS); } private final ConditionType joiningType = ConditionType.AND; private T condition; private List<SearchCondition<T>> 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 (!SUPPORTED_TYPES.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 realGetters * @param propertyTypeInfo * @param condition template object */ public SimpleSearchCondition(Map<String, ConditionType> getters2operators, Map<String, String> realGetters, Map<String, TypeInfo> 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 (!SUPPORTED_TYPES.contains(ct)) { throw new IllegalArgumentException("unsupported condition type: " + ct.name()); } } scts = createConditions(getters2operators, realGetters, propertyTypeInfo, null); } public SimpleSearchCondition(Map<String, ConditionType> getters2operators, T condition) { this(getters2operators, null, null, condition); } @Override public T getCondition() { return condition; } /** * {@inheritDoc} * <p> * When constructor with map is used it returns null. */ @Override public ConditionType getConditionType() { if (scts.size() > 1) { return joiningType; } else { return scts.get(0).getStatement().getCondition(); } } @Override public List<SearchCondition<T>> getSearchConditions() { if (scts.size() > 1) { return Collections.unmodifiableList(scts); } else { return null; } } private List<SearchCondition<T>> createConditions(Map<String, ConditionType> getters2operators, Map<String, String> realGetters, Map<String, TypeInfo> propertyTypeInfo, ConditionType sharedType) { if (isBuiltIn(condition)) { return Collections.singletonList( (SearchCondition<T>)new PrimitiveSearchCondition<>(null, condition, null, sharedType, condition)); } else { List<SearchCondition<T>> list = new ArrayList<>(); Map<String, Object> get2val = getGettersAndValues(); Set<String> 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<T> 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. * <p> * 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}. * <p> * For other types the comparison of given object against template object is done using its * <b>getters</b>; Value returned by {@linkplain #isMet(Object)} operation is <b>conjunction ('and' * operator)</b> 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). * <p> * 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}. * <p> * 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. * <p> * <b>Example:</b> * * <pre> * 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) * * </pre> * * @throws IllegalAccessException when security manager disallows reflective call of getters. */ @Override public boolean isMet(T pojo) { for (SearchCondition<T> 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<String, Object> getGettersAndValues() { if (!SearchBean.class.isAssignableFrom(condition.getClass())) { Map<String, Object> getters2values = new HashMap<>(); Beanspector<T> 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<T> 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"); } @Override public List<T> findAll(Collection<T> pojos) { List<T> 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); } @Override public PrimitiveStatement getStatement() { if (scts.size() == 1) { return scts.get(0).getStatement(); } else { return null; } } @Override public void accept(SearchConditionVisitor<T, ?> visitor) { visitor.visit(this); } }