You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by bd...@apache.org on 2023/01/17 19:38:04 UTC
[directory-scimple] 01/01: Abstract out basic FilterExpression mapping logic
This is an automated email from the ASF dual-hosted git repository.
bdemers pushed a commit to branch expression-mapper
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git
commit bd9574400c87c2cf36f4e0ee6a871a08ed461dff
Author: Brian Demers <bd...@apache.org>
AuthorDate: Tue Jan 17 14:37:56 2023 -0500
Abstract out basic FilterExpression mapping logic
This should allow for creation of custom mapping implementations e.g. a conversion to SQL
---
.../spec/filter/BaseFilterExpressionMapper.java | 133 +++++++++++++++++++++
.../scim/spec/filter/FilterExpressions.java | 8 +-
.../spec/filter/InMemoryScimFilterMatcher.java | 114 +++++-------------
3 files changed, 164 insertions(+), 91 deletions(-)
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/BaseFilterExpressionMapper.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/BaseFilterExpressionMapper.java
new file mode 100644
index 00000000..b89cba63
--- /dev/null
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/BaseFilterExpressionMapper.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.directory.scim.spec.filter;
+
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.schema.AttributeContainer;
+import org.apache.directory.scim.spec.schema.Schema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.function.BiFunction;
+
+/**
+ * The {@code BaseFilterExpressionMapper} is a utility class to aid in conversion of a {@link FilterExpression} into a
+ * class that can be used to look up or search for SCIM Resources. For example, convert a
+ * FilterExpression into a SQL query.
+ * @param <R>
+ */
+public abstract class BaseFilterExpressionMapper<R> implements BiFunction<FilterExpression, AttributeContainer, R> {
+
+ private static final Logger log = LoggerFactory.getLogger(BaseFilterExpressionMapper.class);
+
+ @Override
+ public R apply(FilterExpression expression, AttributeContainer attributeContainer) {
+
+ // attribute EQ "something"
+ if (expression instanceof AttributeComparisonExpression) {
+ return apply((AttributeComparisonExpression) expression, attributeContainer);
+ }
+ // (attribute EQ "something") AND (otherAttribute EQ "something else")
+ else if (expression instanceof LogicalExpression) {
+ return apply((LogicalExpression) expression, attributeContainer);
+ }
+ // NOT (attribute EQ "something")
+ else if (expression instanceof GroupExpression) {
+ return apply((GroupExpression) expression, attributeContainer);
+ }
+ // attribute PR
+ else if (expression instanceof AttributePresentExpression) {
+ return apply((AttributePresentExpression) expression, attributeContainer);
+ }
+ // addresses[type EQ "work"]
+ else if (expression instanceof ValuePathExpression) {
+ return apply((ValuePathExpression) expression, attributeContainer);
+ }
+ return unhandledExpression(expression, attributeContainer);
+ }
+
+ protected abstract R apply(AttributeComparisonExpression expression, AttributeContainer attributeContainer);
+
+ protected R apply(LogicalExpression expression, AttributeContainer attributeContainer) {
+ R left = apply(expression.getLeft(), attributeContainer);
+ R right = apply(expression.getRight(), attributeContainer);
+
+ LogicalOperator op = expression.getOperator();
+ return apply(op, left, right);
+ }
+
+ protected abstract R apply(LogicalOperator op, R left, R right);
+
+ protected R apply(GroupExpression expression, AttributeContainer attributeContainer) {
+ R result = apply(expression.getFilterExpression(), attributeContainer);
+ return expression.isNot()
+ ? negate(result)
+ : result;
+ }
+
+ protected abstract R negate(R expression);
+
+ protected abstract R apply(AttributePresentExpression expression, AttributeContainer attributeContainer);
+
+ protected abstract R apply(ValuePathExpression expression, AttributeContainer attributeContainer);
+
+ protected R unhandledExpression(FilterExpression expression, AttributeContainer attributeContainer) {
+ throw new IllegalArgumentException("FilterExpression '" + expression + "' is not supported");
+ }
+
+ protected static Schema.Attribute attribute(AttributeContainer attributeContainer, AttributeReference attributeReference) {
+ String baseAttributeName = attributeReference.getAttributeName();
+
+ Schema.Attribute schemaAttribute = attributeContainer.getAttribute(baseAttributeName);
+ if (schemaAttribute == null) {
+ log.warn("Invalid filter: attribute '" + baseAttributeName + "' is NOT a valid SCIM attribute.");
+ return null;
+ }
+
+ String subAttribute = attributeReference.getSubAttributeName();
+ if (subAttribute != null) {
+ schemaAttribute = schemaAttribute.getAttribute(subAttribute);
+ if (schemaAttribute == null) {
+ log.warn("Invalid filter: attribute '" + attributeReference.getFullyQualifiedAttributeName() + "' is NOT a valid SCIM attribute.");
+ return null;
+ }
+ }
+
+ // filter out fields like passwords that should not be queried against
+ if (schemaAttribute.getReturned() == Schema.Attribute.Returned.NEVER) {
+ log.warn("Invalid filter: attribute '" + attributeReference.getAttributeName() + "' is not filterable.");
+ return null;
+ }
+
+ return schemaAttribute;
+ }
+
+ protected static boolean isStringExpression(Schema.Attribute attribute, Object compareValue) {
+ if (attribute.getType() != Schema.Attribute.Type.STRING) {
+ log.debug("Invalid query, non String value for expression : " + attribute.getType());
+ return false;
+ }
+ if (compareValue == null) {
+ log.debug("Invalid query, empty value for expression : " + attribute.getType());
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/FilterExpressions.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/FilterExpressions.java
index 531788dc..f6cedf2a 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/FilterExpressions.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/FilterExpressions.java
@@ -15,7 +15,7 @@ public final class FilterExpressions {
* the appropriate query language.
* <p>
*
- * <b>This implementation should only be used for demo proposes.</b>
+ * <b>This implementation should only be used for small collections or demo proposes.</b>
*/
public static Predicate<ScimResource> inMemory(Filter filter, Schema schema) {
if (filter == null) {
@@ -25,10 +25,10 @@ public final class FilterExpressions {
if (expression == null) {
return x -> true;
}
- return InMemoryScimFilterMatcher.toPredicate(expression, schema);
+ return new InMemoryScimFilterMatcher<ScimResource>().apply(expression, schema);
}
- public static Predicate<Object> inMemory(FilterExpression expression, Schema schema) {
- return InMemoryScimFilterMatcher.toPredicate(expression, schema);
+ public static <R> Predicate<R> inMemory(FilterExpression expression, Schema schema) {
+ return new InMemoryScimFilterMatcher<R>().apply(expression, schema);
}
}
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/InMemoryScimFilterMatcher.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/InMemoryScimFilterMatcher.java
index 53746867..77dc0cff 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/InMemoryScimFilterMatcher.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/InMemoryScimFilterMatcher.java
@@ -30,104 +30,44 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.function.Predicate;
-final class InMemoryScimFilterMatcher {
+final class InMemoryScimFilterMatcher<R> extends BaseFilterExpressionMapper<Predicate<R>> {
private static final Logger log = LoggerFactory.getLogger(InMemoryScimFilterMatcher.class);
- private InMemoryScimFilterMatcher() {}
-
- /**
- * Converts a FilterExpression to a Predicate that can be used to later test a ScimResource (or child attribute).
- * <p>
- * The {@code attributeContainer} should be a top level Schema for the initial call, but recursive calls to this method
- * will pass the AttributeContainer (a child-schema). For example, if given a
- * {@link org.apache.directory.scim.spec.resources.ScimUser ScimUser} expression of {@code emails.value co "example.org"},
- * The initial call to this method pass the ScimUser Schema object. When this method is called recursively, the
- * {@link org.apache.directory.scim.spec.resources.Email Email}'s AttributeContainer will be used.
- *
- * @param expression the FilterExpression to build a predicate from.
- * @param attributeContainer the Schema (or other sub attribute container) to build the predicate from.
- * @return A Predicate that can be used to later test a ScimResource (or child attribute).
- */
- static <R> Predicate<R> toPredicate(FilterExpression expression, AttributeContainer attributeContainer) {
-
- // attribute EQ "something"
- if (expression instanceof AttributeComparisonExpression) {
- return new AttributeComparisonPredicate<>((AttributeComparisonExpression) expression, attributeContainer);
- }
- // (attribute EQ "something") AND (otherAttribute EQ "something else")
- else if (expression instanceof LogicalExpression) {
- LogicalExpression logicalExpression = (LogicalExpression) expression;
- Predicate<R> left = toPredicate(logicalExpression.getLeft(), attributeContainer);
- Predicate<R> right = toPredicate(logicalExpression.getRight(), attributeContainer);
-
- LogicalOperator op = logicalExpression.getOperator();
- if (op == LogicalOperator.AND) {
- return left.and(right);
- } else {
- return left.or(right);
- }
- }
- // NOT (attribute EQ "something")
- else if (expression instanceof GroupExpression) {
- GroupExpression groupExpression = (GroupExpression) expression;
- Predicate<R> predicate = toPredicate(groupExpression.getFilterExpression(), attributeContainer);
- return groupExpression.isNot()
- ? predicate.negate()
- : predicate;
- }
- // attribute PR
- else if (expression instanceof AttributePresentExpression) {
- return new AttributePresentPredicate<>((AttributePresentExpression) expression, attributeContainer);
- }
- // addresses[type EQ "work"]
- else if (expression instanceof ValuePathExpression) {
- ValuePathExpression valuePathExpression = (ValuePathExpression) expression;
- Predicate<Object> nestedPredicate = toPredicate(valuePathExpression.getAttributeExpression(), attribute(attributeContainer, valuePathExpression.getAttributePath()));
- return new ValuePathPredicate<>(valuePathExpression, attributeContainer, nestedPredicate);
- }
- log.debug("Unsupported Filter expression of type: " + expression.getClass());
- return scimResource -> false;
+ @Override
+ protected Predicate<R> apply(AttributeComparisonExpression expression, AttributeContainer attributeContainer) {
+ return new AttributeComparisonPredicate<>(expression, attributeContainer);
}
-
- private static boolean isStringExpression(Schema.Attribute attribute, Object compareValue) {
- if (attribute.getType() != Schema.Attribute.Type.STRING) {
- log.debug("Invalid query, non String value for expression : " + attribute.getType());
- return false;
- }
- if (compareValue == null) {
- log.debug("Invalid query, empty value for expression : " + attribute.getType());
- return false;
+ @Override
+ protected Predicate<R> apply(LogicalOperator op, Predicate<R> left, Predicate<R> right) {
+ if (op == LogicalOperator.AND) {
+ return left.and(right);
+ } else {
+ return left.or(right);
}
- return true;
}
- private static Schema.Attribute attribute(AttributeContainer attributeContainer, AttributeReference attributeReference) {
- String baseAttributeName = attributeReference.getAttributeName();
-
- Schema.Attribute schemaAttribute = attributeContainer.getAttribute(baseAttributeName);
- if (schemaAttribute == null) {
- log.warn("Invalid filter: attribute '" + baseAttributeName + "' is NOT a valid SCIM attribute.");
- return null;
- }
+ @Override
+ protected Predicate<R> negate(Predicate<R> expression) {
+ return expression.negate();
+ }
- String subAttribute = attributeReference.getSubAttributeName();
- if (subAttribute != null) {
- schemaAttribute = schemaAttribute.getAttribute(subAttribute);
- if (schemaAttribute == null) {
- log.warn("Invalid filter: attribute '" + attributeReference.getFullyQualifiedAttributeName() + "' is NOT a valid SCIM attribute.");
- return null;
- }
- }
+ @Override
+ protected Predicate<R> apply(AttributePresentExpression expression, AttributeContainer attributeContainer) {
+ return new AttributePresentPredicate<>(expression, attributeContainer);
+ }
- // filter out fields like passwords that should not be queried against
- if (schemaAttribute.getReturned() == Schema.Attribute.Returned.NEVER) {
- log.warn("Invalid filter: attribute '" + attributeReference.getAttributeName() + "' is not filterable.");
- return null;
- }
+ @Override
+ protected Predicate<R> apply(ValuePathExpression expression, AttributeContainer attributeContainer) {
+ Predicate<Object> nestedPredicate = new InMemoryScimFilterMatcher<>().apply(expression.getAttributeExpression(), attribute(attributeContainer, expression.getAttributePath()));
+ return new ValuePathPredicate<>(expression, attributeContainer, nestedPredicate);
+ }
- return schemaAttribute;
+ @Override
+ protected Predicate<R> unhandledExpression(FilterExpression expression, AttributeContainer attributeContainer) {
+ log.debug("Unsupported Filter expression of type: " + expression.getClass());
+ return scimResource -> false;
}
private static abstract class AbstractAttributePredicate<T extends FilterExpression, R> implements Predicate<R> {