From 92e667d109a8ac0ff811201d4c67ddd98122e68f Mon Sep 17 00:00:00 2001 From: Elias Huwyler Date: Mon, 24 Jun 2024 20:10:19 +0200 Subject: [PATCH 1/7] First Version of https://github.com/turkraft/springfilter/issues/334 --- .../helper/FieldTypeResolver.java | 1 + .../helper/FieldTypeResolverImpl.java | 8 + .../helper/JsonNodeHelperImpl.java | 21 +- .../springfilter/helper/TransformerUtils.java | 13 + .../helper/TransformerUtilsImpl.java | 227 ++++++++++++++++++ .../FilterJsonNodeTransformer.java | 3 + .../InOperationJsonNodeProcessor.java | 35 +-- .../LikeOperationJsonNodeProcessor.java | 64 ++--- .../NotInOperationJsonNodeProcessor.java | 35 +-- ...ot.autoconfigure.AutoConfiguration.imports | 1 + 10 files changed, 337 insertions(+), 71 deletions(-) create mode 100644 mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java create mode 100644 mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolver.java b/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolver.java index 3499f7c1..da704c9e 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolver.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolver.java @@ -8,4 +8,5 @@ public interface FieldTypeResolver { Field getField(Class klass, String path); + boolean isIterable(Class klass, String path); } diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java index 5e29406f..3ef5b8e0 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java @@ -61,6 +61,14 @@ private Class normalize(Field field) { } + public boolean isIterable(Class klass, String path) { + return isIterable(getField(klass, path)); + } + + private boolean isIterable(Field field) { + return Collection.class.isAssignableFrom(field.getType()) || field.getType().isArray(); + } + private Class getFirstTypeParameterOf(Field field) { return (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; } diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java index 49448270..7f2e987a 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java @@ -12,13 +12,15 @@ public class JsonNodeHelperImpl implements JsonNodeHelper { protected final ObjectMapper objectMapper; - + protected final TransformerUtils transformerUtils; protected final FieldTypeResolver fieldTypeResolver; public JsonNodeHelperImpl(ObjectMapper objectMapper, - FieldTypeResolver fieldTypeResolver) { + FieldTypeResolver fieldTypeResolver, + TransformerUtils transformerUtils) { this.objectMapper = objectMapper; this.fieldTypeResolver = fieldTypeResolver; + this.transformerUtils = transformerUtils; } @Override @@ -28,7 +30,7 @@ public ObjectNode wrapWithMongoExpression(JsonNode node) { @Override public JsonNode transform(FilterJsonNodeTransformer transformer, InfixOperationNode source, - String mongoOperator) { + String mongoOperator) { if (source.getLeft() instanceof FieldNode fieldNode) { transformer.registerTargetType(source.getRight(), @@ -42,19 +44,20 @@ public JsonNode transform(FilterJsonNodeTransformer transformer, InfixOperationN if (transformer.getRegisteredTargetType(source.getLeft()) != null) { transformer.registerTargetType(source.getRight(), - transformer.getRegisteredTargetType(source.getLeft())); + transformer.getRegisteredTargetType(source.getLeft())); } else if (transformer.getRegisteredTargetType(source.getRight()) != null) { transformer.registerTargetType(source.getLeft(), - transformer.getRegisteredTargetType(source.getRight())); + transformer.getRegisteredTargetType(source.getRight())); } JsonNode rightResult = transformer.transform(source.getRight()); - return transformer.getObjectMapper().createObjectNode().set(mongoOperator, - transformer.getObjectMapper().createArrayNode() - .add(leftResult) - .add(rightResult)); + JsonNode result = transformer.getObjectMapper().createObjectNode().set(mongoOperator, + transformer.getObjectMapper().createArrayNode() + .add(leftResult) + .add(rightResult)); + return transformerUtils.wrapArrays(transformer, result, source, mongoOperator); } } diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java new file mode 100644 index 00000000..ee49dffb --- /dev/null +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java @@ -0,0 +1,13 @@ +package com.turkraft.springfilter.helper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.turkraft.springfilter.parser.node.InfixOperationNode; +import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; + +public interface TransformerUtils { + JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source); + + JsonNode wrapArraysRegex(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source); + + JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source, String mongoOperator); +} diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java new file mode 100644 index 00000000..3095ef78 --- /dev/null +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java @@ -0,0 +1,227 @@ +package com.turkraft.springfilter.helper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Lists; +import com.turkraft.springfilter.parser.node.FieldNode; +import com.turkraft.springfilter.parser.node.FilterNode; +import com.turkraft.springfilter.parser.node.InfixOperationNode; +import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +class TransformerUtilsImpl implements TransformerUtils{ + + private final FieldTypeResolver fieldTypeResolver; + + TransformerUtilsImpl(FieldTypeResolver fieldTypeResolver) { + this.fieldTypeResolver = fieldTypeResolver; + } + + + public JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source) { + if (!(source.getLeft() instanceof FieldNode)) { + return node; + } + + String textValue = transformer.transform(source.getLeft()).textValue(); + String[] fields = textValue.replace("$", "").split("\\."); + List arrayPaths = getArrayPaths(transformer, source, fields); + + if (arrayPaths.isEmpty()) { + return node; + } + + JsonNode resultNode = null; + + for (int i = arrayPaths.size() - 1; i >= 0; i--) { + String input = (i > 0) ? "$$this.".concat(arrayPaths.get(i)) : "$".concat(arrayPaths.get(i)); + + if (resultNode == null) { + FilterNode left = getFilterNode(transformer, source, arrayPaths, i); + JsonNode newNode = transformer.getObjectMapper().createObjectNode().set("$and", + transformer.getObjectMapper().createArrayNode() + .add(transformer.getObjectMapper().createObjectNode() + .set("$isArray", transformer.getObjectMapper().createArrayNode() + .add(transformer.transform(source.getRight())))) + .add(transformer.getObjectMapper().createObjectNode().set("$in", + transformer.getObjectMapper().createArrayNode() + .add(transformer.transform(left)) + .add(transformer.transform(source.getRight()))))); + + resultNode = getJsonNode(transformer, input, newNode); + } else { + JsonNode ifNull = getIfNullNode(transformer, input); + + resultNode = getMapNode(transformer, ifNull, resultNode); + } + + } + return resultNode; + } + + private String getLeafNodePath(String[] fields, List arrayPaths) { + StringBuilder fullPath = new StringBuilder(); + for (int i = fields.length-1; i >=0 ; i--) { + if(arrayPaths.contains(fields[i])){ + for (int j = i+1; j < fields.length; j++) { + if (fullPath.isEmpty()) { + fullPath.append(fields[j]); + } else { + fullPath.append(".").append(fields[j]); + } + } + break; + } + } + return fullPath.toString(); + } + + public JsonNode wrapArraysRegex(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source) { + if (!(source.getLeft() instanceof FieldNode)) { + return node; + } + String textValue = transformer.transform(source.getLeft()).textValue(); + String[] fields = textValue.replace("$", "").split("\\."); + + List arrayPaths = getArrayPaths(transformer, source, fields); + + if (arrayPaths.isEmpty()) { + return node; + } + + JsonNode resultNode = null; + for (int i = arrayPaths.size() - 1; i >= 0; i--) { + String input = (i > 0) ? "$$this.".concat(arrayPaths.get(i)) : "$".concat(arrayPaths.get(i)); + + if (resultNode == null) { + List split = Lists.newArrayList(transformer.transform(source.getLeft()).asText() + .split(arrayPaths.get(i), 2)); + + if (split.size() == 1) { + split.add(""); + } + + JsonNode regex = node.findValue("$regexMatch"); + ObjectNode regexPayload = (ObjectNode) regex; + + regexPayload.put("input", "$$this."+getLeafNodePath(fields, arrayPaths)); + + resultNode = getJsonNode(transformer, input, node); + } else { + JsonNode ifNull = getIfNullNode(transformer, input); + + resultNode = getMapNode(transformer, ifNull, resultNode); + } + + } + return resultNode; + } + + private static JsonNode getMapNode(FilterJsonNodeTransformer transformer, JsonNode ifNull, JsonNode resultNode) { + return transformer.getObjectMapper().createObjectNode().set("$anyElementTrue", + transformer.getObjectMapper().createObjectNode().set("$map", + transformer.getObjectMapper().createObjectNode() + .putPOJO("input", ifNull) + .set("in", resultNode))); + } + + public JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source, String mongoOperator) { + if (!(source.getLeft() instanceof FieldNode)) { + return node; + } + String textValue = transformer.transform(source.getLeft()).textValue(); + String[] fields = textValue.replace("$", "").split("\\."); + List arrayPaths = getArrayPaths(transformer, source, fields); + + if (arrayPaths.isEmpty()) { + return node; + } + + JsonNode resultNode = null; + + for (int i = arrayPaths.size() - 1; i >= 0; i--) { + String input = (i > 0) ? "$$this.".concat(arrayPaths.get(i)) : "$".concat(arrayPaths.get(i)); + + if (resultNode == null) { + FilterNode left = getFilterNode(transformer, source, arrayPaths, i); + + JsonNode newNode = transformer.getObjectMapper().createObjectNode().set(mongoOperator, + transformer.getObjectMapper().createArrayNode() + .add(transformer.transform(left)) + .add(transformer.transform(source.getRight()))); + + resultNode = getJsonNode(transformer, input, newNode); + } else { + JsonNode ifNull = getIfNullNode(transformer, input); + + resultNode = getMapNode(transformer, ifNull, resultNode); + } + + } + return resultNode; + } + + private static FilterNode getFilterNode(FilterJsonNodeTransformer transformer, InfixOperationNode source, List arrayPaths, int i) { + List split = Lists.newArrayList(transformer.transform(source.getLeft()).asText() + .split(arrayPaths.get(i), 2)); + + if (split.size() == 1) { + split.add(""); + } + + String nodeName = "$this".concat(split.get(1)); + FilterNode left = new FieldNode(nodeName); + left.setPayload(source.getLeft().getPayload()); + return left; + } + + private List getArrayPaths(FilterJsonNodeTransformer transformer, InfixOperationNode source, String[] fields) { + List arrayPaths = new ArrayList<>(); + + String fullPath = ""; + String prevArrayPath = ""; + + for (String field : fields) { + + if (fullPath.isEmpty()) { + fullPath = fullPath.concat(field); + } else { + fullPath = fullPath.concat(".").concat(field); + } + + boolean isArray = fieldTypeResolver.isIterable(transformer.getEntityType(), fullPath.replace("_id", "id")); + + if (isArray) { + String result = fullPath.replace(prevArrayPath, ""); + + if (!arrayPaths.isEmpty()) result = result.replaceFirst("\\.", ""); + + arrayPaths.add(result); + prevArrayPath = fullPath; + } + } + return arrayPaths; + } + + private JsonNode getJsonNode(FilterJsonNodeTransformer transformer, String input, JsonNode newNode) { + JsonNode resultNode; + JsonNode ifNull = getIfNullNode(transformer, input); + + resultNode = transformer.getObjectMapper().createObjectNode().set("$anyElementTrue", + transformer.getObjectMapper().createObjectNode().set("$map", + transformer.getObjectMapper().createObjectNode() + .putPOJO("input", ifNull) + .put("as", "this") + .set("in", newNode))); + return resultNode; + } + + private static JsonNode getIfNullNode(FilterJsonNodeTransformer transformer, String input) { + return transformer.getObjectMapper().createObjectNode().set("$ifNull", + transformer.getObjectMapper().createArrayNode().add(input).add(transformer.getObjectMapper().createArrayNode())); + } +} diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/FilterJsonNodeTransformer.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/FilterJsonNodeTransformer.java index f7bfbb43..6906ad83 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/transformer/FilterJsonNodeTransformer.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/FilterJsonNodeTransformer.java @@ -55,6 +55,9 @@ public Class getTargetType() { @Override public JsonNode transformField(FieldNode node) { + if(node.getName().contains("$this")) { + return objectMapper.createObjectNode().textNode("$" +node.getName()); + } Field field = fieldTypeResolver.getField(getEntityType(), node.getName()); if (field.isAnnotationPresent(Id.class)) { return objectMapper.createObjectNode().textNode("$_id"); diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java index 38ee548b..5187b890 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java @@ -6,16 +6,19 @@ import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; +import com.turkraft.springfilter.helper.TransformerUtils; import org.springframework.stereotype.Component; @Component public class InOperationJsonNodeProcessor implements - FilterInfixOperationProcessor { + FilterInfixOperationProcessor { protected final FieldTypeResolver fieldTypeResolver; + protected final TransformerUtils transformerUtils; - public InOperationJsonNodeProcessor(FieldTypeResolver fieldTypeResolver) { + public InOperationJsonNodeProcessor(FieldTypeResolver fieldTypeResolver, TransformerUtils transformerUtils) { this.fieldTypeResolver = fieldTypeResolver; + this.transformerUtils = transformerUtils; } @Override @@ -30,28 +33,28 @@ public Class getDefinitionType() { @Override public JsonNode process(FilterJsonNodeTransformer transformer, - InfixOperationNode source) { + InfixOperationNode source) { transformer.registerTargetType(source, Boolean.class); if (source.getLeft() instanceof FieldNode fieldNode) { transformer.registerTargetType(source.getRight(), - fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); + fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); } else if (source.getRight() instanceof FieldNode fieldNode) { transformer.registerTargetType(source.getLeft(), - fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); + fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); } - return transformer.getObjectMapper().createObjectNode().set("$and", - transformer.getObjectMapper().createArrayNode() - .add(transformer.getObjectMapper().createObjectNode() - .set("$isArray", transformer.getObjectMapper().createArrayNode() - .add(transformer.transform(source.getRight())))) - .add(transformer.getObjectMapper().createObjectNode().set("$in", - transformer.getObjectMapper().createArrayNode() - .add(transformer.transform(source.getLeft())) - .add(transformer.transform(source.getRight()))))); - + JsonNode result = transformer.getObjectMapper().createObjectNode().set("$and", + transformer.getObjectMapper().createArrayNode() + .add(transformer.getObjectMapper().createObjectNode() + .set("$isArray", transformer.getObjectMapper().createArrayNode() + .add(transformer.transform(source.getRight())))) + .add(transformer.getObjectMapper().createObjectNode().set("$in", + transformer.getObjectMapper().createArrayNode() + .add(transformer.transform(source.getLeft())) + .add(transformer.transform(source.getRight()))))); + + return transformerUtils.wrapArrays(transformer, result, source); } - } diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java index 25da7273..3c9557fc 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java @@ -8,18 +8,23 @@ import com.turkraft.springfilter.parser.node.InfixOperationNode; import com.turkraft.springfilter.parser.node.InputNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; -import java.lang.reflect.Field; +import com.turkraft.springfilter.helper.TransformerUtils; import org.springframework.data.annotation.Id; import org.springframework.stereotype.Component; +import java.lang.reflect.Field; + @Component public class LikeOperationJsonNodeProcessor implements - FilterInfixOperationProcessor { + FilterInfixOperationProcessor { protected final FieldTypeResolver fieldTypeResolver; + protected final TransformerUtils transformerUtils; - public LikeOperationJsonNodeProcessor(FieldTypeResolver fieldTypeResolver) { + public LikeOperationJsonNodeProcessor(FieldTypeResolver fieldTypeResolver, + TransformerUtils transformerUtils) { this.fieldTypeResolver = fieldTypeResolver; + this.transformerUtils = transformerUtils; } @Override @@ -34,14 +39,14 @@ public Class getDefinitionType() { @Override public JsonNode process(FilterJsonNodeTransformer transformer, - InfixOperationNode infixOperationNode) { + InfixOperationNode infixOperationNode) { return getRegexNode(transformer, infixOperationNode, ""); } - public ObjectNode getRegexNode(FilterJsonNodeTransformer transformer, - InfixOperationNode infixOperationNode, String regexOptions) { + public JsonNode getRegexNode(FilterJsonNodeTransformer transformer, + InfixOperationNode infixOperationNode, String regexOptions) { transformer.registerTargetType(infixOperationNode, Boolean.class); @@ -49,30 +54,26 @@ public ObjectNode getRegexNode(FilterJsonNodeTransformer transformer, transformer.registerTargetType(infixOperationNode.getRight(), String.class); if (infixOperationNode.getLeft() instanceof FieldNode fieldNode - && infixOperationNode.getRight() instanceof InputNode inputNode) { + && infixOperationNode.getRight() instanceof InputNode inputNode) { - Field field = fieldTypeResolver.getField(transformer.getEntityType(), fieldNode.getName()); + try { + Field field = fieldTypeResolver.getField(transformer.getEntityType(), fieldNode.getName()); - if (field.isAnnotationPresent(Id.class) && field.getType().equals(String.class)) { + if (field.isAnnotationPresent(Id.class) && field.getType().equals(String.class)) { - /* - $function: { - body: "function (id) {return new RegExp(regex, options).test(input)}", - args: [ "$_id" ], - lang: "js" - } - */ + ObjectNode functionBody = transformer.getObjectMapper().createObjectNode(); + functionBody.set("lang", transformer.getObjectMapper().createObjectNode().textNode("js")); + functionBody.set("args", transformer.getObjectMapper().createArrayNode() + .add(transformer.getObjectMapper().createObjectNode().textNode("$_id"))); + functionBody.set("body", transformer.getObjectMapper().createObjectNode() + .textNode("function(id) { return new RegExp('" + createRegex( + String.valueOf(inputNode.getValue())).replace("'", "\\'") + "', '" + regexOptions + + "').test(id) }")); - ObjectNode functionBody = transformer.getObjectMapper().createObjectNode(); - functionBody.set("lang", transformer.getObjectMapper().createObjectNode().textNode("js")); - functionBody.set("args", transformer.getObjectMapper().createArrayNode() - .add(transformer.getObjectMapper().createObjectNode().textNode("$_id"))); - functionBody.set("body", transformer.getObjectMapper().createObjectNode() - .textNode("function(id) { return new RegExp('" + createRegex( - String.valueOf(inputNode.getValue())).replace("'", "\\'") + "', '" + regexOptions - + "').test(id) }")); + return transformer.getObjectMapper().createObjectNode().set("$function", functionBody); - return transformer.getObjectMapper().createObjectNode().set("$function", functionBody); + } + } catch (IllegalArgumentException ignored) { } @@ -81,14 +82,15 @@ public ObjectNode getRegexNode(FilterJsonNodeTransformer transformer, ObjectNode regexOperation = transformer.getObjectMapper().createObjectNode(); regexOperation.set("input", transformer.transform(infixOperationNode.getLeft())); regexOperation.set("regex", - infixOperationNode.getRight() instanceof InputNode ? transformer.getObjectMapper() - .createObjectNode().textNode( - createRegex(String.valueOf(((InputNode) infixOperationNode.getRight()).getValue()))) - : transformer.transform(infixOperationNode.getRight())); + infixOperationNode.getRight() instanceof InputNode ? transformer.getObjectMapper() + .createObjectNode().textNode( + createRegex(String.valueOf(((InputNode) infixOperationNode.getRight()).getValue()))) + : transformer.transform(infixOperationNode.getRight())); regexOperation.set("options", - transformer.getObjectMapper().createObjectNode().textNode(regexOptions)); + transformer.getObjectMapper().createObjectNode().textNode(regexOptions)); - return transformer.getObjectMapper().createObjectNode().set("$regexMatch", regexOperation); + ObjectNode regex = transformer.getObjectMapper().createObjectNode().set("$regexMatch", regexOperation); + return transformerUtils.wrapArraysRegex(transformer, regex, infixOperationNode); } diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java index ca82ee61..59a2fe98 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java @@ -6,17 +6,21 @@ import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; +import com.turkraft.springfilter.helper.TransformerUtils; import org.springframework.stereotype.Component; @Component public class NotInOperationJsonNodeProcessor implements - FilterInfixOperationProcessor { + FilterInfixOperationProcessor { protected final FieldTypeResolver fieldTypeResolver; + protected final TransformerUtils transformerUtils; public NotInOperationJsonNodeProcessor( - FieldTypeResolver fieldTypeResolver) { + FieldTypeResolver fieldTypeResolver, + TransformerUtils transformerUtils) { this.fieldTypeResolver = fieldTypeResolver; + this.transformerUtils = transformerUtils; } @Override @@ -31,28 +35,29 @@ public Class getDefinitionType() { @Override public JsonNode process(FilterJsonNodeTransformer transformer, - InfixOperationNode source) { + InfixOperationNode source) { transformer.registerTargetType(source, Boolean.class); if (source.getLeft() instanceof FieldNode fieldNode) { transformer.registerTargetType(source.getRight(), - fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); + fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); } else if (source.getRight() instanceof FieldNode fieldNode) { transformer.registerTargetType(source.getLeft(), - fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); + fieldTypeResolver.resolve(transformer.getEntityType(), fieldNode.getName())); } - return transformer.getObjectMapper().createObjectNode().set("$and", - transformer.getObjectMapper().createArrayNode() - .add(transformer.getObjectMapper().createObjectNode() - .set("$isArray", transformer.transform(source.getRight()))) - .add(transformer.getObjectMapper().createObjectNode() - .set("$not", transformer.getObjectMapper().createObjectNode().set("$in", - transformer.getObjectMapper().createArrayNode() - .add(transformer.transform(source.getLeft())) - .add(transformer.transform(source.getRight())))))); + JsonNode result = transformer.getObjectMapper().createObjectNode().set("$and", + transformer.getObjectMapper().createArrayNode() + .add(transformer.getObjectMapper().createObjectNode() + .set("$isArray", transformer.transform(source.getRight()))) + .add(transformer.getObjectMapper().createObjectNode() + .set("$not", transformer.getObjectMapper().createObjectNode().set("$in", + transformer.getObjectMapper().createArrayNode() + .add(transformer.transform(source.getLeft())) + .add(transformer.transform(source.getRight())))))); - } + return transformerUtils.wrapArrays(transformer, result, source); + } } diff --git a/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 05130787..c5278cd8 100644 --- a/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -2,6 +2,7 @@ com.turkraft.springfilter.boot.FilterJsonNodeArgumentResolverConfigurer com.turkraft.springfilter.helper.JsonNodeHelperImpl com.turkraft.springfilter.helper.FieldTypeResolverImpl +com.turkraft.springfilter.helper.TransformerUtilsImpl com.turkraft.springfilter.converter.StringCustomObjectIdConverter com.turkraft.springfilter.converter.StringCustomUUIDConverter From 4f230e3e4d8616addad93e5e3f0fe7840eb4fbe1 Mon Sep 17 00:00:00 2001 From: Elias Huwyler Date: Mon, 24 Jun 2024 20:16:30 +0200 Subject: [PATCH 2/7] remove google package --- .../springfilter/helper/TransformerUtilsImpl.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java index 3095ef78..b0e21f87 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.Lists; import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.FilterNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; @@ -98,8 +97,8 @@ public JsonNode wrapArraysRegex(FilterJsonNodeTransformer transformer, JsonNode String input = (i > 0) ? "$$this.".concat(arrayPaths.get(i)) : "$".concat(arrayPaths.get(i)); if (resultNode == null) { - List split = Lists.newArrayList(transformer.transform(source.getLeft()).asText() - .split(arrayPaths.get(i), 2)); + List split = new ArrayList<>(List.of(transformer.transform(source.getLeft()).asText() + .split(arrayPaths.get(i), 2))); if (split.size() == 1) { split.add(""); @@ -166,8 +165,7 @@ public JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, } private static FilterNode getFilterNode(FilterJsonNodeTransformer transformer, InfixOperationNode source, List arrayPaths, int i) { - List split = Lists.newArrayList(transformer.transform(source.getLeft()).asText() - .split(arrayPaths.get(i), 2)); + List split = new ArrayList<>(List.of(transformer.transform(source.getLeft()).asText().split(arrayPaths.get(i), 2))); if (split.size() == 1) { split.add(""); From ad964c9914d956a091292d8ca9e496db15115be4 Mon Sep 17 00:00:00 2001 From: Elias Huwyler Date: Tue, 25 Jun 2024 20:40:03 +0200 Subject: [PATCH 3/7] add AnyElemenTry Fusing for simple cases + tests --- .../boot/FilterJsonNodeArgumentResolver.java | 21 +- ...terJsonNodeArgumentResolverConfigurer.java | 9 +- .../springfilter/helper/TransformerUtils.java | 2 + .../helper/TransformerUtilsImpl.java | 94 ++++++ .../helper/TransformerUtilsImplTest.java | 268 ++++++++++++++++++ 5 files changed, 386 insertions(+), 8 deletions(-) create mode 100644 mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java diff --git a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java index 5c8912d6..b524d32a 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java +++ b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java @@ -1,9 +1,11 @@ package com.turkraft.springfilter.boot; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.turkraft.springfilter.helper.FieldTypeResolver; import com.turkraft.springfilter.helper.JsonNodeHelper; +import com.turkraft.springfilter.helper.TransformerUtils; import com.turkraft.springfilter.parser.node.FilterNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; import com.turkraft.springfilter.transformer.processor.factory.FilterNodeProcessorFactories; @@ -33,18 +35,21 @@ public class FilterJsonNodeArgumentResolver implements HandlerMethodArgumentReso protected final FieldTypeResolver fieldTypeResolver; + protected final TransformerUtils transformerUtils; + public FilterJsonNodeArgumentResolver( - ConversionService conversionService, ObjectMapper objectMapper, - FilterNodeArgumentResolverHelper filterNodeArgumentResolverHelper, - JsonNodeHelper jsonNodeHelper, - FilterNodeProcessorFactories filterNodeProcessorFactories, - FieldTypeResolver fieldTypeResolver) { + ConversionService conversionService, ObjectMapper objectMapper, + FilterNodeArgumentResolverHelper filterNodeArgumentResolverHelper, + JsonNodeHelper jsonNodeHelper, + FilterNodeProcessorFactories filterNodeProcessorFactories, + FieldTypeResolver fieldTypeResolver, TransformerUtils transformerUtils) { this.conversionService = conversionService; this.objectMapper = objectMapper; this.filterNodeArgumentResolverHelper = filterNodeArgumentResolverHelper; this.jsonNodeHelper = jsonNodeHelper; this.filterNodeProcessorFactories = filterNodeProcessorFactories; this.fieldTypeResolver = fieldTypeResolver; + this.transformerUtils = transformerUtils; } @Override @@ -102,8 +107,12 @@ public Object resolveArgument(MethodParameter methodParameter, conversionService, objectMapper, filterNodeProcessorFactories, fieldTypeResolver, methodParameter.getParameterAnnotation(Filter.class).entityClass()); + JsonNode transform = filterJsonNodeTransformer.transform(result.get()); + transform = transformerUtils.simplify(filterJsonNodeTransformer,transform); + + ObjectNode jsonResult = jsonNodeHelper.wrapWithMongoExpression( - filterJsonNodeTransformer.transform(result.get())); + transform); if (methodParameter.getParameterType().isAssignableFrom(ObjectNode.class)) { return jsonResult; diff --git a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java index 500f016c..7e4f0017 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java +++ b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.turkraft.springfilter.helper.FieldTypeResolver; import com.turkraft.springfilter.helper.JsonNodeHelper; +import com.turkraft.springfilter.helper.TransformerUtils; import com.turkraft.springfilter.transformer.processor.factory.FilterNodeProcessorFactories; import java.util.List; import org.springframework.beans.factory.annotation.Qualifier; @@ -27,19 +28,23 @@ public class FilterJsonNodeArgumentResolverConfigurer implements WebMvcConfigure protected final FieldTypeResolver fieldTypeResolver; + protected final TransformerUtils transformerUtils; + public FilterJsonNodeArgumentResolverConfigurer( @Lazy @Qualifier("sfConversionService") ConversionService conversionService, @Lazy ObjectMapper objectMapper, @Lazy FilterNodeArgumentResolverHelper filterNodeArgumentResolverHelper, @Lazy JsonNodeHelper jsonNodeHelper, @Lazy FilterNodeProcessorFactories filterNodeProcessorFactories, - FieldTypeResolver fieldTypeResolver) { + FieldTypeResolver fieldTypeResolver, + TransformerUtils transformerUtils) { this.conversionService = conversionService; this.objectMapper = objectMapper; this.filterNodeArgumentResolverHelper = filterNodeArgumentResolverHelper; this.jsonNodeHelper = jsonNodeHelper; this.filterNodeProcessorFactories = filterNodeProcessorFactories; this.fieldTypeResolver = fieldTypeResolver; + this.transformerUtils = transformerUtils; } @Override @@ -47,7 +52,7 @@ public void addArgumentResolvers( List resolvers) { resolvers.add(new FilterJsonNodeArgumentResolver(conversionService, objectMapper, filterNodeArgumentResolverHelper, jsonNodeHelper, - filterNodeProcessorFactories, fieldTypeResolver)); + filterNodeProcessorFactories, fieldTypeResolver, transformerUtils)); } } diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java index ee49dffb..780214ca 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java @@ -10,4 +10,6 @@ public interface TransformerUtils { JsonNode wrapArraysRegex(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source); JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source, String mongoOperator); + + JsonNode simplify(FilterJsonNodeTransformer transformer, JsonNode node); } diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java index b0e21f87..747b75ce 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java @@ -1,6 +1,7 @@ package com.turkraft.springfilter.helper; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.FilterNode; @@ -9,6 +10,7 @@ import org.springframework.stereotype.Component; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; @Component @@ -222,4 +224,96 @@ private static JsonNode getIfNullNode(FilterJsonNodeTransformer transformer, Str return transformer.getObjectMapper().createObjectNode().set("$ifNull", transformer.getObjectMapper().createArrayNode().add(input).add(transformer.getObjectMapper().createArrayNode())); } + + @Override + public JsonNode simplify(FilterJsonNodeTransformer transformer, JsonNode node) { + JsonNode result = node; + if (node.has("$and") && node.get("$and").isArray()) { + result = simplifyAnd(transformer, node); + fuseAnyElements(transformer, result, "$and"); + + } + if (node.has("$or") && node.get("$or").isArray()) { + result = simplifyOr(transformer, node); + fuseAnyElements(transformer, result, "$or"); + } + return result; + } + + private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonNode parentNode, String currentOperator) { + ArrayNode arrayNode = (ArrayNode) parentNode.get(currentOperator); + HashMap map = new HashMap<>(); + List toRemove = new ArrayList<>(); + + for (int i = 0; i < arrayNode.size(); i++) { + JsonNode anyElementChild = arrayNode.get(i); + if (anyElementChild.has("$anyElementTrue") && anyElementChild.get("$anyElementTrue").has("$map")) { + var mapNode = anyElementChild.get("$anyElementTrue").get("$map"); + if (mapNode.has("input") && mapNode.get("input").has("$ifNull")) { + var ifNullArray = mapNode.get("input").get("$ifNull").get(0); + if (ifNullArray.isTextual() && map.containsKey(ifNullArray.asText())) { + fuseExistingAndNew(transformer, map, ifNullArray, mapNode, currentOperator, toRemove, i); + } else { + map.put(ifNullArray.asText(), anyElementChild); + } + } + } + } + + toRemove.forEach(arrayNode::remove); + } + + private static void fuseExistingAndNew(FilterJsonNodeTransformer transformer, HashMap map, JsonNode ifNullArray, JsonNode mapNode, String currentOperator, List toRemove, int i) { + var existing = map.get(ifNullArray.asText()); + ObjectNode existingMap = (ObjectNode) existing.get("$anyElementTrue").get("$map"); + var existingIn = existingMap.get("in"); + var newIn = mapNode.get("in"); + ArrayNode newfusedArray = transformer.getObjectMapper().createArrayNode(); + newfusedArray.add(existingIn).add(newIn); + var newAgg = transformer.getObjectMapper().createObjectNode().set(currentOperator, newfusedArray); + existingMap.set("in", newAgg); + toRemove.add(i); + } + + private JsonNode simplifyAnd(FilterJsonNodeTransformer transformer, JsonNode node) { + List andNodes = new ArrayList<>(); + node.get("$and").elements().forEachRemaining(andNodes::add); + List simplifiedNodes = new ArrayList<>(); + for (JsonNode andNode : andNodes) { + simplifiedNodes.add(simplify(transformer, andNode)); + } + //pull out nested $and + List nestedAndNodes = new ArrayList<>(); + simplifiedNodes.forEach(n -> { + if (n.has("$and")) { + n.get("$and").elements().forEachRemaining(nestedAndNodes::add); + } else { + nestedAndNodes.add(n); + } + }); + return transformer.getObjectMapper().createObjectNode().set("$and", + transformer.getObjectMapper().createArrayNode().addAll(nestedAndNodes)); + } + + private JsonNode simplifyOr(FilterJsonNodeTransformer transformer, JsonNode node) { + List orNodes = new ArrayList<>(); + node.get("$or").elements().forEachRemaining(orNodes::add); + List simplifiedNodes = new ArrayList<>(); + for (JsonNode orNode : orNodes) { + simplifiedNodes.add(simplify(transformer, orNode)); + } + //pull out nested $or + List nestedOrNodes = new ArrayList<>(); + simplifiedNodes.forEach(n -> { + if (n.has("$or")) { + n.get("$or").elements().forEachRemaining(nestedOrNodes::add); + } else { + nestedOrNodes.add(n); + } + }); + return transformer.getObjectMapper().createObjectNode().set("$or", + transformer.getObjectMapper().createArrayNode().addAll(nestedOrNodes)); + } + + } diff --git a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java new file mode 100644 index 00000000..8260bb36 --- /dev/null +++ b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java @@ -0,0 +1,268 @@ +package com.turkraft.springfilter.helper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.turkraft.springfilter.TestEntity; +import com.turkraft.springfilter.builder.FilterBuilder; +import com.turkraft.springfilter.parser.node.InfixOperationNode; +import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; +import com.turkraft.springfilter.transformer.processor.factory.FilterNodeProcessorFactories; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.ConversionService; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +class TransformerUtilsImplTest { + + @Autowired + private ConversionService conversionService; + @Autowired + private ObjectMapper objectMapper; + @Autowired + private FilterBuilder fb; + @Autowired + private FilterNodeProcessorFactories filterNodeProcessorFactories; + @Autowired + private FieldTypeResolver fieldTypeResolver; + private FilterJsonNodeTransformer transformer; + @Mock + private JsonNode node; + @Mock + private InfixOperationNode source; + private TransformerUtils transformerUtils; + + @BeforeEach + void setUp() { + transformerUtils = new TransformerUtilsImpl(fieldTypeResolver); + transformer = new FilterJsonNodeTransformer(conversionService, objectMapper, filterNodeProcessorFactories, fieldTypeResolver, + TestEntity.class); + } + + @Test + void simplifyShouldReturnNodeWhenNoAndOrOrPresent() { + when(node.has("$and")).thenReturn(false); + when(node.has("$or")).thenReturn(false); + + JsonNode result = transformerUtils.simplify(transformer, node); + + assertSame(node, result); + } + + @Test + void simplifyShouldFuseAndElementsWhenAndPresent() { + //arrange + // $and: [ { $and: [ {}, {} ] }, {} , {}] + ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); + arrayNode.add(mock(ObjectNode.class)); + arrayNode.add(mock(ObjectNode.class)); + ArrayNode innerAndArray = transformer.getObjectMapper().createArrayNode(); + innerAndArray.add(mock(ObjectNode.class)); + innerAndArray.add(mock(ObjectNode.class)); + ObjectNode innerAnd = transformer.getObjectMapper().createObjectNode(); + innerAnd.set("$and", innerAndArray); + arrayNode.add(innerAnd); + + ObjectNode input = transformer.getObjectMapper().createObjectNode(); + input.set("$and", arrayNode); + + //act + JsonNode result = transformerUtils.simplify(transformer, input); + + //assert + assertTrue(result.has("$and")); + assertEquals(4, result.get("$and").size()); + } + + @Test + void simplifyShouldFuseOrElementsWhenOrPresent() { + //arrange + // $or: [ { $or: [ {}, {} ] }, {} , {}] + ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); + arrayNode.add(mock(ObjectNode.class)); + arrayNode.add(mock(ObjectNode.class)); + ArrayNode innerOrArray = transformer.getObjectMapper().createArrayNode(); + innerOrArray.add(mock(ObjectNode.class)); + innerOrArray.add(mock(ObjectNode.class)); + ObjectNode innerOr = transformer.getObjectMapper().createObjectNode(); + innerOr.set("$or", innerOrArray); + arrayNode.add(innerOr); + + ObjectNode input = transformer.getObjectMapper().createObjectNode(); + input.set("$or", arrayNode); + + //act + JsonNode result = transformerUtils.simplify(transformer, input); + + //assert + assertTrue(result.has("$or")); + assertEquals(4, result.get("$or").size()); + } + + @Test + void simplifyShouldFuseAndAndOrElementsWhenAndAndOrPresent() { + //arrange + // $and: [ { $and: [ { $or: [ {}, {} ] }, {} ] }, {} , {}] + ArrayNode innerOrArray = transformer.getObjectMapper().createArrayNode(); + innerOrArray.add(mock(ObjectNode.class)); + innerOrArray.add(mock(ObjectNode.class)); + ObjectNode innerOr = transformer.getObjectMapper().createObjectNode(); + innerOr.set("$or", innerOrArray); + + ArrayNode innerAndArray = transformer.getObjectMapper().createArrayNode(); + innerAndArray.add(innerOr); + innerAndArray.add(mock(ObjectNode.class)); + ObjectNode innerAnd = transformer.getObjectMapper().createObjectNode(); + innerAnd.set("$and", innerAndArray); + ObjectNode input = transformer.getObjectMapper().createObjectNode(); + ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); + + arrayNode.add(mock(ObjectNode.class)); + arrayNode.add(mock(ObjectNode.class)); + arrayNode.add(innerAnd); + input.set("$and", arrayNode); + + //act + JsonNode result = transformerUtils.simplify(transformer, input); + + //assert + assertTrue(result.has("$and")); + assertEquals(4, result.get("$and").size()); + for (JsonNode node : result.get("$and")) { + if (node.has("$or")) { + assertEquals(2, node.get("$or").size()); + } + } + } + @Test + void simplifyShouldNotFuseAnyElemenTrueWithDifferentMapping() { + //arrange + // $and: [ + // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping1", [] ] }, as: "this", in: filter } }, + // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping2", [] ] }, as: "this", in: filter } }, + // {} + // ] + ObjectNode filter = transformer.getObjectMapper().createObjectNode(); + filter.put("$key", "value"); + ObjectNode filter2 = transformer.getObjectMapper().createObjectNode(); + filter2.put("$key", "value2"); + JsonNode anyElementsNode1 = getAnyElementsNode("$mapping1", filter); + JsonNode anyElementsNode2 = getAnyElementsNode("$mapping2", filter2); + ObjectNode input = transformer.getObjectMapper().createObjectNode(); + ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); + arrayNode.add(mock(ObjectNode.class)); + arrayNode.add(anyElementsNode1); + arrayNode.add(anyElementsNode2); + input.set("$and", arrayNode); + + + //act + JsonNode result = transformerUtils.simplify(transformer, input); + + //assert + assertTrue(result.has("$and")); + assertEquals(3, result.get("$and").size()); + int count = 0; + Set values = new HashSet<>(); + for (JsonNode node : result.get("$and")) { + if(node.has("$anyElementTrue")){ + assertTrue(node.has("$anyElementTrue")); + assertTrue(node.get("$anyElementTrue").get("$map").get("in").has("$key")); + values.add(node.get("$anyElementTrue").get("$map").get("in").get("$key").asText()); + count++; + } + } + assertEquals(2, count); + assertEquals(2, values.size()); + } + + @Test + void simplifyShouldFuseAnyElemenTrueWithSameMapping() { + //arrange + // $and: [ + // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }, + // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }, + // {} + // ] + ObjectNode filter = transformer.getObjectMapper().createObjectNode(); + filter.put("$key", "value"); + ObjectNode filter2 = transformer.getObjectMapper().createObjectNode(); + filter2.put("$key", "value2"); + JsonNode anyElementsNode1 = getAnyElementsNode("$mapping", filter); + JsonNode anyElementsNode2 = getAnyElementsNode("$mapping", filter2); + ObjectNode input = transformer.getObjectMapper().createObjectNode(); + ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); + arrayNode.add(mock(ObjectNode.class)); + arrayNode.add(anyElementsNode1); + arrayNode.add(anyElementsNode2); + input.set("$and", arrayNode); + + + //act + JsonNode result = transformerUtils.simplify(transformer, input); + + //assert + assertTrue(result.has("$and")); + assertEquals(2, result.get("$and").size()); + int count = 0; + for (JsonNode node : result.get("$and")) { + if(node.has("$anyElementTrue")){ + assertTrue(node.has("$anyElementTrue")); + assertEquals(2, node.get("$anyElementTrue").get("$map").get("in").get("$and").size()); + count++; + } + } + assertEquals(1, count); + } + + JsonNode getAnyElementsNode(String mapping, JsonNode filter) { +/* { + "$anyElementTrue": { + "$map": { + "input": { + "$ifNull": [ + "$mapping", + [] + ] + }, + "as": "this", + "in": filter + } + }*/ + ObjectNode result = objectMapper.createObjectNode(); + ObjectNode anyElementTrue = objectMapper.createObjectNode(); + ObjectNode map = objectMapper.createObjectNode(); + ObjectNode input = objectMapper.createObjectNode(); + ArrayNode ifNull = objectMapper.createArrayNode(); + ifNull.add(mapping); + ifNull.add(objectMapper.createArrayNode()); + input.set("$ifNull", ifNull); + map.set("input", input); + map.put("as", "this"); + map.set("in", filter); + anyElementTrue.set("$map", map); + return result.set("$anyElementTrue", anyElementTrue); + } + + @Configuration + @ComponentScan("com.turkraft.springfilter") + static class Config { + + } +} From fe91610b0e507f0f35ae239ff51e9b6bf234fe4b Mon Sep 17 00:00:00 2001 From: Elias Huwyler Date: Tue, 25 Jun 2024 21:20:03 +0200 Subject: [PATCH 4/7] fix issue due to POJONodes being present --- .../springfilter/helper/TransformerUtilsImpl.java | 14 +++++++------- .../helper/TransformerUtilsImplTest.java | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java index 747b75ce..12c6ee69 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java @@ -249,12 +249,12 @@ private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonN JsonNode anyElementChild = arrayNode.get(i); if (anyElementChild.has("$anyElementTrue") && anyElementChild.get("$anyElementTrue").has("$map")) { var mapNode = anyElementChild.get("$anyElementTrue").get("$map"); - if (mapNode.has("input") && mapNode.get("input").has("$ifNull")) { - var ifNullArray = mapNode.get("input").get("$ifNull").get(0); - if (ifNullArray.isTextual() && map.containsKey(ifNullArray.asText())) { - fuseExistingAndNew(transformer, map, ifNullArray, mapNode, currentOperator, toRemove, i); + if (mapNode.has("input")) { + var inputAsText= mapNode.get("input").asText(); + if (map.containsKey(inputAsText)) { + fuseExistingAndNew(transformer, map, inputAsText, mapNode, currentOperator, toRemove, i); } else { - map.put(ifNullArray.asText(), anyElementChild); + map.put(inputAsText, anyElementChild); } } } @@ -263,8 +263,8 @@ private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonN toRemove.forEach(arrayNode::remove); } - private static void fuseExistingAndNew(FilterJsonNodeTransformer transformer, HashMap map, JsonNode ifNullArray, JsonNode mapNode, String currentOperator, List toRemove, int i) { - var existing = map.get(ifNullArray.asText()); + private static void fuseExistingAndNew(FilterJsonNodeTransformer transformer, HashMap map, String inputNode, JsonNode mapNode, String currentOperator, List toRemove, int i) { + var existing = map.get(inputNode); ObjectNode existingMap = (ObjectNode) existing.get("$anyElementTrue").get("$map"); var existingIn = existingMap.get("in"); var newIn = mapNode.get("in"); diff --git a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java index 8260bb36..b9642758 100644 --- a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java +++ b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.POJONode; import com.turkraft.springfilter.TestEntity; import com.turkraft.springfilter.builder.FilterBuilder; import com.turkraft.springfilter.parser.node.InfixOperationNode; @@ -248,12 +249,12 @@ JsonNode getAnyElementsNode(String mapping, JsonNode filter) { ObjectNode result = objectMapper.createObjectNode(); ObjectNode anyElementTrue = objectMapper.createObjectNode(); ObjectNode map = objectMapper.createObjectNode(); - ObjectNode input = objectMapper.createObjectNode(); + var input = objectMapper.createObjectNode(); ArrayNode ifNull = objectMapper.createArrayNode(); ifNull.add(mapping); ifNull.add(objectMapper.createArrayNode()); input.set("$ifNull", ifNull); - map.set("input", input); + map.set("input",new POJONode(input)); map.put("as", "this"); map.set("in", filter); anyElementTrue.set("$map", map); From 32fd3523827ecd83c23162de3d50ffff4f8114bb Mon Sep 17 00:00:00 2001 From: Elias Huwyler Date: Tue, 25 Jun 2024 21:28:48 +0200 Subject: [PATCH 5/7] removed POJONode from tree --- .../helper/TransformerUtilsImpl.java | 40 +++++++++---------- .../helper/TransformerUtilsImplTest.java | 5 +-- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java index 12c6ee69..50d1c775 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java @@ -55,9 +55,7 @@ public JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, resultNode = getJsonNode(transformer, input, newNode); } else { - JsonNode ifNull = getIfNullNode(transformer, input); - - resultNode = getMapNode(transformer, ifNull, resultNode); + resultNode = getMapNode(transformer, getIfNullNode(transformer, input), resultNode); } } @@ -113,9 +111,8 @@ public JsonNode wrapArraysRegex(FilterJsonNodeTransformer transformer, JsonNode resultNode = getJsonNode(transformer, input, node); } else { - JsonNode ifNull = getIfNullNode(transformer, input); - resultNode = getMapNode(transformer, ifNull, resultNode); + resultNode = getMapNode(transformer, getIfNullNode(transformer, input), resultNode); } } @@ -123,11 +120,12 @@ public JsonNode wrapArraysRegex(FilterJsonNodeTransformer transformer, JsonNode } private static JsonNode getMapNode(FilterJsonNodeTransformer transformer, JsonNode ifNull, JsonNode resultNode) { + ObjectNode mapNode = transformer.getObjectMapper().createObjectNode(); + mapNode.set("input", ifNull); + mapNode.set("in", resultNode); return transformer.getObjectMapper().createObjectNode().set("$anyElementTrue", transformer.getObjectMapper().createObjectNode().set("$map", - transformer.getObjectMapper().createObjectNode() - .putPOJO("input", ifNull) - .set("in", resultNode))); + mapNode)); } public JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source, String mongoOperator) { @@ -157,9 +155,8 @@ public JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, resultNode = getJsonNode(transformer, input, newNode); } else { - JsonNode ifNull = getIfNullNode(transformer, input); - resultNode = getMapNode(transformer, ifNull, resultNode); + resultNode = getMapNode(transformer, getIfNullNode(transformer, input), resultNode); } } @@ -211,12 +208,13 @@ private JsonNode getJsonNode(FilterJsonNodeTransformer transformer, String input JsonNode resultNode; JsonNode ifNull = getIfNullNode(transformer, input); + ObjectNode mapNode = transformer.getObjectMapper().createObjectNode(); + mapNode.put("input", ifNull); + mapNode.put("as", "this"); + mapNode.set("in", newNode); resultNode = transformer.getObjectMapper().createObjectNode().set("$anyElementTrue", transformer.getObjectMapper().createObjectNode().set("$map", - transformer.getObjectMapper().createObjectNode() - .putPOJO("input", ifNull) - .put("as", "this") - .set("in", newNode))); + mapNode)); return resultNode; } @@ -249,12 +247,12 @@ private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonN JsonNode anyElementChild = arrayNode.get(i); if (anyElementChild.has("$anyElementTrue") && anyElementChild.get("$anyElementTrue").has("$map")) { var mapNode = anyElementChild.get("$anyElementTrue").get("$map"); - if (mapNode.has("input")) { - var inputAsText= mapNode.get("input").asText(); - if (map.containsKey(inputAsText)) { - fuseExistingAndNew(transformer, map, inputAsText, mapNode, currentOperator, toRemove, i); + if (mapNode.has("input") && mapNode.get("input").has("$ifNull")) { + var ifNullArray = mapNode.get("input").get("$ifNull").get(0); + if (ifNullArray.isTextual() && map.containsKey(ifNullArray.asText())) { + fuseExistingAndNew(transformer, map, ifNullArray, mapNode, currentOperator, toRemove, i); } else { - map.put(inputAsText, anyElementChild); + map.put(ifNullArray.asText(), anyElementChild); } } } @@ -263,8 +261,8 @@ private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonN toRemove.forEach(arrayNode::remove); } - private static void fuseExistingAndNew(FilterJsonNodeTransformer transformer, HashMap map, String inputNode, JsonNode mapNode, String currentOperator, List toRemove, int i) { - var existing = map.get(inputNode); + private static void fuseExistingAndNew(FilterJsonNodeTransformer transformer, HashMap map, JsonNode ifNullArray, JsonNode mapNode, String currentOperator, List toRemove, int i) { + var existing = map.get(ifNullArray.asText()); ObjectNode existingMap = (ObjectNode) existing.get("$anyElementTrue").get("$map"); var existingIn = existingMap.get("in"); var newIn = mapNode.get("in"); diff --git a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java index b9642758..8260bb36 100644 --- a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java +++ b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.POJONode; import com.turkraft.springfilter.TestEntity; import com.turkraft.springfilter.builder.FilterBuilder; import com.turkraft.springfilter.parser.node.InfixOperationNode; @@ -249,12 +248,12 @@ JsonNode getAnyElementsNode(String mapping, JsonNode filter) { ObjectNode result = objectMapper.createObjectNode(); ObjectNode anyElementTrue = objectMapper.createObjectNode(); ObjectNode map = objectMapper.createObjectNode(); - var input = objectMapper.createObjectNode(); + ObjectNode input = objectMapper.createObjectNode(); ArrayNode ifNull = objectMapper.createArrayNode(); ifNull.add(mapping); ifNull.add(objectMapper.createArrayNode()); input.set("$ifNull", ifNull); - map.set("input",new POJONode(input)); + map.set("input", input); map.put("as", "this"); map.set("in", filter); anyElementTrue.set("$map", map); From 27f7111009b8a3bf293c47acce2dbb06970e33f0 Mon Sep 17 00:00:00 2001 From: Elias Huwyler Date: Tue, 25 Jun 2024 22:18:15 +0200 Subject: [PATCH 6/7] include the custom Date Converter --- .../converter/StringCustomDateConverter.java | 37 +++++++ .../helper/FieldTypeResolverImpl.java | 103 ++++++++++-------- .../helper/TransformerUtilsImpl.java | 6 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../helper/TransformerUtilsImplTest.java | 98 ++++++++++++----- 5 files changed, 173 insertions(+), 72 deletions(-) create mode 100644 mongo/src/main/java/com/turkraft/springfilter/converter/StringCustomDateConverter.java diff --git a/mongo/src/main/java/com/turkraft/springfilter/converter/StringCustomDateConverter.java b/mongo/src/main/java/com/turkraft/springfilter/converter/StringCustomDateConverter.java new file mode 100644 index 00000000..1ff9b13d --- /dev/null +++ b/mongo/src/main/java/com/turkraft/springfilter/converter/StringCustomDateConverter.java @@ -0,0 +1,37 @@ +package com.turkraft.springfilter.converter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.turkraft.springfilter.converter.StringCustomDateConverter.CustomDate; +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Service; + +import java.io.Serializable; + +@Service +public class StringCustomDateConverter implements Converter { + + @Override + public CustomDate convert(String source) { + return new CustomDate(source); + } + + public static class CustomDate implements Serializable { + + @JsonProperty("$date") + private String value; + + public CustomDate(String id) { + this.value = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } + +} diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java index 3ef5b8e0..d9ca8ed2 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/FieldTypeResolverImpl.java @@ -1,76 +1,89 @@ package com.turkraft.springfilter.helper; +import com.turkraft.springfilter.converter.StringCustomDateConverter; import com.turkraft.springfilter.converter.StringCustomObjectIdConverter.CustomObjectId; import com.turkraft.springfilter.converter.StringCustomUUIDConverter.CustomUUID; +import org.springframework.data.annotation.Id; +import org.springframework.stereotype.Service; +import org.springframework.util.ReflectionUtils; + import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.Collection; import java.util.UUID; -import org.springframework.data.annotation.Id; -import org.springframework.stereotype.Service; -import org.springframework.util.ReflectionUtils; @Service class FieldTypeResolverImpl implements FieldTypeResolver { - @Override - public Class resolve(Class klass, String path) { - return normalize(getField(klass, path)); - } + @Override + public Class resolve(Class klass, String path) { + return normalize(getField(klass, path)); + } - @Override - public Field getField(Class klass, final String path) { + @Override + public Field getField(Class klass, final String path) { - String[] fieldNames = path.split("\\."); + String[] fieldNames = path.split("\\."); - Field lastField = null; + Field lastField = null; - for (String fieldName : fieldNames) { + for (String fieldName : fieldNames) { - lastField = ReflectionUtils.findField(klass, fieldName); + lastField = ReflectionUtils.findField(klass, fieldName); - if (lastField != null) { - klass = normalize(lastField); - } else { - throw new IllegalArgumentException("Could not find field '" + fieldName + "' in " + klass); - } + if (lastField != null) { + klass = normalize(lastField); + } else { + throw new IllegalArgumentException("Could not find field '" + fieldName + "' in " + klass); + } - } + } - return lastField; + return lastField; - } + } - private Class normalize(Field field) { + private Class normalize(Field field) { + + if (field.isAnnotationPresent(Id.class) && field.getType().equals(String.class)) { + return CustomObjectId.class; + } + + if (field.getType().equals(UUID.class)) { + return CustomUUID.class; + } + if (field.getType().equals(LocalDateTime.class) + || field.getType().equals(ZonedDateTime.class) + || field.getType().equals(LocalDate.class) + || field.getType().equals(java.util.Date.class) + || field.getType().equals(Instant.class)) { + return StringCustomDateConverter.CustomDate.class; + } + + if (Collection.class.isAssignableFrom(field.getType())) { + return getFirstTypeParameterOf(field); + } else if (field.getType().isArray()) { + return field.getType().getComponentType(); + } else { + return field.getType(); + } - if (field.isAnnotationPresent(Id.class) && field.getType().equals(String.class)) { - return CustomObjectId.class; } - if (field.getType().equals(UUID.class)) { - return CustomUUID.class; + public boolean isIterable(Class klass, String path) { + return isIterable(getField(klass, path)); } - if (Collection.class.isAssignableFrom(field.getType())) { - return getFirstTypeParameterOf(field); - } else if (field.getType().isArray()) { - return field.getType().getComponentType(); - } else { - return field.getType(); + private boolean isIterable(Field field) { + return Collection.class.isAssignableFrom(field.getType()) || field.getType().isArray(); } - } - - public boolean isIterable(Class klass, String path) { - return isIterable(getField(klass, path)); - } - - private boolean isIterable(Field field) { - return Collection.class.isAssignableFrom(field.getType()) || field.getType().isArray(); - } - - private Class getFirstTypeParameterOf(Field field) { - return (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; - } + private Class getFirstTypeParameterOf(Field field) { + return (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + } } diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java index 50d1c775..2b5060e0 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java @@ -229,11 +229,17 @@ public JsonNode simplify(FilterJsonNodeTransformer transformer, JsonNode node) { if (node.has("$and") && node.get("$and").isArray()) { result = simplifyAnd(transformer, node); fuseAnyElements(transformer, result, "$and"); + if(result.has("$and") && result.get("$and").size() == 1){ + result = result.get("$and").get(0); + } } if (node.has("$or") && node.get("$or").isArray()) { result = simplifyOr(transformer, node); fuseAnyElements(transformer, result, "$or"); + if(result.has("$or") && result.get("$or").size() == 1){ + result = result.get("$or").get(0); + } } return result; } diff --git a/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c5278cd8..94650d5b 100644 --- a/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -6,6 +6,7 @@ com.turkraft.springfilter.helper.TransformerUtilsImpl com.turkraft.springfilter.converter.StringCustomObjectIdConverter com.turkraft.springfilter.converter.StringCustomUUIDConverter +com.turkraft.springfilter.converter.StringCustomDateConverter com.turkraft.springfilter.transformer.processor.TodayFunctionJsonNodeProcessor com.turkraft.springfilter.transformer.processor.OrOperationJsonNodeProcessor diff --git a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java index 8260bb36..ee457e0d 100644 --- a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java +++ b/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.POJONode; import com.turkraft.springfilter.TestEntity; import com.turkraft.springfilter.builder.FilterBuilder; import com.turkraft.springfilter.parser.node.InfixOperationNode; @@ -67,8 +68,10 @@ void simplifyShouldReturnNodeWhenNoAndOrOrPresent() { @Test void simplifyShouldFuseAndElementsWhenAndPresent() { + // {$and: [ { $and: [ {}, {} ] }, {} , {}]} + // => + // {$and: [ {}, {}, {}, {}]} //arrange - // $and: [ { $and: [ {}, {} ] }, {} , {}] ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); arrayNode.add(mock(ObjectNode.class)); arrayNode.add(mock(ObjectNode.class)); @@ -92,8 +95,10 @@ void simplifyShouldFuseAndElementsWhenAndPresent() { @Test void simplifyShouldFuseOrElementsWhenOrPresent() { + // {$or: [ { $or: [ {}, {} ] }, {} , {}]} + // => + // {$or: [ {}, {}, {}, {}]} //arrange - // $or: [ { $or: [ {}, {} ] }, {} , {}] ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); arrayNode.add(mock(ObjectNode.class)); arrayNode.add(mock(ObjectNode.class)); @@ -117,8 +122,10 @@ void simplifyShouldFuseOrElementsWhenOrPresent() { @Test void simplifyShouldFuseAndAndOrElementsWhenAndAndOrPresent() { + // {$and: [ { $and: [ { $or: [ {}, {} ] }, {} ] }, {} , {}]} + // => + // {$and: [ { $or: [ {}, {} ] }, {} , {}, {}]} //arrange - // $and: [ { $and: [ { $or: [ {}, {} ] }, {} ] }, {} , {}] ArrayNode innerOrArray = transformer.getObjectMapper().createArrayNode(); innerOrArray.add(mock(ObjectNode.class)); innerOrArray.add(mock(ObjectNode.class)); @@ -150,14 +157,16 @@ void simplifyShouldFuseAndAndOrElementsWhenAndAndOrPresent() { } } } + @Test void simplifyShouldNotFuseAnyElemenTrueWithDifferentMapping() { - //arrange - // $and: [ + // {$and: [ // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping1", [] ] }, as: "this", in: filter } }, // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping2", [] ] }, as: "this", in: filter } }, // {} - // ] + // ]} + // => no change + //arrange ObjectNode filter = transformer.getObjectMapper().createObjectNode(); filter.put("$key", "value"); ObjectNode filter2 = transformer.getObjectMapper().createObjectNode(); @@ -181,7 +190,7 @@ void simplifyShouldNotFuseAnyElemenTrueWithDifferentMapping() { int count = 0; Set values = new HashSet<>(); for (JsonNode node : result.get("$and")) { - if(node.has("$anyElementTrue")){ + if (node.has("$anyElementTrue")) { assertTrue(node.has("$anyElementTrue")); assertTrue(node.get("$anyElementTrue").get("$map").get("in").has("$key")); values.add(node.get("$anyElementTrue").get("$map").get("in").get("$key").asText()); @@ -194,12 +203,17 @@ void simplifyShouldNotFuseAnyElemenTrueWithDifferentMapping() { @Test void simplifyShouldFuseAnyElemenTrueWithSameMapping() { - //arrange - // $and: [ - // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }, - // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }, + // {$and: [ + // {$anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }}, + // {$anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }}, // {} - // ] + // ]} + // => + //{$and: [ + // {$anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: { $and: [ filter, filter ] } } }, + // {} + //]} + //arrange ObjectNode filter = transformer.getObjectMapper().createObjectNode(); filter.put("$key", "value"); ObjectNode filter2 = transformer.getObjectMapper().createObjectNode(); @@ -222,7 +236,7 @@ void simplifyShouldFuseAnyElemenTrueWithSameMapping() { assertEquals(2, result.get("$and").size()); int count = 0; for (JsonNode node : result.get("$and")) { - if(node.has("$anyElementTrue")){ + if (node.has("$anyElementTrue")) { assertTrue(node.has("$anyElementTrue")); assertEquals(2, node.get("$anyElementTrue").get("$map").get("in").get("$and").size()); count++; @@ -231,24 +245,54 @@ void simplifyShouldFuseAnyElemenTrueWithSameMapping() { assertEquals(1, count); } + @Test + void simplifyShouldFuseAnyElemenTrueWithSameMappingAndRemoveSingleElementAndOrOrs() { + // {$and: [ + // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter1 } }, + // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter2 } }, + // ]} + // => + // {$anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: { $and: [ filter1, filter2 ] } } }} + //arrange + ObjectNode filter = transformer.getObjectMapper().createObjectNode(); + filter.put("$key", "value"); + ObjectNode filter2 = transformer.getObjectMapper().createObjectNode(); + filter2.put("$key", "value2"); + JsonNode anyElementsNode1 = getAnyElementsNode("$mapping", filter); + JsonNode anyElementsNode2 = getAnyElementsNode("$mapping", filter2); + ObjectNode input = transformer.getObjectMapper().createObjectNode(); + ArrayNode arrayNode = transformer.getObjectMapper().createArrayNode(); + arrayNode.add(anyElementsNode1); + arrayNode.add(anyElementsNode2); + input.set("$and", arrayNode); + + + //act + JsonNode result = transformerUtils.simplify(transformer, input); + + //assert + assertTrue(result.has("$anyElementTrue")); + assertEquals(2, result.get("$anyElementTrue").get("$map").get("in").get("$and").size()); + } + JsonNode getAnyElementsNode(String mapping, JsonNode filter) { -/* { - "$anyElementTrue": { - "$map": { - "input": { - "$ifNull": [ - "$mapping", - [] - ] - }, - "as": "this", - "in": filter - } - }*/ + //{ + // "$anyElementTrue": { + // "$map": { + // "input": { + // "$ifNull": [ + // "$mapping", + // [] + // ] + // }, + // "as": "this", + // "in": filter + // } + //} ObjectNode result = objectMapper.createObjectNode(); ObjectNode anyElementTrue = objectMapper.createObjectNode(); ObjectNode map = objectMapper.createObjectNode(); - ObjectNode input = objectMapper.createObjectNode(); + var input = objectMapper.createObjectNode(); ArrayNode ifNull = objectMapper.createArrayNode(); ifNull.add(mapping); ifNull.add(objectMapper.createArrayNode()); From 9b8f37c423051af27c6e6abb20fb3b0ff0e7aef8 Mon Sep 17 00:00:00 2001 From: Elias Huwyler Date: Wed, 26 Jun 2024 20:20:28 +0200 Subject: [PATCH 7/7] move transformer class --- .../boot/FilterJsonNodeArgumentResolver.java | 2 +- ...ilterJsonNodeArgumentResolverConfigurer.java | 2 +- .../springfilter/helper/JsonNodeHelperImpl.java | 1 + .../TransformerUtils.java | 3 +-- .../TransformerUtilsImpl.java | 15 +++++++-------- .../processor/InOperationJsonNodeProcessor.java | 2 +- .../LikeOperationJsonNodeProcessor.java | 2 +- .../NotInOperationJsonNodeProcessor.java | 2 +- ...boot.autoconfigure.AutoConfiguration.imports | 2 +- .../TransformerUtilsImplTest.java | 17 ++++++++--------- pom.xml | 2 +- 11 files changed, 24 insertions(+), 26 deletions(-) rename mongo/src/main/java/com/turkraft/springfilter/{helper => transformer}/TransformerUtils.java (83%) rename mongo/src/main/java/com/turkraft/springfilter/{helper => transformer}/TransformerUtilsImpl.java (95%) rename mongo/src/test/java/com/turkraft/springfilter/{helper => transformer}/TransformerUtilsImplTest.java (95%) diff --git a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java index b524d32a..abc2d60f 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java +++ b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolver.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.turkraft.springfilter.helper.FieldTypeResolver; import com.turkraft.springfilter.helper.JsonNodeHelper; -import com.turkraft.springfilter.helper.TransformerUtils; +import com.turkraft.springfilter.transformer.TransformerUtils; import com.turkraft.springfilter.parser.node.FilterNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; import com.turkraft.springfilter.transformer.processor.factory.FilterNodeProcessorFactories; diff --git a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java index 7e4f0017..8b5351a5 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java +++ b/mongo/src/main/java/com/turkraft/springfilter/boot/FilterJsonNodeArgumentResolverConfigurer.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.turkraft.springfilter.helper.FieldTypeResolver; import com.turkraft.springfilter.helper.JsonNodeHelper; -import com.turkraft.springfilter.helper.TransformerUtils; +import com.turkraft.springfilter.transformer.TransformerUtils; import com.turkraft.springfilter.transformer.processor.factory.FilterNodeProcessorFactories; import java.util.List; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java index 7f2e987a..b08eec4d 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java @@ -6,6 +6,7 @@ import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; +import com.turkraft.springfilter.transformer.TransformerUtils; import org.springframework.stereotype.Service; @Service diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtils.java similarity index 83% rename from mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java rename to mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtils.java index 780214ca..e7391d7e 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtils.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtils.java @@ -1,8 +1,7 @@ -package com.turkraft.springfilter.helper; +package com.turkraft.springfilter.transformer; import com.fasterxml.jackson.databind.JsonNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; -import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; public interface TransformerUtils { JsonNode wrapArrays(FilterJsonNodeTransformer transformer, JsonNode node, InfixOperationNode source); diff --git a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtilsImpl.java similarity index 95% rename from mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java rename to mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtilsImpl.java index 2b5060e0..830ea598 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/helper/TransformerUtilsImpl.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtilsImpl.java @@ -1,12 +1,12 @@ -package com.turkraft.springfilter.helper; +package com.turkraft.springfilter.transformer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.turkraft.springfilter.helper.FieldTypeResolver; import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.FilterNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; -import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -228,15 +228,14 @@ public JsonNode simplify(FilterJsonNodeTransformer transformer, JsonNode node) { JsonNode result = node; if (node.has("$and") && node.get("$and").isArray()) { result = simplifyAnd(transformer, node); - fuseAnyElements(transformer, result, "$and"); + mergeAnyElements(transformer, result, "$and"); if(result.has("$and") && result.get("$and").size() == 1){ result = result.get("$and").get(0); } - } if (node.has("$or") && node.get("$or").isArray()) { result = simplifyOr(transformer, node); - fuseAnyElements(transformer, result, "$or"); + mergeAnyElements(transformer, result, "$or"); if(result.has("$or") && result.get("$or").size() == 1){ result = result.get("$or").get(0); } @@ -244,7 +243,7 @@ public JsonNode simplify(FilterJsonNodeTransformer transformer, JsonNode node) { return result; } - private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonNode parentNode, String currentOperator) { + private static void mergeAnyElements(FilterJsonNodeTransformer transformer, JsonNode parentNode, String currentOperator) { ArrayNode arrayNode = (ArrayNode) parentNode.get(currentOperator); HashMap map = new HashMap<>(); List toRemove = new ArrayList<>(); @@ -256,7 +255,7 @@ private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonN if (mapNode.has("input") && mapNode.get("input").has("$ifNull")) { var ifNullArray = mapNode.get("input").get("$ifNull").get(0); if (ifNullArray.isTextual() && map.containsKey(ifNullArray.asText())) { - fuseExistingAndNew(transformer, map, ifNullArray, mapNode, currentOperator, toRemove, i); + mergeExistingAndNew(transformer, map, ifNullArray, mapNode, currentOperator, toRemove, i); } else { map.put(ifNullArray.asText(), anyElementChild); } @@ -267,7 +266,7 @@ private static void fuseAnyElements(FilterJsonNodeTransformer transformer, JsonN toRemove.forEach(arrayNode::remove); } - private static void fuseExistingAndNew(FilterJsonNodeTransformer transformer, HashMap map, JsonNode ifNullArray, JsonNode mapNode, String currentOperator, List toRemove, int i) { + private static void mergeExistingAndNew(FilterJsonNodeTransformer transformer, HashMap map, JsonNode ifNullArray, JsonNode mapNode, String currentOperator, List toRemove, int i) { var existing = map.get(ifNullArray.asText()); ObjectNode existingMap = (ObjectNode) existing.get("$anyElementTrue").get("$map"); var existingIn = existingMap.get("in"); diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java index 5187b890..f292d714 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java @@ -6,7 +6,7 @@ import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; -import com.turkraft.springfilter.helper.TransformerUtils; +import com.turkraft.springfilter.transformer.TransformerUtils; import org.springframework.stereotype.Component; @Component diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java index 3c9557fc..001fe3ae 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/LikeOperationJsonNodeProcessor.java @@ -8,7 +8,7 @@ import com.turkraft.springfilter.parser.node.InfixOperationNode; import com.turkraft.springfilter.parser.node.InputNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; -import com.turkraft.springfilter.helper.TransformerUtils; +import com.turkraft.springfilter.transformer.TransformerUtils; import org.springframework.data.annotation.Id; import org.springframework.stereotype.Component; diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java index 59a2fe98..f3ddd536 100644 --- a/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/NotInOperationJsonNodeProcessor.java @@ -6,7 +6,7 @@ import com.turkraft.springfilter.parser.node.FieldNode; import com.turkraft.springfilter.parser.node.InfixOperationNode; import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; -import com.turkraft.springfilter.helper.TransformerUtils; +import com.turkraft.springfilter.transformer.TransformerUtils; import org.springframework.stereotype.Component; @Component diff --git a/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 94650d5b..0cf96bcc 100644 --- a/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/mongo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -2,7 +2,7 @@ com.turkraft.springfilter.boot.FilterJsonNodeArgumentResolverConfigurer com.turkraft.springfilter.helper.JsonNodeHelperImpl com.turkraft.springfilter.helper.FieldTypeResolverImpl -com.turkraft.springfilter.helper.TransformerUtilsImpl +com.turkraft.springfilter.transformer.TransformerUtilsImpl com.turkraft.springfilter.converter.StringCustomObjectIdConverter com.turkraft.springfilter.converter.StringCustomUUIDConverter diff --git a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java b/mongo/src/test/java/com/turkraft/springfilter/transformer/TransformerUtilsImplTest.java similarity index 95% rename from mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java rename to mongo/src/test/java/com/turkraft/springfilter/transformer/TransformerUtilsImplTest.java index ee457e0d..a8d5756d 100644 --- a/mongo/src/test/java/com/turkraft/springfilter/helper/TransformerUtilsImplTest.java +++ b/mongo/src/test/java/com/turkraft/springfilter/transformer/TransformerUtilsImplTest.java @@ -1,14 +1,13 @@ -package com.turkraft.springfilter.helper; +package com.turkraft.springfilter.transformer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.POJONode; import com.turkraft.springfilter.TestEntity; import com.turkraft.springfilter.builder.FilterBuilder; +import com.turkraft.springfilter.helper.FieldTypeResolver; import com.turkraft.springfilter.parser.node.InfixOperationNode; -import com.turkraft.springfilter.transformer.FilterJsonNodeTransformer; import com.turkraft.springfilter.transformer.processor.factory.FilterNodeProcessorFactories; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -67,7 +66,7 @@ void simplifyShouldReturnNodeWhenNoAndOrOrPresent() { } @Test - void simplifyShouldFuseAndElementsWhenAndPresent() { + void simplifyShouldMergeAndElementsWhenAndPresent() { // {$and: [ { $and: [ {}, {} ] }, {} , {}]} // => // {$and: [ {}, {}, {}, {}]} @@ -94,7 +93,7 @@ void simplifyShouldFuseAndElementsWhenAndPresent() { } @Test - void simplifyShouldFuseOrElementsWhenOrPresent() { + void simplifyShouldMergeOrElementsWhenOrPresent() { // {$or: [ { $or: [ {}, {} ] }, {} , {}]} // => // {$or: [ {}, {}, {}, {}]} @@ -121,7 +120,7 @@ void simplifyShouldFuseOrElementsWhenOrPresent() { } @Test - void simplifyShouldFuseAndAndOrElementsWhenAndAndOrPresent() { + void simplifyShouldMergeAndAndOrElementsWhenAndAndOrPresent() { // {$and: [ { $and: [ { $or: [ {}, {} ] }, {} ] }, {} , {}]} // => // {$and: [ { $or: [ {}, {} ] }, {} , {}, {}]} @@ -159,7 +158,7 @@ void simplifyShouldFuseAndAndOrElementsWhenAndAndOrPresent() { } @Test - void simplifyShouldNotFuseAnyElemenTrueWithDifferentMapping() { + void simplifyShouldNotMergeAnyElemenTrueWithDifferentMapping() { // {$and: [ // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping1", [] ] }, as: "this", in: filter } }, // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping2", [] ] }, as: "this", in: filter } }, @@ -202,7 +201,7 @@ void simplifyShouldNotFuseAnyElemenTrueWithDifferentMapping() { } @Test - void simplifyShouldFuseAnyElemenTrueWithSameMapping() { + void simplifyShouldMergeAnyElemenTrueWithSameMapping() { // {$and: [ // {$anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }}, // {$anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter } }}, @@ -246,7 +245,7 @@ void simplifyShouldFuseAnyElemenTrueWithSameMapping() { } @Test - void simplifyShouldFuseAnyElemenTrueWithSameMappingAndRemoveSingleElementAndOrOrs() { + void simplifyShouldMergeAnyElemenTrueWithSameMappingAndRemoveSingleElementAndOrOrs() { // {$and: [ // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter1 } }, // $anyElementTrue: { $map: { input: { $ifNull: [ "$mapping", [] ] }, as: "this", in: filter2 } }, diff --git a/pom.xml b/pom.xml index b4fa6d92..cc483e74 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ ${java.version} ${java.version} ${springfilter.version} - 3.0.0-SNAPSHOT + 3.1.7-collection 3.3.0 3.2.5 3.3.1