This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 102b8db50d2a08a15e713291590e492e41b34b48
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Thu Mar 7 11:50:01 2019 +0700

    JAMES-2663 QueryDTO with deserializer and translator
---
 .../java/org/apache/james/vault/search/Query.java  |   4 +
 .../webadmin-mailbox-deleted-message-vault/pom.xml |  10 +
 .../webadmin/vault/routes/query/CriterionDTO.java  |  88 ++++++++
 .../webadmin/vault/routes/query/QueryDTO.java      |  71 +++++++
 .../webadmin/vault/routes/query/QueryElement.java  |  31 +--
 .../routes/query/QueryElementDeserializer.java     |  51 +++++
 .../vault/routes/query/QueryTranslator.java        | 225 +++++++++++++++++++++
 .../routes/query/QueryElementDeserializerTest.java |  89 ++++++++
 .../vault/routes/query/QueryElementTest.java       |  36 ++--
 .../vault/routes/query/QueryTranslatorTest.java    |  67 ++++++
 10 files changed, 622 insertions(+), 50 deletions(-)

diff --git 
a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
 
b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
index 039fd52..8595a43 100644
--- 
a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
+++ 
b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
@@ -30,6 +30,10 @@ public class Query {
     public static final Query ALL = new Query(ImmutableList.of());
     private static final Predicate<DeletedMessage> MATCH_ALL = any -> true;
 
+    public static Query of(List<Criterion> criteria) {
+        return new Query(criteria);
+    }
+
     public static Query of(Criterion... criteria) {
         return new Query(ImmutableList.copyOf(criteria));
     }
diff --git 
a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/pom.xml 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/pom.xml
index aa6847d..2c06be8 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/pom.xml
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/pom.xml
@@ -105,6 +105,16 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-assertj</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>nl.jqno.equalsverifier</groupId>
+            <artifactId>equalsverifier</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
             <scope>test</scope>
diff --git 
a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/CriterionDTO.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/CriterionDTO.java
new file mode 100644
index 0000000..547d3c0
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/CriterionDTO.java
@@ -0,0 +1,88 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.vault.routes.query;
+
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+
+@JsonDeserialize(as = CriterionDTO.class)
+public class CriterionDTO implements QueryElement {
+
+    @VisibleForTesting
+    static CriterionDTO from(QueryTranslator.FieldName fieldName, 
QueryTranslator.Operator operator, String value) {
+        return new CriterionDTO(fieldName.getValue(), operator.getValue(), 
value);
+    }
+
+    private final String fieldName;
+    private final String operator;
+    private final String value;
+
+    @JsonCreator
+    public CriterionDTO(@JsonProperty("fieldName") String fieldName,
+                        @JsonProperty("operator") String operator,
+                        @JsonProperty("value") String value) {
+        this.fieldName = fieldName;
+        this.operator = operator;
+        this.value = value;
+    }
+
+    public String getFieldName() {
+        return fieldName;
+    }
+
+    public String getOperator() {
+        return operator;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof CriterionDTO) {
+            CriterionDTO that = (CriterionDTO) o;
+
+            return Objects.equals(this.fieldName, that.getFieldName())
+                && Objects.equals(this.operator, that.getOperator())
+                && Objects.equals(this.value, that.getValue());
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(fieldName, operator, value);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("fieldName", fieldName)
+            .add("operator", operator)
+            .add("value", value)
+            .toString();
+    }
+}
\ No newline at end of file
diff --git 
a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryDTO.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryDTO.java
new file mode 100644
index 0000000..496811b
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryDTO.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.vault.routes.query;
+
+import java.util.List;
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+
+@JsonDeserialize(as = QueryDTO.class)
+public class QueryDTO implements QueryElement {
+
+    @VisibleForTesting
+    static QueryDTO and(QueryElement... queryElements) {
+        return new QueryDTO(QueryTranslator.Combinator.AND.getValue(), 
ImmutableList.copyOf(queryElements));
+    }
+
+    private final String combinator;
+    private final List<QueryElement> criteria;
+
+    @JsonCreator
+    public QueryDTO(@JsonProperty("combinator") String combinator, 
@JsonProperty("criteria") List<QueryElement> criteria) {
+        this.combinator = combinator;
+        this.criteria = criteria;
+    }
+
+    public String getCombinator() {
+        return combinator;
+    }
+
+    public List<QueryElement> getCriteria() {
+        return criteria;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof QueryDTO) {
+            QueryDTO queryDTO = (QueryDTO) o;
+
+            return Objects.equals(this.combinator, queryDTO.getCombinator())
+                && Objects.equals(this.criteria, queryDTO.getCriteria());
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(combinator, criteria);
+    }
+}
diff --git 
a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElement.java
similarity index 59%
copy from 
mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
copy to 
server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElement.java
index 039fd52..2c0e9e1 100644
--- 
a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElement.java
@@ -17,33 +17,10 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.vault.search;
+package org.apache.james.webadmin.vault.routes.query;
 
-import java.util.List;
-import java.util.function.Predicate;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 
-import org.apache.james.vault.DeletedMessage;
-
-import com.google.common.collect.ImmutableList;
-
-public class Query {
-    public static final Query ALL = new Query(ImmutableList.of());
-    private static final Predicate<DeletedMessage> MATCH_ALL = any -> true;
-
-    public static Query of(Criterion... criteria) {
-        return new Query(ImmutableList.copyOf(criteria));
-    }
-
-    private final List<Criterion> criteria;
-
-    private Query(List<Criterion> criteria) {
-        this.criteria = criteria;
-    }
-
-    public Predicate<DeletedMessage> toPredicate() {
-        return criteria.stream()
-            .map(Criterion::toPredicate)
-            .reduce(Predicate::and)
-            .orElse(MATCH_ALL);
-    }
+@JsonDeserialize(using = QueryElementDeserializer.class)
+interface QueryElement {
 }
diff --git 
a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElementDeserializer.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElementDeserializer.java
new file mode 100644
index 0000000..ad608bb
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryElementDeserializer.java
@@ -0,0 +1,51 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.vault.routes.query;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+class QueryElementDeserializer extends StdDeserializer<QueryElement> {
+    protected QueryElementDeserializer() {
+        super(QueryElement.class);
+    }
+
+    @Override
+    public QueryElement deserialize(JsonParser jsonParser, 
DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
+        ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
+        ObjectNode queryElement = mapper.readTree(jsonParser);
+
+        if (isComposedElement(queryElement)) {
+            return mapper.treeToValue(queryElement, QueryDTO.class);
+        }
+        return mapper.treeToValue(queryElement, CriterionDTO.class);
+    }
+
+    private boolean isComposedElement(ObjectNode queryElement) {
+        return queryElement.findValue("combinator") != null
+            || queryElement.findValue("criteria") != null;
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryTranslator.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryTranslator.java
new file mode 100644
index 0000000..33067d6
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/query/QueryTranslator.java
@@ -0,0 +1,225 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.vault.routes.query;
+
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName.DELETION_DATE;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName.DELIVERY_DATE;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName.HAS_ATTACHMENT;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName.ORIGIN_MAILBOXES;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName.RECIPIENTS;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName.SENDER;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName.SUBJECT;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldValueParser.BOOLEAN_PARSER;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldValueParser.MAIL_ADDRESS_PARSER;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldValueParser.STRING_PARSER;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldValueParser.ZONED_DATE_TIME_PARSER;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator.AFTER_OR_EQUALS;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator.BEFORE_OR_EQUALS;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator.CONTAINS;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator.CONTAINS_IGNORE_CASE;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator.EQUALS;
+import static 
org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator.EQUALS_IGNORE_CASE;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.mail.internet.AddressException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.vault.search.Criterion;
+import org.apache.james.vault.search.CriterionFactory;
+import org.apache.james.vault.search.Query;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableTable;
+
+public class QueryTranslator {
+
+    public static class QueryTranslatorException extends RuntimeException {
+        QueryTranslatorException(String message) {
+            super(message);
+        }
+    }
+
+    enum Combinator {
+        AND("and");
+
+        private final String value;
+
+        Combinator(String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+
+    enum FieldName {
+        DELETION_DATE("deletionDate"),
+        DELIVERY_DATE("deliveryDate"),
+        RECIPIENTS("recipients"),
+        SENDER("sender"),
+        HAS_ATTACHMENT("hasAttachment"),
+        ORIGIN_MAILBOXES("originMailboxes"),
+        SUBJECT("subject");
+
+        static FieldName getField(String fieldNameString) throws 
QueryTranslatorException {
+            return Stream.of(values())
+                .filter(fieldName -> fieldName.value.equals(fieldNameString))
+                .findFirst()
+                .orElseThrow(() -> new QueryTranslatorException("fieldName: '" 
+ fieldNameString + "' is not supported"));
+        }
+
+        private final String value;
+
+        FieldName(String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+
+    enum Operator {
+        EQUALS("equals"),
+        EQUALS_IGNORE_CASE("equalsIgnoreCase"),
+        CONTAINS("contains"),
+        CONTAINS_IGNORE_CASE("containsIgnoreCase"),
+        BEFORE_OR_EQUALS("beforeOrEquals"),
+        AFTER_OR_EQUALS("afterOrEquals");
+
+        static Operator getOperator(String operator) throws 
QueryTranslatorException {
+            return Stream.of(values())
+                .filter(operatorString -> 
operatorString.value.equals(operator))
+                .findFirst()
+                .orElseThrow(() -> new QueryTranslatorException("operator: '" 
+ operator + "' is not supported"));
+        }
+
+        private final String value;
+
+        Operator(String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+
+    interface FieldValueParser<T> {
+
+        class MailboxIdValueParser implements FieldValueParser<MailboxId> {
+
+            final MailboxId.Factory mailboxIdFactory;
+
+            MailboxIdValueParser(MailboxId.Factory mailboxIdFactory) {
+                this.mailboxIdFactory = mailboxIdFactory;
+            }
+
+            @Override
+            public MailboxId parse(String mailboxIdString) {
+                return mailboxIdFactory.fromString(mailboxIdString);
+            }
+        }
+
+        FieldValueParser<ZonedDateTime> ZONED_DATE_TIME_PARSER = 
ZonedDateTime::parse;
+        FieldValueParser<String> STRING_PARSER = input -> input;
+        FieldValueParser<Boolean> BOOLEAN_PARSER = Boolean::valueOf;
+        FieldValueParser<MailAddress> MAIL_ADDRESS_PARSER = 
FieldValueParser::parseMailAddress;
+
+        static MailAddress parseMailAddress(String mailAddressString) throws 
QueryTranslatorException {
+            try {
+                return new MailAddress(mailAddressString);
+            } catch (AddressException e) {
+                throw new QueryTranslatorException("mailAddress(" + 
mailAddressString + ") parsing got error: " + e.getMessage());
+            }
+        }
+
+        T parse(String input);
+    }
+
+    private final ImmutableTable<FieldName, Operator, Function<String, 
Criterion>> criterionRegistry;
+
+    @Inject
+    public QueryTranslator(MailboxId.Factory mailboxIdFactory) {
+        criterionRegistry = withMailboxIdCriterionParser(mailboxIdFactory);
+    }
+
+    private ImmutableTable<FieldName, Operator, Function<String, Criterion>> 
withMailboxIdCriterionParser(MailboxId.Factory mailboxIdFactor) {
+        FieldValueParser.MailboxIdValueParser mailboxIdParser = new 
FieldValueParser.MailboxIdValueParser(mailboxIdFactor);
+
+        return defaultRegistryBuilder()
+            .put(ORIGIN_MAILBOXES, CONTAINS, testedValue -> 
CriterionFactory.containsOriginMailbox(mailboxIdParser.parse(testedValue)))
+            .build();
+    }
+
+    private ImmutableTable.Builder<FieldName, Operator, Function<String, 
Criterion>> defaultRegistryBuilder() {
+        return ImmutableTable.<FieldName, Operator, Function<String, 
Criterion>>builder()
+            .put(DELETION_DATE, BEFORE_OR_EQUALS, testedValue -> 
CriterionFactory.deletionDate().beforeOrEquals(ZONED_DATE_TIME_PARSER.parse(testedValue)))
+            .put(DELETION_DATE, AFTER_OR_EQUALS, testedValue -> 
CriterionFactory.deletionDate().afterOrEquals(ZONED_DATE_TIME_PARSER.parse(testedValue)))
+            .put(DELIVERY_DATE, BEFORE_OR_EQUALS, testedValue -> 
CriterionFactory.deliveryDate().beforeOrEquals(ZONED_DATE_TIME_PARSER.parse(testedValue)))
+            .put(DELIVERY_DATE, AFTER_OR_EQUALS, testedValue -> 
CriterionFactory.deliveryDate().afterOrEquals(ZONED_DATE_TIME_PARSER.parse(testedValue)))
+            .put(RECIPIENTS, CONTAINS, testedValue -> 
CriterionFactory.containsRecipient(MAIL_ADDRESS_PARSER.parse(testedValue)))
+            .put(SENDER, EQUALS, testedValue -> 
CriterionFactory.hasSender(MAIL_ADDRESS_PARSER.parse(testedValue)))
+            .put(HAS_ATTACHMENT, EQUALS, testedValue -> 
CriterionFactory.hasAttachment(BOOLEAN_PARSER.parse(testedValue)))
+            .put(SUBJECT, EQUALS, testedValue -> 
CriterionFactory.subject().equals(STRING_PARSER.parse(testedValue)))
+            .put(SUBJECT, EQUALS_IGNORE_CASE, testedValue -> 
CriterionFactory.subject().equalsIgnoreCase(STRING_PARSER.parse(testedValue)))
+            .put(SUBJECT, CONTAINS, testedValue -> 
CriterionFactory.subject().contains(STRING_PARSER.parse(testedValue)))
+            .put(SUBJECT, CONTAINS_IGNORE_CASE, testedValue -> 
CriterionFactory.subject().containsIgnoreCase(STRING_PARSER.parse(testedValue)));
+    }
+
+    private Criterion translate(CriterionDTO dto) throws 
QueryTranslatorException {
+        return Optional.ofNullable(getCriterionParser(dto))
+            .map(criterionGeneratingFunction -> 
criterionGeneratingFunction.apply(dto.getValue()))
+            .orElseThrow(() -> new QueryTranslatorException("pair of 
fieldName: '" + dto.getFieldName() + "' and operator: '" + dto.getOperator() + 
"' is not supported"));
+    }
+
+    private Function<String, Criterion> getCriterionParser(CriterionDTO dto) {
+        return criterionRegistry.get(
+            FieldName.getField(dto.getFieldName()),
+            Operator.getOperator(dto.getOperator()));
+    }
+
+    public Query translate(QueryDTO queryDTO) throws QueryTranslatorException {
+        Preconditions.checkArgument(isAndCombinator(queryDTO.getCombinator()), 
"combinator '" + queryDTO.getCombinator() + "' is not yet handled");
+        
Preconditions.checkArgument(queryDTO.getCriteria().stream().allMatch(this::isCriterion),
 "nested query structure is not yet handled");
+
+        return Query.of(queryDTO.getCriteria().stream()
+            .map(queryElement -> (CriterionDTO) queryElement)
+            .map(Throwing.function(this::translate))
+            .collect(Guavate.toImmutableList()));
+    }
+
+    private boolean isAndCombinator(String combinator) {
+        return Combinator.AND.getValue().equals(combinator);
+    }
+
+    private boolean isCriterion(QueryElement queryElement) {
+        return queryElement instanceof CriterionDTO;
+    }
+}
diff --git 
a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryElementDeserializerTest.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryElementDeserializerTest.java
new file mode 100644
index 0000000..d338c9b
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryElementDeserializerTest.java
@@ -0,0 +1,89 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.vault.routes.query;
+
+import static org.apache.james.vault.DeletedMessageFixture.SUBJECT;
+import static org.apache.mailet.base.MailAddressFixture.SENDER;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName;
+import org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+class QueryElementDeserializerTest {
+
+    private ObjectMapper objectMapper;
+
+    @BeforeEach
+    void beforeEach() {
+        objectMapper = new ObjectMapper();
+    }
+
+    @Test
+    void shouldDeserializeNestedStructure() throws Exception {
+        String queryJson = 
+            "{  " +
+            "  \"combinator\": \"and\",  " +
+            "  \"criteria\": [  " +
+            "    {  " +
+            "      \"combinator\": \"and\",  " +
+            "      \"criteria\": [  " +
+            "        {\"fieldName\": \"subject\", \"operator\": \"contains\", 
\"value\": \"" + SUBJECT + "\"}," +
+            "        {\"fieldName\": \"sender\", \"operator\": \"equals\", 
\"value\": \"" + SENDER.asString() + "\"}" +
+            "      ]  " +
+            "    },  " +
+            "    {\"fieldName\": \"hasAttachment\", \"operator\": \"equals\", 
\"value\": \"true\"}" +
+            "  ]  " +
+            "}  ";
+
+        QueryDTO queryDTO = objectMapper.readValue(queryJson, QueryDTO.class);
+        assertThat(queryDTO)
+            .isEqualTo(QueryDTO.and(
+                QueryDTO.and(
+                    CriterionDTO.from(FieldName.SUBJECT, Operator.CONTAINS, 
SUBJECT),
+                    CriterionDTO.from(FieldName.SENDER, Operator.EQUALS, 
SENDER.asString())),
+                CriterionDTO.from(FieldName.HAS_ATTACHMENT, Operator.EQUALS, 
"true")
+            ));
+    }
+
+    @Test
+    void shouldDeserializeFlattenStructure() throws Exception {
+        String queryJson =
+            "{  " +
+            "  \"combinator\": \"and\",  " +
+            "  \"criteria\": [  " +
+            "    {\"fieldName\": \"subject\", \"operator\": \"contains\", 
\"value\": \"" + SUBJECT + "\"}," +
+            "    {\"fieldName\": \"sender\", \"operator\": \"equals\", 
\"value\": \"" + SENDER.asString() + "\"}," +
+            "    {\"fieldName\": \"hasAttachment\", \"operator\": \"equals\", 
\"value\": \"true\"}" +
+            "  ]  " +
+            "}  ";
+
+        QueryDTO queryDTO = objectMapper.readValue(queryJson, QueryDTO.class);
+        assertThat(queryDTO)
+            .isEqualTo(QueryDTO.and(
+                CriterionDTO.from(FieldName.SUBJECT, Operator.CONTAINS, 
SUBJECT),
+                CriterionDTO.from(FieldName.SENDER, Operator.EQUALS, 
SENDER.asString()),
+                CriterionDTO.from(FieldName.HAS_ATTACHMENT, Operator.EQUALS, 
"true")
+            ));
+    }
+}
\ No newline at end of file
diff --git 
a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryElementTest.java
similarity index 60%
copy from 
mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
copy to 
server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryElementTest.java
index 039fd52..6478a4e 100644
--- 
a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/search/Query.java
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryElementTest.java
@@ -17,33 +17,23 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.vault.search;
+package org.apache.james.webadmin.vault.routes.query;
 
-import java.util.List;
-import java.util.function.Predicate;
+import org.junit.jupiter.api.Test;
 
-import org.apache.james.vault.DeletedMessage;
+import nl.jqno.equalsverifier.EqualsVerifier;
 
-import com.google.common.collect.ImmutableList;
+class QueryElementTest {
 
-public class Query {
-    public static final Query ALL = new Query(ImmutableList.of());
-    private static final Predicate<DeletedMessage> MATCH_ALL = any -> true;
-
-    public static Query of(Criterion... criteria) {
-        return new Query(ImmutableList.copyOf(criteria));
-    }
-
-    private final List<Criterion> criteria;
-
-    private Query(List<Criterion> criteria) {
-        this.criteria = criteria;
+    @Test
+    void queryDTOShouldMatchBeanContract() {
+        EqualsVerifier.forClass(QueryDTO.class)
+            .verify();
     }
 
-    public Predicate<DeletedMessage> toPredicate() {
-        return criteria.stream()
-            .map(Criterion::toPredicate)
-            .reduce(Predicate::and)
-            .orElse(MATCH_ALL);
+    @Test
+    void criterionDTOShouldMatchBeanContract() {
+        EqualsVerifier.forClass(CriterionDTO.class)
+            .verify();
     }
-}
+}
\ No newline at end of file
diff --git 
a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryTranslatorTest.java
 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryTranslatorTest.java
new file mode 100644
index 0000000..6689781
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/query/QueryTranslatorTest.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.vault.routes.query;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.james.mailbox.inmemory.InMemoryId;
+import org.apache.james.webadmin.vault.routes.query.QueryTranslator.FieldName;
+import org.apache.james.webadmin.vault.routes.query.QueryTranslator.Operator;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+class QueryTranslatorTest {
+
+    private QueryTranslator queryTranslator;
+
+    @BeforeEach
+    void beforeEach() {
+        queryTranslator = new QueryTranslator(new InMemoryId.Factory());
+    }
+
+    @Test
+    void translateShouldThrowWhenPassingNotAndOperator() {
+        assertThatThrownBy(() -> queryTranslator.translate(new QueryDTO("or", 
ImmutableList.of())))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("combinator 'or' is not yet handled");
+    }
+
+    @Test
+    void translateShouldThrowWhenPassingNestedQuery() {
+        assertThatThrownBy(() -> queryTranslator.translate(QueryDTO.and(
+                QueryDTO.and(new CriterionDTO(FieldName.SUBJECT.getValue(), 
Operator.CONTAINS.getValue(), "james"))
+            )))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("nested query structure is not yet handled");
+    }
+
+    @Test
+    void translateShouldNotThrowWhenPassingFlattenQuery() {
+        assertThatCode(() -> queryTranslator.translate(QueryDTO.and(
+                new CriterionDTO(FieldName.SUBJECT.getValue(), 
Operator.CONTAINS.getValue(), "james"),
+                new CriterionDTO(FieldName.SENDER.getValue(), 
Operator.EQUALS.getValue(), "u...@james.org"),
+                new CriterionDTO(FieldName.HAS_ATTACHMENT.getValue(), 
Operator.EQUALS.getValue(), "true")
+            )))
+            .doesNotThrowAnyException();
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to