KEYCLOAK-19749 Optimize DefaultModelCriteria creation
This commit is contained in:
parent
b37f2d5915
commit
6966e0cfe9
7 changed files with 298 additions and 56 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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])"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue