Skip to content

Commit

Permalink
KEYCLOAK-18854 Introduce storage-independent ModelCriteriaBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
hmlnarik committed Oct 29, 2021
1 parent b0b4d01 commit 877ae96
Show file tree
Hide file tree
Showing 4 changed files with 389 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.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;

/**
* Descriptive model criteria implementation which in other words represents a Boolean formula on searchable fields.
* @author hmlnarik
*/
public class DefaultModelCriteria<M> implements ModelCriteriaBuilder<M> {

private final ModelCriteriaNode<M> node;

public DefaultModelCriteria() {
this.node = null;
}

private DefaultModelCriteria(ModelCriteriaNode<M> node) {
this.node = node;
}

@Override
public DefaultModelCriteria<M> compare(SearchableModelField<M> modelField, Operator op, Object... value) {
final ModelCriteriaNode<M> targetNode;
if (isEmpty()) {
targetNode = new ModelCriteriaNode<>(modelField, op, value);
} else if (node.getNodeOperator() == ExtOperator.AND) {
targetNode = node.cloneTree();
targetNode.addChild(new ModelCriteriaNode<>(modelField, op, value));
} else {
targetNode = new ModelCriteriaNode<>(ExtOperator.AND);
targetNode.addChild(node.cloneTree());
targetNode.addChild(new ModelCriteriaNode<>(modelField, op, value));
}
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);
}
return new DefaultModelCriteria<>(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);
}
return new DefaultModelCriteria<>(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);
}

/**
* Copies contents of this {@code ModelCriteriaBuilder} into
* another {@code ModelCriteriaBuilder}.
* @param mcb {@code ModelCriteriaBuilder} to copy the contents onto
* @return Updated {@code ModelCriteriaBuilder}
*/
public ModelCriteriaBuilder<M> flashToModelCriteriaBuilder(ModelCriteriaBuilder<M> mcb) {
return mcb == null ? null : node.flashToModelCriteriaBuilder(mcb);
}

public boolean isEmpty() {
return node == null;
}

