ModelCriteriaNode.java
/*
* 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.lang.reflect.Array;
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, C extends ModelCriteriaBuilder<M, C>> C apply(C mcb, ModelCriteriaNode<M> node) {
if (node.getChildren().isEmpty()) {
return null;
}
final C[] operands = node.getChildren().stream()
.map(n -> n.flashToModelCriteriaBuilder(mcb))
.filter(Objects::nonNull)
.toArray(n -> (C[]) Array.newInstance(mcb.getClass(), n));
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, C extends ModelCriteriaBuilder<M, C>> C apply(C mcb, ModelCriteriaNode<M> node) {
if (node.getChildren().isEmpty()) {
return null;
}
final C[] operands = node.getChildren().stream()
.map(n -> n.flashToModelCriteriaBuilder(mcb))
.filter(Objects::nonNull)
.toArray(n -> (C[]) Array.newInstance(mcb.getClass(), n));
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, C extends ModelCriteriaBuilder<M, C>> C apply(C mcb, ModelCriteriaNode<M> node) {
final ModelCriteriaNode<M> child = node.getChildren().iterator().next();
return child.isFalseNode()
? mcb.and((C[]) Array.newInstance(mcb.getClass(), 0))
: (child.isTrueNode()
? mcb.or((C[]) Array.newInstance(mcb.getClass(), 0))
: mcb.not(child.flashToModelCriteriaBuilder(mcb))
);
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "! " + node.getChildren().iterator().next().toString();
}
},
ATOMIC_FORMULA {
@Override public <M, C extends ModelCriteriaBuilder<M, C>> C apply(C mcb, ModelCriteriaNode<M> node) {
return (C) mcb.compare(
node.field,
node.simpleOperator,
node.simpleOperatorArguments
);
}
@Override public String toString(ModelCriteriaNode<?> node) {
return node.field.getName() + " " + node.simpleOperator + " " + Arrays.deepToString(node.simpleOperatorArguments);
}
},
__FALSE__ {
@Override public <M, C extends ModelCriteriaBuilder<M, C>> C apply(C mcb, ModelCriteriaNode<M> node) {
return mcb.or((C[]) Array.newInstance(mcb.getClass(), 0));
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "__FALSE__";
}
},
__TRUE__ {
@Override public <M, C extends ModelCriteriaBuilder<M, C>> C apply(C mcb, ModelCriteriaNode<M> node) {
return mcb.and((C[]) Array.newInstance(mcb.getClass(), 0));
}
@Override public String toString(ModelCriteriaNode<?> node) {
return "__TRUE__";
}
}
;
public abstract <M, C extends ModelCriteriaBuilder<M, C>> C apply(C mcbCreator, ModelCriteriaNode<M> node);
public abstract String toString(ModelCriteriaNode<?> node);
}
private final ExtOperator nodeOperator;
private final Operator simpleOperator;
private final SearchableModelField<? super M> field;
private final 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.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) {
super(Collections.emptyMap());
this.nodeOperator = nodeOperator;
this.simpleOperator = null;
this.field = null;
this.simpleOperatorArguments = null;
}
public ExtOperator getNodeOperator() {
return nodeOperator;
}
public Operator getSimpleOperator() {
return simpleOperator;
}
public SearchableModelField<? super M> getField() {
return field;
}
public Object[] getSimpleOperatorArguments() {
return simpleOperatorArguments;
}
public ModelCriteriaNode<M> cloneTree() {
return cloneTree(ModelCriteriaNode::new, ModelCriteriaNode::new);
}
@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>> C flashToModelCriteriaBuilder(C mcb) {
final C res = nodeOperator.apply(mcb, this);
return res == null ? mcb : res;
}
@Override
public String toString() {
return nodeOperator.toString(this);
}
}