KEYCLOAK-19749 Optimize DefaultModelCriteria creation

This commit is contained in:
Hynek Mlnarik 2021-11-01 13:18:52 +01:00 committed by Hynek Mlnařík
parent b37f2d5915
commit 6966e0cfe9
7 changed files with 298 additions and 56 deletions

View file

@ -117,7 +117,7 @@ public interface ModelCriteriaBuilder<M> {
* @return
* @throws CriterionNotSupported If the operator is not supported for the given field.
*/
ModelCriteriaBuilder<M> compare(SearchableModelField<M> modelField, Operator op, Object... value);
ModelCriteriaBuilder<M> compare(SearchableModelField<? super M> modelField, Operator op, Object... value);
/**
* Creates and returns a new instance of {@code ModelCriteriaBuilder} that

View file

@ -53,7 +53,7 @@ public class ConcurrentHashMapStorage<K, V extends AbstractEntity & UpdatableEnt
protected final ConcurrentMap<K, V> store = new ConcurrentHashMap<>();
protected final Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates;
protected final Map<SearchableModelField<? super M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates;
protected final StringKeyConvertor<K> keyConvertor;
protected final DeepCloner cloner;

View file

@ -527,7 +527,7 @@ public class MapFieldPredicates {
}
@SuppressWarnings("unchecked")
public static <K, V extends AbstractEntity, M> Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> getPredicates(Class<M> clazz) {
public static <K, V extends AbstractEntity, M> Map<SearchableModelField<? super M>, UpdatePredicatesFunc<K, V, M>> getPredicates(Class<M> clazz) {
return PREDICATES.get(clazz);
}
}

View file

@ -43,14 +43,14 @@ public class MapModelCriteriaBuilder<K, V extends AbstractEntity, M> implements
private static final Predicate<Object> ALWAYS_FALSE = (e) -> false;
private final Predicate<? super K> keyFilter;
private final Predicate<? super V> entityFilter;
private final Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates;
private final Map<SearchableModelField<? super M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates;
private final StringKeyConvertor<K> keyConvertor;
public MapModelCriteriaBuilder(StringKeyConvertor<K> keyConvertor, Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates) {
public MapModelCriteriaBuilder(StringKeyConvertor<K> keyConvertor, Map<SearchableModelField<? super M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates) {
this(keyConvertor, fieldPredicates, ALWAYS_TRUE, ALWAYS_TRUE);
}
private MapModelCriteriaBuilder(StringKeyConvertor<K> keyConvertor, Map<SearchableModelField<M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates, Predicate<? super K> indexReadFilter, Predicate<? super V> sequentialReadFilter) {
private MapModelCriteriaBuilder(StringKeyConvertor<K> keyConvertor, Map<SearchableModelField<? super M>, UpdatePredicatesFunc<K, V, M>> fieldPredicates, Predicate<? super K> indexReadFilter, Predicate<? super V> sequentialReadFilter) {
this.keyConvertor = keyConvertor;
this.fieldPredicates = fieldPredicates;
this.keyFilter = indexReadFilter;
@ -58,7 +58,7 @@ public class MapModelCriteriaBuilder<K, V extends AbstractEntity, M> implements
}
@Override
public MapModelCriteriaBuilder<K, V, M> compare(SearchableModelField<M> modelField, Operator op, Object... values) {
public MapModelCriteriaBuilder<K, V, M> compare(SearchableModelField<? super M> modelField, Operator op, Object... values) {
UpdatePredicatesFunc<K, V, M> method = fieldPredicates.get(modelField);
if (method == null) {
throw new IllegalArgumentException("Filter not implemented for field " + modelField);

View file

@ -19,6 +19,8 @@ package org.keycloak.models.map.storage.criteria;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.criteria.ModelCriteriaNode.ExtOperator;
import org.keycloak.storage.SearchableModelField;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
/**
* Descriptive model criteria implementation which in other words represents a Boolean formula on searchable fields.
@ -26,55 +28,113 @@ import org.keycloak.storage.SearchableModelField;
*/
public class DefaultModelCriteria<M> implements ModelCriteriaBuilder<M> {
private final ModelCriteriaNode<M> node;
private static final DefaultModelCriteria<?> INSTANCE = new DefaultModelCriteria<>(null);
public DefaultModelCriteria() {
this.node = null;
}
private final ModelCriteriaNode<M> node;
private DefaultModelCriteria(ModelCriteriaNode<M> node) {
this.node = node;
}
public static <M> DefaultModelCriteria<M> criteria() {
return (DefaultModelCriteria<M>) INSTANCE;
}
@Override
public DefaultModelCriteria<M> compare(SearchableModelField<M> modelField, Operator op, Object... value) {
final ModelCriteriaNode<M> targetNode;
public DefaultModelCriteria<M> compare(SearchableModelField<? super M> modelField, Operator op, Object... value) {
return compare(new ModelCriteriaNode<>(modelField, op, value));
}
private DefaultModelCriteria<M> compare(final ModelCriteriaNode<M> nodeToAdd) {
ModelCriteriaNode<M> targetNode;
if (isEmpty()) {
targetNode = new ModelCriteriaNode<>(modelField, op, value);
targetNode = nodeToAdd;
} else if (node.getNodeOperator() == ExtOperator.AND) {
targetNode = node.cloneTree();
targetNode.addChild(new ModelCriteriaNode<>(modelField, op, value));
targetNode.addChild(nodeToAdd);
} else {
targetNode = new ModelCriteriaNode<>(ExtOperator.AND);
targetNode.addChild(node.cloneTree());
targetNode.addChild(new ModelCriteriaNode<>(modelField, op, value));
targetNode.addChild(nodeToAdd);
}
return new DefaultModelCriteria<>(targetNode);
}
@Override
public DefaultModelCriteria<M> and(ModelCriteriaBuilder<M>... mcbs) {
final ModelCriteriaNode<M> targetNode = new ModelCriteriaNode<>(ExtOperator.AND);
for (ModelCriteriaBuilder<M> mcb : mcbs) {
targetNode.addChild(((DefaultModelCriteria<M>) mcb.unwrap(DefaultModelCriteria.class)).node);
if (mcbs.length == 1) {
ModelCriteriaNode<M> toBeChild = ((DefaultModelCriteria<M>) mcbs[0].unwrap(DefaultModelCriteria.class)).node;
if (toBeChild.getNodeOperator() == ExtOperator.AND || toBeChild.getNodeOperator() == ExtOperator.OR) {
return ((DefaultModelCriteria<M>) mcbs[0].unwrap(DefaultModelCriteria.class));
}
}
return new DefaultModelCriteria<>(targetNode);
final ModelCriteriaNode<M> targetNode = new ModelCriteriaNode<>(ExtOperator.AND);
AtomicBoolean hasFalseNode = new AtomicBoolean(false);
for (ModelCriteriaBuilder<M> mcb : mcbs) {
final ModelCriteriaNode<M> nodeToAdd = ((DefaultModelCriteria<M>) mcb.unwrap(DefaultModelCriteria.class)).node;
getNodesToAddForAndOr(nodeToAdd, ExtOperator.AND)
.filter(ModelCriteriaNode::isNotTrueNode)
.peek(n -> { if (n.isFalseNode()) hasFalseNode.lazySet(true); })
.map(ModelCriteriaNode::cloneTree)
.forEach(targetNode::addChild);
if (hasFalseNode.get()) {
return compare(new ModelCriteriaNode<>(ExtOperator.__FALSE__));
}
}
if (targetNode.getChildren().isEmpty()) {
// AND on empty set of formulae is TRUE: It does hold that there all formulae are satisfied
return compare(new ModelCriteriaNode<>(ExtOperator.__TRUE__));
}
return compare(targetNode);
}
@Override
public DefaultModelCriteria<M> or(ModelCriteriaBuilder<M>... mcbs) {
final ModelCriteriaNode<M> targetNode = new ModelCriteriaNode<>(ExtOperator.OR);
for (ModelCriteriaBuilder<M> mcb : mcbs) {
targetNode.addChild(((DefaultModelCriteria<M>) mcb.unwrap(DefaultModelCriteria.class)).node);
if (mcbs.length == 1) {
ModelCriteriaNode<M> toBeChild = ((DefaultModelCriteria<M>) mcbs[0].unwrap(DefaultModelCriteria.class)).node;
if (toBeChild.getNodeOperator() == ExtOperator.AND || toBeChild.getNodeOperator() == ExtOperator.OR) {
return ((DefaultModelCriteria<M>) mcbs[0].unwrap(DefaultModelCriteria.class));
}
}
return new DefaultModelCriteria<>(targetNode);
final ModelCriteriaNode<M> targetNode = new ModelCriteriaNode<>(ExtOperator.OR);
AtomicBoolean hasTrueNode = new AtomicBoolean(false);
for (ModelCriteriaBuilder<M> mcb : mcbs) {
final ModelCriteriaNode<M> nodeToAdd = ((DefaultModelCriteria<M>) mcb.unwrap(DefaultModelCriteria.class)).node;
getNodesToAddForAndOr(nodeToAdd, ExtOperator.OR)
.filter(ModelCriteriaNode::isNotFalseNode)
.peek(n -> { if (n.isTrueNode()) hasTrueNode.lazySet(true); })
.map(ModelCriteriaNode::cloneTree)
.forEach(targetNode::addChild);
if (hasTrueNode.get()) {
return compare(new ModelCriteriaNode<>(ExtOperator.__TRUE__));
}
}
if (targetNode.getChildren().isEmpty()) {
// OR on empty set of formulae is FALSE: It does not hold that there is at least one satisfied formula
return compare(new ModelCriteriaNode<>(ExtOperator.__FALSE__));
}
return compare(targetNode);
}
@Override
public DefaultModelCriteria<M> not(ModelCriteriaBuilder<M> mcb) {
final ModelCriteriaNode<M> targetNode = new ModelCriteriaNode<>(ExtOperator.NOT);
targetNode.addChild(((DefaultModelCriteria<M>) mcb.unwrap(DefaultModelCriteria.class)).node);
return new DefaultModelCriteria<>(targetNode);
ModelCriteriaNode<M> toBeChild = ((DefaultModelCriteria<M>) mcb.unwrap(DefaultModelCriteria.class)).node;
if (toBeChild.getNodeOperator() == ExtOperator.NOT) {
return compare(toBeChild.getChildren().get(0).cloneTree());
}
targetNode.addChild(toBeChild.cloneTree());
return compare(targetNode);
}
/**
@ -83,10 +143,35 @@ public class DefaultModelCriteria<M> implements ModelCriteriaBuilder<M> {
* @param mcb {@code ModelCriteriaBuilder} to copy the contents onto
* @return Updated {@code ModelCriteriaBuilder}
*/
public ModelCriteriaBuilder<M> flashToModelCriteriaBuilder(ModelCriteriaBuilder<M> mcb) {
public <C extends ModelCriteriaBuilder<M>> C flashToModelCriteriaBuilder(C mcb) {
return mcb == null ? null : node.flashToModelCriteriaBuilder(mcb);
}
/**
* Optimizes this formula into another {@code ModelCriteriaBuilder}, using the values of
* {@link ExtOperator#__TRUE__} and {@link ExtOperator#__FALSE__} accordingly.
* @return New instance of {@code }
*/
public DefaultModelCriteria<M> optimize() {
return flashToModelCriteriaBuilder(criteria());
}
@FunctionalInterface
public interface AtomicFormulaTester<M> {
public Boolean test(SearchableModelField<? super M> field, Operator operator, Object[] operatorArguments);
}
public DefaultModelCriteria<M> partiallyEvaluate(AtomicFormulaTester<M> tester) {
return new DefaultModelCriteria<>(node.cloneTree((field, operator, operatorArguments) -> {
Boolean res = tester.test(field, operator, operatorArguments);
if (res == null) {
return new ModelCriteriaNode<>(field, operator, operatorArguments);
} else {
return new ModelCriteriaNode<>(res ? ExtOperator.__TRUE__ : ExtOperator.__FALSE__);
}
}, ModelCriteriaNode::new));
}
public boolean isEmpty() {
return node == null;
}
@ -96,4 +181,14 @@ public class DefaultModelCriteria<M> implements ModelCriteriaBuilder<M> {
return isEmpty() ? "" : node.toString();
}
private Stream<ModelCriteriaNode<M>> getNodesToAddForAndOr(ModelCriteriaNode<M> nodeToAdd, ExtOperator operatorBeingAdded) {
final ExtOperator op = nodeToAdd.getNodeOperator();
if (op == operatorBeingAdded) {
return nodeToAdd.getChildren().stream();
}
return Stream.of(nodeToAdd);
}
}

View file

@ -23,17 +23,19 @@ import org.keycloak.storage.SearchableModelField;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
* TODO: Introduce separation of parameter values and the structure
* @author hmlnarik
*/
public class ModelCriteriaNode<M> extends DefaultTreeNode<ModelCriteriaNode<M>> {
public static enum ExtOperator {
AND {
@Override public <M> ModelCriteriaBuilder<M> apply(ModelCriteriaBuilder<M> mcb, ModelCriteriaNode<M> node) {
@Override public <M, C extends ModelCriteriaBuilder<M>> C apply(C mcb, ModelCriteriaNode<M> node) {
if (node.getChildren().isEmpty()) {
return null;
}
@ -41,14 +43,14 @@ public class ModelCriteriaNode<M> extends DefaultTreeNode<ModelCriteriaNode<M>>
.map(n -> n.flashToModelCriteriaBuilder(mcb))
.filter(Objects::nonNull)
.toArray(ModelCriteriaBuilder[]::new);
return operands.length == 0 ? null : mcb.and(operands);
return operands.length == 0 ? null : (C) mcb.and(operands);
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "(" + node.getChildren().stream().map(ModelCriteriaNode::toString).collect(Collectors.joining(" && ")) + ")";
}
},
OR {
@Override public <M> ModelCriteriaBuilder<M> apply(ModelCriteriaBuilder<M> mcb, ModelCriteriaNode<M> node) {
@Override public <M, C extends ModelCriteriaBuilder<M>> C apply(C mcb, ModelCriteriaNode<M> node) {
if (node.getChildren().isEmpty()) {
return null;
}
@ -56,23 +58,23 @@ public class ModelCriteriaNode<M> extends DefaultTreeNode<ModelCriteriaNode<M>>
.map(n -> n.flashToModelCriteriaBuilder(mcb))
.filter(Objects::nonNull)
.toArray(ModelCriteriaBuilder[]::new);
return operands.length == 0 ? null : mcb.or(operands);
return operands.length == 0 ? null : (C) mcb.or(operands);
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "(" + node.getChildren().stream().map(ModelCriteriaNode::toString).collect(Collectors.joining(" || ")) + ")";
}
},
NOT {
@Override public <M> ModelCriteriaBuilder<M> apply(ModelCriteriaBuilder<M> mcb, ModelCriteriaNode<M> node) {
return mcb.not(node.getChildren().iterator().next().flashToModelCriteriaBuilder(mcb));
@Override public <M, C extends ModelCriteriaBuilder<M>> C apply(C mcb, ModelCriteriaNode<M> node) {
return (C) mcb.not(node.getChildren().iterator().next().flashToModelCriteriaBuilder(mcb));
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "! " + node.getChildren().iterator().next().toString();
}
},
SIMPLE_OPERATOR {
@Override public <M> ModelCriteriaBuilder<M> apply(ModelCriteriaBuilder<M> mcb, ModelCriteriaNode<M> node) {
return mcb.compare(
ATOMIC_FORMULA {
@Override public <M, C extends ModelCriteriaBuilder<M>> C apply(C mcb, ModelCriteriaNode<M> node) {
return (C) mcb.compare(
node.field,
node.simpleOperator,
node.simpleOperatorArguments
@ -82,9 +84,25 @@ public class ModelCriteriaNode<M> extends DefaultTreeNode<ModelCriteriaNode<M>>
return node.field.getName() + " " + node.simpleOperator + " " + Arrays.deepToString(node.simpleOperatorArguments);
}
},
__FALSE__ {
@Override public <M, C extends ModelCriteriaBuilder<M>> C apply(C mcb, ModelCriteriaNode<M> node) {
return (C) mcb.or();
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "__FALSE__";
}
},
__TRUE__ {
@Override public <M, C extends ModelCriteriaBuilder<M>> C apply(C mcb, ModelCriteriaNode<M> node) {
return (C) mcb.and();
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "__TRUE__";
}
}
;
public abstract <M> ModelCriteriaBuilder<M> apply(ModelCriteriaBuilder<M> mcbCreator, ModelCriteriaNode<M> node);
public abstract <M, C extends ModelCriteriaBuilder<M>> C apply(C mcbCreator, ModelCriteriaNode<M> node);
public abstract String toString(ModelCriteriaNode<?> node);
}
@ -92,16 +110,27 @@ public class ModelCriteriaNode<M> extends DefaultTreeNode<ModelCriteriaNode<M>>
private final Operator simpleOperator;
private final SearchableModelField<M> field;
private final SearchableModelField<? super M> field;
private final Object[] simpleOperatorArguments;
public ModelCriteriaNode(SearchableModelField<M> field, Operator simpleOperator, Object... simpleOperatorArguments) {
public ModelCriteriaNode(SearchableModelField<? super M> field, Operator simpleOperator, Object[] simpleOperatorArguments) {
super(Collections.emptyMap());
this.simpleOperator = simpleOperator;
this.field = field;
this.simpleOperatorArguments = simpleOperatorArguments;
this.nodeOperator = ExtOperator.SIMPLE_OPERATOR;
this.nodeOperator = ExtOperator.ATOMIC_FORMULA;
if (simpleOperatorArguments != null) {
for (int i = 0; i < simpleOperatorArguments.length; i ++) {
Object arg = simpleOperatorArguments[i];
if (arg instanceof Stream) {
try (Stream<?> sArg = (Stream<?>) arg) {
simpleOperatorArguments[i] = sArg.collect(Collectors.toList());
}
}
}
}
}
public ModelCriteriaNode(ExtOperator nodeOperator) {
@ -112,7 +141,7 @@ public class ModelCriteriaNode<M> extends DefaultTreeNode<ModelCriteriaNode<M>>
this.simpleOperatorArguments = null;
}
private ModelCriteriaNode(ExtOperator nodeOperator, Operator simpleOperator, SearchableModelField<M> field, Object[] simpleOperatorArguments) {
private ModelCriteriaNode(ExtOperator nodeOperator, Operator simpleOperator, SearchableModelField<? super M> field, Object[] simpleOperatorArguments) {
super(Collections.emptyMap());
this.nodeOperator = nodeOperator;
this.simpleOperator = simpleOperator;
@ -125,11 +154,40 @@ public class ModelCriteriaNode<M> extends DefaultTreeNode<ModelCriteriaNode<M>>
}
public ModelCriteriaNode<M> cloneTree() {
return cloneTree(n -> new ModelCriteriaNode<>(n.nodeOperator, n.simpleOperator, n.field, n.simpleOperatorArguments));
return cloneTree(ModelCriteriaNode::new, ModelCriteriaNode::new);
}
public ModelCriteriaBuilder<M> flashToModelCriteriaBuilder(ModelCriteriaBuilder<M> mcb) {
final ModelCriteriaBuilder<M> res = nodeOperator.apply(mcb, this);
@FunctionalInterface
public interface AtomicFormulaInstantiator<M> {
public ModelCriteriaNode<M> instantiate(SearchableModelField<? super M> field, Operator operator, Object[] operatorArguments);
}
public ModelCriteriaNode<M> cloneTree(AtomicFormulaInstantiator<M> atomicFormulaInstantiator, Function<ExtOperator, ModelCriteriaNode<M>> booleanNodeInstantiator) {
return cloneTree(n ->
n.getNodeOperator() == ExtOperator.ATOMIC_FORMULA
? atomicFormulaInstantiator.instantiate(n.field, n.simpleOperator, n.simpleOperatorArguments)
: booleanNodeInstantiator.apply(n.nodeOperator)
);
}
public boolean isFalseNode() {
return getNodeOperator() == ExtOperator.__FALSE__;
}
public boolean isNotFalseNode() {
return getNodeOperator() != ExtOperator.__FALSE__;
}
public boolean isTrueNode() {
return getNodeOperator() == ExtOperator.__TRUE__;
}
public boolean isNotTrueNode() {
return getNodeOperator() != ExtOperator.__TRUE__;
}
public <C extends ModelCriteriaBuilder<M>> C flashToModelCriteriaBuilder(C mcb) {
final C res = nodeOperator.apply(mcb, this);
return res == null ? mcb : res;
}

View file

@ -17,11 +17,14 @@
package org.keycloak.models.map.storage.criteria;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import java.util.Arrays;
import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.Test;
import static org.hamcrest.Matchers.hasToString;
import static org.keycloak.models.ClientModel.SearchableFields.*;
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
/**
*
@ -31,42 +34,71 @@ public class DefaultModelCriteriaTest {
@Test
public void testSimpleCompare() {
DefaultModelCriteria<ClientModel> v = new DefaultModelCriteria<>();
DefaultModelCriteria<ClientModel> v = criteria();
assertThat(v.compare(CLIENT_ID, Operator.EQ, 3), hasToString("clientId EQ [3]"));
assertThat(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5), hasToString("(clientId EQ [4] && id EQ [5])"));
}
@Test
public void testSimpleCompareAnd() {
DefaultModelCriteria<ClientModel> v = new DefaultModelCriteria<>();
DefaultModelCriteria<ClientModel> v = criteria();
assertThat(v.and(), hasToString("__TRUE__"));
assertThat(v.and(v.or()), hasToString("__FALSE__"));
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 3)), hasToString("(clientId EQ [3])"));
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)), hasToString("((clientId EQ [4] && id EQ [5]))"));
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 3), v.or()), hasToString("__FALSE__"));
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] && id EQ [5])"));
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] && id EQ [5])"));
}
@Test
public void testSimpleCompareOr() {
DefaultModelCriteria<ClientModel> v = new DefaultModelCriteria<>();
DefaultModelCriteria<ClientModel> v = criteria();
assertThat(v.or(), hasToString("__FALSE__"));
assertThat(v.or(v.and()), hasToString("__TRUE__"));
assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 3)), hasToString("(clientId EQ [3])"));
assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)), hasToString("((clientId EQ [4] && id EQ [5]))"));
assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 3), v.and()), hasToString("__TRUE__"));
assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] && id EQ [5])"));
assertThat(v.or(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5)), hasToString("(clientId EQ [4] || id EQ [5])"));
assertThat(v.or(v.or(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5))), hasToString("(clientId EQ [4] || id EQ [5])"));
assertThat(v.and(v.or(v.compare(CLIENT_ID, Operator.EQ, 4), v.compare(ID, Operator.EQ, 5))), hasToString("(clientId EQ [4] || id EQ [5])"));
}
@Test
public void testSimpleCompareAndOr() {
DefaultModelCriteria<ClientModel> v = new DefaultModelCriteria<>();
DefaultModelCriteria<ClientModel> v = criteria();
assertThat(v.or(
v.and(
v.compare(CLIENT_ID, Operator.EQ, 4),
v.compare(ID, Operator.EQ, 5)
),
v.compare(ATTRIBUTE, Operator.EQ, "city", "Ankh-Morpork")
v.not(v.not(v.compare(ATTRIBUTE, Operator.EQ, "city", "Ankh-Morpork")))
), hasToString("((clientId EQ [4] && id EQ [5]) || attribute EQ [city, Ankh-Morpork])"));
DefaultModelCriteria<RoleModel> mcb = criteria();
assertThat(mcb.and(
mcb.compare(RoleModel.SearchableFields.REALM_ID, Operator.EQ, "realmId"),
mcb.compare(RoleModel.SearchableFields.CLIENT_ID, Operator.EQ, "clientId"),
mcb.or(
mcb.compare(RoleModel.SearchableFields.NAME, Operator.ILIKE, "%search%"),
mcb.compare(RoleModel.SearchableFields.DESCRIPTION, Operator.ILIKE, "%search%")
)
), hasToString("(realmId EQ [realmId] && clientId EQ [clientId] && (name ILIKE [%search%] || description ILIKE [%search%]))"));
assertThat(mcb
.compare(RoleModel.SearchableFields.REALM_ID, Operator.EQ, "realmId")
.compare(RoleModel.SearchableFields.CLIENT_ID, Operator.EQ, "clientId")
.or(
mcb.compare(RoleModel.SearchableFields.NAME, Operator.ILIKE, "%search%"),
mcb.compare(RoleModel.SearchableFields.DESCRIPTION, Operator.ILIKE, "%search%")
),
hasToString("(realmId EQ [realmId] && clientId EQ [clientId] && (name ILIKE [%search%] || description ILIKE [%search%]))"));
}
@Test
public void testComplexCompareAndOr() {
DefaultModelCriteria<ClientModel> v = new DefaultModelCriteria<>();
DefaultModelCriteria<ClientModel> v = criteria();
assertThat(v.or(
v.and(
v.compare(CLIENT_ID, Operator.EQ, 4),
@ -88,18 +120,49 @@ public class DefaultModelCriteriaTest {
),
hasToString("((clientId EQ [4] && realmId EQ [aa]) || (! id EQ [5] && enabled EQ [true]))")
);
assertThat(v.or(
v.and(
v.compare(CLIENT_ID, Operator.EQ, 4),
v.compare(REALM_ID, Operator.EQ, "aa")
),
v.not(
v.not(
v.compare(ID, Operator.EQ, 5)
).compare(ENABLED, Operator.EQ, "true")
)
),
hasToString("((clientId EQ [4] && realmId EQ [aa]) || ! (! id EQ [5] && enabled EQ [true]))")
);
assertThat(v.or(
v.and(
v.compare(CLIENT_ID, Operator.EQ, 4),
v.compare(REALM_ID, Operator.EQ, "aa")
),
v.not(
v.not(
v.and(
v.compare(ID, Operator.EQ, 5)
.compare(ENABLED, Operator.EQ, "true")
)
)
)
),
hasToString("((clientId EQ [4] && realmId EQ [aa]) || (id EQ [5] && enabled EQ [true]))")
);
}
@Test
public void testFlashingToAnotherMCB() {
DefaultModelCriteria<ClientModel> v = new DefaultModelCriteria<>();
DefaultModelCriteria<ClientModel> v = criteria();
assertThat(v.or(
v.and(
v.compare(CLIENT_ID, Operator.EQ, 4),
v.compare(REALM_ID, Operator.EQ, "aa")
),
v.not(v.compare(ID, Operator.EQ, 5))
).flashToModelCriteriaBuilder(new DefaultModelCriteria<>()),
).flashToModelCriteriaBuilder(criteria()),
hasToString("((clientId EQ [4] && realmId EQ [aa]) || ! id EQ [5])")
);
@ -111,9 +174,35 @@ public class DefaultModelCriteriaTest {
v.not(
v.compare(ID, Operator.EQ, 5)
).compare(ENABLED, Operator.EQ, "true")
).flashToModelCriteriaBuilder(new DefaultModelCriteria<>()),
).flashToModelCriteriaBuilder(criteria()),
hasToString("((clientId EQ [4] && realmId EQ [aa]) || (! id EQ [5] && enabled EQ [true]))")
);
}
@Test
public void testCloning() {
DefaultModelCriteria<ClientModel> v = criteria();
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5))
.partiallyEvaluate((field, operator, operatorArguments) ->
(field == CLIENT_ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(4))
|| (field == ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(5))
? true
: null
),
hasToString("(__TRUE__ && __TRUE__)"));
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5))
.partiallyEvaluate((field, operator, operatorArguments) ->
(field == CLIENT_ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(4))
|| (field == ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(5))
? true
: null
).optimize(),
hasToString("__TRUE__"));
assertThat(v.and(v.compare(CLIENT_ID, Operator.EQ, 4).compare(ID, Operator.EQ, 5))
.partiallyEvaluate((field, operator, operatorArguments) -> field == CLIENT_ID && operator == Operator.EQ && Arrays.asList(operatorArguments).contains(6) ? true : null),
hasToString("(clientId EQ [4] && id EQ [5])"));
}
}