@Override
public String toString() {
return isEmpty() ? "" : node.toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.models.map.storage.criteria;

import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator;
import org.keycloak.models.map.storage.tree.DefaultTreeNode;
import org.keycloak.storage.SearchableModelField;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;

/**
*
* @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) {
if (node.getChildren().isEmpty()) {
return null;
}
final ModelCriteriaBuilder[] operands = node.getChildren().stream()
.map(n -> n.flashToModelCriteriaBuilder(mcb))
.filter(Objects::nonNull)
.toArray(ModelCriteriaBuilder[]::new);
return operands.length == 0 ? null : 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) {
if (node.getChildren().isEmpty()) {
return null;
}
final ModelCriteriaBuilder[] operands = node.getChildren().stream()
.map(n -> n.flashToModelCriteriaBuilder(mcb))
.filter(Objects::nonNull)
.toArray(ModelCriteriaBuilder[]::new);
return operands.length == 0 ? null : 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 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(
node.field,
node.simpleOperator,
node.simpleOperatorArguments
);
}
@Override public String toString(ModelCriteriaNode<?> node) {
return node.field.getName() + " " + node.simpleOperator + " " + Arrays.deepToString(node.simpleOperatorArguments);
}
},
;

public abstract <M> ModelCriteriaBuilder<M> apply(ModelCriteriaBuilder<M> mcbCreator, ModelCriteriaNode<M> node);
public abstract String toString(ModelCriteriaNode<?> node);
}

private final ExtOperator nodeOperator;

private final Operator simpleOperator;

private final SearchableModelField<M> field;

private final Object[] simpleOperatorArguments;

public ModelCriteriaNode(SearchableModelField<M> field, Operator simpleOperator, Object... simpleOperatorArguments) {
super(Collections.emptyMap());
this.simpleOperator = simpleOperator;
this.field = field;
this.simpleOperatorArguments = simpleOperatorArguments;
this.nodeOperator = ExtOperator.SIMPLE_OPERATOR;
}

public ModelCriteriaNode(ExtOperator nodeOperator) {
super(Collections.emptyMap());
this.nodeOperator = nodeOperator;
this.simpleOperator = null;
this.field = null;
this.simpleOperatorArguments = null;
}

private ModelCriteriaNode(ExtOperator nodeOperator, Operator simpleOperator, SearchableModelField<M> field, Object[] simpleOperatorArguments) {
super(Collections.emptyMap());
this.nodeOperator = nodeOperator;
this.simpleOperator = simpleOperator;
this.field = field;
this.simpleOperatorArguments = simpleOperatorArguments;
}

public ExtOperator getNodeOperator() {
return nodeOperator;
}

public ModelCriteriaNode<M> cloneTree() {
return cloneTree(n -> new ModelCriteriaNode<>(n.nodeOperator, n.simpleOperator, n.field, n.simpleOperatorArguments));
}

public ModelCriteriaBuilder<M> flashToModelCriteriaBuilder(ModelCriteriaBuilder<M> mcb) {
final ModelCriteriaBuilder<M> res = nodeOperator.apply(mcb, this);
return res == null ? mcb : res;
}

@Override
public String toString() {
return nodeOperator.toString(this);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Stack;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
Expand All @@ -39,8 +39,8 @@
*/
public class DefaultTreeNode<Self extends DefaultTreeNode<Self>> implements TreeNode<Self> {

private final Map<String, Object> edgeProperties = new HashMap<>();
private final Map<String, Object> nodeProperties = new HashMap<>();
private final Map<String, Object> nodeProperties;
private final Map<String, Object> edgeProperties;
private final Map<String, Object> treeProperties;
private final LinkedList<Self> children = new LinkedList<>();
private String id;
Expand All @@ -51,6 +51,14 @@ public class DefaultTreeNode<Self extends DefaultTreeNode<Self>> implements Tree
*/
protected DefaultTreeNode(Map<String, Object> treeProperties) {
this.treeProperties = treeProperties;
this.edgeProperties = new HashMap<>();
this.nodeProperties = new HashMap<>();
}

public DefaultTreeNode(Map<String, Object> nodeProperties, Map<String, Object> edgeProperties, Map<String, Object> treeProperties) {
this.nodeProperties = nodeProperties;
this.edgeProperties = edgeProperties;
this.treeProperties = treeProperties;
}

@Override
Expand Down Expand Up @@ -106,7 +114,7 @@ public void setId(String id) {
@Override
public Optional<Self> findFirstDfs(Predicate<Self> visitor) {
Deque<Self> stack = new LinkedList<>();
stack.add((Self) this);
stack.add(getThis());
while (! stack.isEmpty()) {
Self node = stack.pop();
if (visitor.test(node)) {
Expand All @@ -124,7 +132,7 @@ public Optional<Self> findFirstDfs(Predicate<Self> visitor) {
@Override
public Optional<Self> findFirstBottommostDfs(Predicate<Self> visitor) {
Deque<Self> stack = new LinkedList<>();
stack.add((Self) this);
stack.add(getThis());
while (! stack.isEmpty()) {
Self node = stack.pop();
if (visitor.test(node)) {
Expand All @@ -149,7 +157,7 @@ public Optional<Self> findFirstBottommostDfs(Predicate<Self> visitor) {
@Override
public Optional<Self> findFirstBfs(Predicate<Self> visitor) {
Queue<Self> queue = new LinkedList<>();
queue.add((Self) this);
queue.add(getThis());
while (! queue.isEmpty()) {
Self node = queue.poll();
if (visitor.test(node)) {
Expand All @@ -165,7 +173,7 @@ public Optional<Self> findFirstBfs(Predicate<Self> visitor) {
public List<Self> getPathToRoot(PathOrientation orientation) {
LinkedList<Self> res = new LinkedList<>();
Consumer<Self> addFunc = orientation == PathOrientation.BOTTOM_FIRST ? res::addLast : res::addFirst;
Optional<Self> p = Optional.of((Self) this);
Optional<Self> p = Optional.of(getThis());
while (p.isPresent()) {
addFunc.accept(p.get());
p = p.get().getParent();
Expand All @@ -186,7 +194,7 @@ public void addChild(Self node) {
if (! this.children.contains(node)) {
this.children.add(node);
}
node.setParent((Self) this);
node.setParent(getThis());

// Prevent setting a parent of this node as a child of this node. In such a case, remove the parent of this node
for (Optional<Self> p = getParent(); p.isPresent(); p = p.get().getParent()) {
Expand All @@ -205,7 +213,7 @@ public void addChild(int index, Self node) {
if (! this.children.contains(node)) {
this.children.add(index, node);
}
node.setParent((Self) this);
node.setParent(getThis());

// Prevent setting a parent of this node as a child of this node. In such a case, remove the parent of this node
for (Optional<Self> p = getParent(); p.isPresent(); p = p.get().getParent()) {
Expand Down Expand Up @@ -276,12 +284,23 @@ public void setParent(Self parent) {
if (this.parent != null) {
Self previousParent = this.parent;
this.parent = null;
previousParent.removeChild((Self) this);
previousParent.removeChild(getThis());
}

if (parent != null) {
this.parent = parent;
parent.addChild((Self) this);
parent.addChild(getThis());
}
}

public <RNode extends TreeNode<? super RNode>> RNode cloneTree(Function<Self, RNode> instantiateFunc) {
final RNode res = instantiateFunc.apply(getThis());
this.getChildren().forEach(c -> res.addChild(c.cloneTree(instantiateFunc)));
return res;
}

@SuppressWarnings("unchecked")
private Self getThis() {
return (Self) this;
}
}
Loading

0 comments on commit 877ae96

Please sign in to comment.