package org.radargun.stages.query; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import org.radargun.config.DefinitionElement; import org.radargun.config.Init; import org.radargun.config.Property; import org.radargun.config.PropertyHelper; import org.radargun.traits.Query; import org.radargun.utils.NumberConverter; import org.radargun.utils.ObjectConverter; import org.radargun.utils.RandomValue; import org.radargun.utils.ReflexiveConverters; /** * Definition elements that formulate the condition that should be * used in {@link org.radargun.traits.Query.Builder}. * * @author Radim Vansa <rvansa@redhat.com> */ public abstract class Condition { protected static final Class<? extends SelectExpressionElement>[] SELECT_EXPRESSIONS = new Class[]{Attribute.class, Count.class, Sum.class, Avg.class, Min.class, Max.class}; protected static final Class<? extends OrderedSelectExpressionElement>[] ORDERED_SELECT_EXPRESSIONS = new Class[] {OrderedAttribute.class, OrderedCount.class, OrderedSum.class, OrderedAvg.class, OrderedMin.class, OrderedMax.class}; public abstract void apply(Query.Builder builder); public String toString() { return PropertyHelper.getDefinitionElementName(getClass()) + PropertyHelper.toString(this); } public abstract static class PathCondition extends Condition { @Property(doc = "Target path (field on the queried object or path through embedded objects)") public String path; @Property(doc = "Target path, can be aggregated.", complexConverter = SelectExpressionConverter.class) public SelectExpressionElement aggregatedPath; Query.SelectExpression resolvedPath; public void apply(Query.Builder builder) { if (path != null) { resolvedPath = new Query.SelectExpression(path); } if (aggregatedPath != null) { resolvedPath = aggregatedPath.toSelectExpression(); } if (path != null && aggregatedPath != null) { throw new IllegalStateException("Condition can only have a single target path!"); } if (resolvedPath == null) { throw new IllegalStateException("You need to specify a target path for the condition!"); } } } @DefinitionElement(name = "eq", doc = "Target is equal to value") public static class Eq extends PathCondition { @Property(doc = "Value used in the condition.", converter = ObjectConverter.class) public Object value; @Property(doc = "Random value to be generated during query build time.", complexConverter = RandomValue.PrimitiveConverter.class) public RandomValue random; @Init public void init() { if (value == null && random == null) throw new IllegalStateException("Define either value or random"); if (value != null && random != null) throw new IllegalStateException("Define one of: value, random"); } @Override public void apply(Query.Builder builder) { super.apply(builder); builder.eq(resolvedPath, value != null ? value : random.nextValue(ThreadLocalRandom.current())); } } public abstract static class PathNumberCondition extends PathCondition { @Property(doc = "Value used in the condition", converter = NumberConverter.class) public Number value; @Property(doc = "Random value to be generated during query build time.", complexConverter = RandomValue.NumberConverter.class) public RandomValue<Number> random; @Init public void init() { if (value == null && random == null) throw new IllegalStateException("Define either value or random"); if (value != null && random != null) throw new IllegalStateException("Define one of: value, random"); } protected Number getValue() { return value != null ? value : random.nextValue(ThreadLocalRandom.current()); } } @DefinitionElement(name = "lt", doc = "Target is < than value") public static class Lt extends PathNumberCondition { @Override public void apply(Query.Builder builder) { super.apply(builder); builder.lt(resolvedPath, getValue()); } } @DefinitionElement(name = "le", doc = "Target is <= than value") public static class Le extends PathNumberCondition { @Override public void apply(Query.Builder builder) { super.apply(builder); builder.le(resolvedPath, getValue()); } } @DefinitionElement(name = "gt", doc = "Target is > than value") public static class Gt extends PathNumberCondition { @Override public void apply(Query.Builder builder) { super.apply(builder); builder.gt(resolvedPath, getValue()); } } @DefinitionElement(name = "ge", doc = "Target is >= than value") public static class Ge extends PathNumberCondition { @Override public void apply(Query.Builder builder) { super.apply(builder); builder.ge(resolvedPath, getValue()); } } @DefinitionElement(name = "between", doc = "Target is between two values") public static class Between extends PathCondition { @Property(doc = "Lower bound for the value", converter = NumberConverter.class) public Number lowerBound; @Property(doc = "Random lower bound to be generated during query build time.", complexConverter = RandomValue.NumberConverter.class) public RandomValue<Number> randomLowerBound; @Property(doc = "Does the range include the lower-bound? Default is true.") public boolean lowerInclusive = true; @Property(doc = "Upper bound for the value", converter = NumberConverter.class) public Number upperBound; @Property(doc = "Random upper bound to be generated during query build time.", complexConverter = RandomValue.NumberConverter.class) public RandomValue<Number> randomUpperBound; @Property(doc = "Does the range include the upper-bound? Default is true.") public boolean upperInclusive = true; @Init public void init() { if (lowerBound == null && randomLowerBound == null) throw new IllegalStateException("Define either lowerBound or randomLowerBound"); if (lowerBound != null && randomLowerBound != null) throw new IllegalStateException("Define one of: lowerBound, randomLowerBound"); if (upperBound == null && randomUpperBound == null) throw new IllegalStateException("Define either upperBound or randomUpperBound"); if (upperBound != null && randomUpperBound != null) throw new IllegalStateException("Define one of: upperBound, randomUpperBound"); } @Override public void apply(Query.Builder builder) { super.apply(builder); Number lowerBound = this.lowerBound != null ? this.lowerBound : randomLowerBound.nextValue(ThreadLocalRandom.current()); Number upperBound = this.upperBound != null ? this.upperBound : randomUpperBound.nextValue(ThreadLocalRandom.current()); builder.between(resolvedPath, lowerBound, lowerInclusive, upperBound, upperInclusive); } } @DefinitionElement(name = "like", doc = "Target string matches the value") public static class Like extends PathCondition { @Property(doc = "Value used in the condition") public String value; @Property(doc = "Random value to be generated during query build time.", complexConverter = RandomValue.StringConverter.class) public RandomValue<String> random; @Init public void init() { if (value == null && random == null) throw new IllegalStateException("Define either value or random"); if (value != null && random != null) throw new IllegalStateException("Define one of: value, random"); } @Override public void apply(Query.Builder builder) { super.apply(builder); builder.like(resolvedPath, value != null ? value : random.nextValue(ThreadLocalRandom.current())); } } @DefinitionElement(name = "contains", doc = "Target is collection containing the value") public static class Contains extends PathCondition { @Property(doc = "Value used in the condition", converter = ObjectConverter.class) public Object value; @Property(doc = "Random value to be generated during query build time.", complexConverter = RandomValue.PrimitiveConverter.class) public RandomValue<Object> random; @Init public void init() { if (value == null && random == null) throw new IllegalStateException("Define either value or random"); if (value != null && random != null) throw new IllegalStateException("Define one of: value, random"); } @Override public void apply(Query.Builder builder) { super.apply(builder); builder.contains(resolvedPath, value != null ? value : random.nextValue(ThreadLocalRandom.current())); } } @DefinitionElement(name = "is-null", doc = "Target is not defined (null)") public static class IsNull extends PathCondition { @Override public void apply(Query.Builder builder) { super.apply(builder); builder.isNull(resolvedPath); } } @DefinitionElement(name = "not", doc = "All inner conditions are false", resolveType = DefinitionElement.ResolveType.PASS_BY_DEFINITION) public static class Not extends Condition { @Property(name = "", doc = "Inner conditions", complexConverter = ConditionConverter.class) public final List<Condition> subs = new ArrayList<>(); @Override public void apply(Query.Builder builder) { Query.Builder subBuilder = builder.subquery(); for (Condition sub : subs) { sub.apply(subBuilder); } builder.not(subBuilder); } } @DefinitionElement(name = "any", doc = "Any of inner conditions is true", resolveType = DefinitionElement.ResolveType.PASS_BY_DEFINITION) public static class Any extends Condition { @Property(name = "", doc = "Inner conditions", complexConverter = ConditionConverter.class) public final List<Condition> subs = new ArrayList<>(); @Override public void apply(Query.Builder builder) { Query.Builder[] subBuilders = new Query.Builder[subs.size()]; int i = 0; for (Condition sub : subs) { Query.Builder subBuilder = builder.subquery(); sub.apply(subBuilder); subBuilders[i++] = subBuilder; } builder.any(subBuilders); } } @DefinitionElement(name = "all", doc = "All inner conditions are true", resolveType = DefinitionElement.ResolveType.PASS_BY_DEFINITION) public static class All extends Condition { @Property(name = "", doc = "Inner conditions", complexConverter = ConditionConverter.class) public final List<Condition> subs = new ArrayList<>(); @Override public void apply(Query.Builder builder) { for (Condition sub : subs) { sub.apply(builder); } } } protected abstract static class SelectExpressionElement { @Property(doc = "Target path (field on the queried object or path through embedded objects)", optional = false) public String path; public abstract Query.SelectExpression toSelectExpression(); public String toString() { return PropertyHelper.getDefinitionElementName(getClass()) + PropertyHelper.toString(this); } } @DefinitionElement(name = "attribute", doc = "Simple attribute projection, with no aggregation function.") protected static class Attribute extends SelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.NONE); } } @DefinitionElement(name = "count", doc = "Count aggregation.") protected static class Count extends SelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.COUNT); } } @DefinitionElement(name = "sum", doc = "Sum aggregation.") protected static class Sum extends SelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.SUM); } } @DefinitionElement(name = "avg", doc = "Average aggregation.") protected static class Avg extends SelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.AVG); } } @DefinitionElement(name = "min", doc = "Minimum aggregation.") protected static class Min extends SelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.MIN); } } @DefinitionElement(name = "max", doc = "Max aggregation.") protected static class Max extends SelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.MAX); } } protected abstract static class OrderedSelectExpressionElement extends SelectExpressionElement { @Property(doc = "Whether the column should be ordered in ascending order. Default is true, and false means descending.") protected boolean asc = true; public abstract Query.SelectExpression toSelectExpression(); } @DefinitionElement(name = "attribute", doc = "Simple attribute projection, with no aggregation function.") protected static class OrderedAttribute extends OrderedSelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.NONE, asc); } } @DefinitionElement(name = "count", doc = "Count aggregation.") protected static class OrderedCount extends OrderedSelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.COUNT, asc); } } @DefinitionElement(name = "sum", doc = "Sum aggregation.") protected static class OrderedSum extends OrderedSelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.SUM, asc); } } @DefinitionElement(name = "avg", doc = "Average aggregation.") protected static class OrderedAvg extends OrderedSelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.AVG, asc); } } @DefinitionElement(name = "min", doc = "Minimum aggregation.") protected static class OrderedMin extends OrderedSelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.MIN, asc); } } @DefinitionElement(name = "max", doc = "Max aggregation.") protected static class OrderedMax extends OrderedSelectExpressionElement { @Override public Query.SelectExpression toSelectExpression() { return new Query.SelectExpression(path, Query.AggregationFunction.MAX, asc); } } protected static class ProjectionConverter extends ReflexiveConverters.ListConverter { public ProjectionConverter() { super(SELECT_EXPRESSIONS); } } protected static class SelectExpressionConverter extends ReflexiveConverters.ObjectConverter { public SelectExpressionConverter() { super(SELECT_EXPRESSIONS); } } protected static class AggregatedSortConverter extends ReflexiveConverters.ListConverter { public AggregatedSortConverter() { super(ORDERED_SELECT_EXPRESSIONS); } } public static class ConditionConverter extends ReflexiveConverters.ListConverter { public ConditionConverter() { super(new Class[] {Eq.class, Lt.class, Le.class, Gt.class, Ge.class, Between.class, Like.class, Contains.class, IsNull.class, Not.class, Any.class, All.class}); } } }