From feeb924ea9416c2486fcf30aca60c7e834c449cf Mon Sep 17 00:00:00 2001
From: Hicham Barhoumi <hicham.barhoumi@archiveco.fr>
Date: Thu, 13 Aug 2020 18:11:17 +0200
Subject: [PATCH] [FENIX-66] add elemMatch support for criteria

---
 .../commons/api/utils/CriteriaUtils.java      | 22 ++++++++++++----
 .../commons/mongo/utils/MongoUtils.java       | 26 +++++++++++++------
 .../commons/mongo/utils/MongoUtilsTest.java   | 24 +++++++++++++++++
 3 files changed, 59 insertions(+), 13 deletions(-)

diff --git a/commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/utils/CriteriaUtils.java b/commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/utils/CriteriaUtils.java
index ba76a41b..310fc23b 100644
--- a/commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/utils/CriteriaUtils.java
+++ b/commons/commons-api/src/main/java/fr/gouv/vitamui/commons/api/utils/CriteriaUtils.java
@@ -122,16 +122,28 @@ public final class CriteriaUtils {
 
     /**
      * Check if criteria contains only allowed keys
-     * @param criteriaDto
+     * @param queryDto
      * @param allowedKeys
      */
-    public static void checkContainsAuthorizedKeys(final QueryDto criteriaDto, final Collection<String> allowedKeys) {
-        criteriaDto.getCriterionList().forEach(criterion -> {
-            if (!allowedKeys.contains(criterion.getKey())) {
+    public static void checkContainsAuthorizedKeys(final QueryDto queryDto, final Collection<String> allowedKeys) {
+        queryDto.getCriterionList().forEach(criterion -> {
+            // if we have a ElemMatch operator we have to check that current field is allowed and his child field also
+            // field.childField
+            if (criterion.getOperator().equals(CriterionOperator.ELEMMATCH) &&
+                allowedKeys.stream().anyMatch(key -> key.startsWith(criterion.getKey() + "."))) {
+                // we recurse on children to check the allowed key
+                try {
+                    QueryDto elemMatchQuery = QueryDto.fromJson(JsonUtils.toJson(criterion.getValue()));
+                    checkContainsAuthorizedKeys(elemMatchQuery, allowedKeys);
+                }
+                catch (JsonProcessingException e) {
+                    throw new InvalidFormatException(e.getMessage(), e);
+                }
+            } else if (!allowedKeys.contains(criterion.getKey())) {
                 throw new ForbiddenException("Criterion with key : " + criterion.getKey() + " is not allowed");
             }
         });
-        criteriaDto.getSubQueries().forEach(queryDto -> checkContainsAuthorizedKeys(queryDto, allowedKeys));
+        queryDto.getSubQueries().forEach(queryDtoItem -> checkContainsAuthorizedKeys(queryDtoItem, allowedKeys));
     }
 
     public static QueryDto fromJson(final String criteriaJson) {
diff --git a/commons/commons-mongo/src/main/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtils.java b/commons/commons-mongo/src/main/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtils.java
index 740963ba..2186ecef 100644
--- a/commons/commons-mongo/src/main/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtils.java
+++ b/commons/commons-mongo/src/main/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtils.java
@@ -48,7 +48,7 @@ import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import fr.gouv.vitamui.commons.api.domain.QueryDto;
-import fr.gouv.vitamui.commons.api.domain.QueryOperator;
+import fr.gouv.vitamui.commons.api.exception.NotImplementedException;
 import fr.gouv.vitamui.commons.utils.JsonUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.data.mongodb.core.query.Criteria;
@@ -290,7 +290,7 @@ public final class MongoUtils {
                 criteria = buildAndOperator(startCriteria, endCriteria);
                 break;
             case ELEMMATCH :
-                criteria = Criteria.where(key).elemMatch(queryDTOToCriterion((QueryDto)val));
+                criteria = Criteria.where(key).elemMatch(queryDtoToCriterion((QueryDto)val));
                 break;
             default :
                 throw new IllegalArgumentException("Operator " + operator + " is not supported");
@@ -298,7 +298,7 @@ public final class MongoUtils {
         return criteria;
     }
 
-    public static Criteria queryDTOToCriterion(QueryDto queryDto) {
+    public static Criteria queryDtoToCriterion(QueryDto queryDto) {
         Collection<CriteriaDefinition> criteria = new ArrayList<>();
         queryDto.getCriterionList().forEach(criterion -> {
             criteria.add(MongoUtils.getCriteria(criterion.getKey(), criterion.getValue(), criterion.getOperator()));
@@ -306,15 +306,25 @@ public final class MongoUtils {
 
         // if the criteria contains subQueries, a recursive call is made for each subQuery
         queryDto.getSubQueries().forEach(queryDtoItem -> {
-            criteria.add(queryDTOToCriterion(queryDtoItem));
+            criteria.add(queryDtoToCriterion(queryDtoItem));
         });
 
         final Criteria commonCustomCriteria = new Criteria();
-        if (queryDto.getQueryOperator() == QueryOperator.AND) {
-            commonCustomCriteria.andOperator(criteria.stream().map(c -> (Criteria) c).toArray(Criteria[]::new));
-        } else if (queryDto.getQueryOperator() == QueryOperator.OR) {
-            commonCustomCriteria.orOperator(criteria.stream().map(c -> (Criteria) c).toArray(Criteria[]::new));
+        Criteria[] criteriaList = criteria.stream().map(c -> (Criteria) c).toArray(Criteria[]::new);
+        switch (queryDto.getQueryOperator()){
+            case AND:
+                commonCustomCriteria.andOperator(criteriaList);
+                break;
+            case OR:
+                commonCustomCriteria.orOperator(criteriaList);
+                break;
+            case NOR:
+                commonCustomCriteria.norOperator(criteriaList);
+                break;
+            default:
+                throw new NotImplementedException("Method is not implemented => " + queryDto.getQueryOperator().name());
         }
+
         return commonCustomCriteria;
     }
 
diff --git a/commons/commons-mongo/src/test/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtilsTest.java b/commons/commons-mongo/src/test/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtilsTest.java
index b314a111..b9a9f2d5 100644
--- a/commons/commons-mongo/src/test/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtilsTest.java
+++ b/commons/commons-mongo/src/test/java/fr/gouv/vitamui/commons/mongo/utils/MongoUtilsTest.java
@@ -10,6 +10,10 @@ import java.util.List;
 import java.util.Optional;
 import java.util.regex.Pattern;
 
+import com.mongodb.BasicDBList;
+import fr.gouv.vitamui.commons.api.domain.QueryDto;
+import org.bson.Document;
+
 import org.junit.Test;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.CriteriaDefinition;
@@ -153,4 +157,24 @@ public class MongoUtilsTest {
 
     }
 
+
+    @Test
+    public void getCriteria_with_elemMatch() {
+        QueryDto queryDto = QueryDto.criteria("country", "France", CriterionOperator.EQUALS);
+        fr.gouv.vitamui.commons.api.domain.Criterion criterion =
+            new fr.gouv.vitamui.commons.api.domain.Criterion("addressList", queryDto, CriterionOperator.ELEMMATCH);
+        Criteria criteria = MongoUtils.getCriteriaDefinitionFromEntityClass(criterion, Person.class);
+
+        assertThat(criteria.getKey()).isEqualTo("addressList");
+
+        Document addressCriteria = (Document)criteria.getCriteriaObject().get("addressList");
+        assertThat(addressCriteria).isNotNull();
+
+        Document elemMatch = (Document)addressCriteria.get("$elemMatch");
+        assertThat(elemMatch).isNotNull();
+
+        Document elemMatchInternalQuery = (Document)((BasicDBList)elemMatch.get("$and")).get(0);
+        assertThat(elemMatchInternalQuery.getString("country")).isEqualTo("France");
+    }
+
 }
-- 
GitLab