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..abc2d60f 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.transformer.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..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,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.transformer.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/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/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..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,68 +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(); } - } - - 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/JsonNodeHelperImpl.java b/mongo/src/main/java/com/turkraft/springfilter/helper/JsonNodeHelperImpl.java index 49448270..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,19 +6,22 @@ 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 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 +31,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 +45,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/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/TransformerUtils.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtils.java new file mode 100644 index 00000000..e7391d7e --- /dev/null +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtils.java @@ -0,0 +1,14 @@ +package com.turkraft.springfilter.transformer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.turkraft.springfilter.parser.node.InfixOperationNode; + +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); + + JsonNode simplify(FilterJsonNodeTransformer transformer, JsonNode node); +} diff --git a/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtilsImpl.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtilsImpl.java new file mode 100644 index 00000000..830ea598 --- /dev/null +++ b/mongo/src/main/java/com/turkraft/springfilter/transformer/TransformerUtilsImpl.java @@ -0,0 +1,322 @@ +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 org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +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 { + resultNode = getMapNode(transformer, getIfNullNode(transformer, input), 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 = new ArrayList<>(List.of(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 { + + resultNode = getMapNode(transformer, getIfNullNode(transformer, input), resultNode); + } + + } + return resultNode; + } + + 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", + mapNode)); + } + + 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 { + + resultNode = getMapNode(transformer, getIfNullNode(transformer, input), resultNode); + } + + } + return resultNode; + } + + private static FilterNode getFilterNode(FilterJsonNodeTransformer transformer, InfixOperationNode source, List arrayPaths, int i) { + List split = new ArrayList<>(List.of(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); + + 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", + mapNode)); + 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())); + } + + @Override + public JsonNode simplify(FilterJsonNodeTransformer transformer, JsonNode node) { + JsonNode result = node; + if (node.has("$and") && node.get("$and").isArray()) { + result = simplifyAnd(transformer, node); + 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); + mergeAnyElements(transformer, result, "$or"); + if(result.has("$or") && result.get("$or").size() == 1){ + result = result.get("$or").get(0); + } + } + return result; + } + + private static void mergeAnyElements(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())) { + mergeExistingAndNew(transformer, map, ifNullArray, mapNode, currentOperator, toRemove, i); + } else { + map.put(ifNullArray.asText(), anyElementChild); + } + } + } + } + + toRemove.forEach(arrayNode::remove); + } + + 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"); + 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/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java b/mongo/src/main/java/com/turkraft/springfilter/transformer/processor/InOperationJsonNodeProcessor.java index 38ee548b..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,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.transformer.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..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,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.transformer.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..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,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.transformer.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..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,9 +2,11 @@ com.turkraft.springfilter.boot.FilterJsonNodeArgumentResolverConfigurer com.turkraft.springfilter.helper.JsonNodeHelperImpl com.turkraft.springfilter.helper.FieldTypeResolverImpl +com.turkraft.springfilter.transformer.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/transformer/TransformerUtilsImplTest.java b/mongo/src/test/java/com/turkraft/springfilter/transformer/TransformerUtilsImplTest.java new file mode 100644 index 00000000..a8d5756d --- /dev/null +++ b/mongo/src/test/java/com/turkraft/springfilter/transformer/TransformerUtilsImplTest.java @@ -0,0 +1,311 @@ +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.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.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 simplifyShouldMergeAndElementsWhenAndPresent() { + // {$and: [ { $and: [ {}, {} ] }, {} , {}]} + // => + // {$and: [ {}, {}, {}, {}]} + //arrange + 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 simplifyShouldMergeOrElementsWhenOrPresent() { + // {$or: [ { $or: [ {}, {} ] }, {} , {}]} + // => + // {$or: [ {}, {}, {}, {}]} + //arrange + 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 simplifyShouldMergeAndAndOrElementsWhenAndAndOrPresent() { + // {$and: [ { $and: [ { $or: [ {}, {} ] }, {} ] }, {} , {}]} + // => + // {$and: [ { $or: [ {}, {} ] }, {} , {}, {}]} + //arrange + 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 simplifyShouldNotMergeAnyElemenTrueWithDifferentMapping() { + // {$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(); + 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 simplifyShouldMergeAnyElemenTrueWithSameMapping() { + // {$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(); + 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); + } + + @Test + void simplifyShouldMergeAnyElemenTrueWithSameMappingAndRemoveSingleElementAndOrOrs() { + // {$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 + // } + //} + ObjectNode result = objectMapper.createObjectNode(); + ObjectNode anyElementTrue = objectMapper.createObjectNode(); + ObjectNode map = 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.put("as", "this"); + map.set("in", filter); + anyElementTrue.set("$map", map); + return result.set("$anyElementTrue", anyElementTrue); + } + + @Configuration + @ComponentScan("com.turkraft.springfilter") + static class Config { + + } +} 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