diff --git a/source/java/org/alfresco/rest/workflow/api/impl/MapBasedQueryWalker.java b/source/java/org/alfresco/rest/workflow/api/impl/MapBasedQueryWalker.java
index 033a47ba77..ff57b10654 100644
--- a/source/java/org/alfresco/rest/workflow/api/impl/MapBasedQueryWalker.java
+++ b/source/java/org/alfresco/rest/workflow/api/impl/MapBasedQueryWalker.java
@@ -261,8 +261,8 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
}
else if (negated)
{
- // Throw error for the unsupported NOT operator only if the property was valid for comparison, show the more meaningful error first.
- throw new InvalidArgumentException("NOT operator is not supported for " + WhereClauseParser.tokenNames[type] + " comparison.");
+ // Throw error for the unsupported negation only if the property was valid for comparison, show the more meaningful error first.
+ throw new InvalidArgumentException("Cannot use NOT for " + WhereClauseParser.tokenNames[type] + " comparison.");
}
}
diff --git a/source/test-java/org/alfresco/rest/api/tests/ApiTest.java b/source/test-java/org/alfresco/rest/api/tests/ApiTest.java
index d9ee7fb745..fbdc0aeb54 100644
--- a/source/test-java/org/alfresco/rest/api/tests/ApiTest.java
+++ b/source/test-java/org/alfresco/rest/api/tests/ApiTest.java
@@ -55,7 +55,8 @@ import org.junit.runners.Suite;
ActivitiesPostingTest.class,
DeletedNodesTest.class,
AuthenticationsTest.class,
- ModulePackagesApiTest.class,
+ ModulePackagesApiTest.class,
+ WherePredicateApiTest.class,
TestSites.class,
TestNodeComments.class,
TestCMIS.class,
diff --git a/source/test-java/org/alfresco/rest/api/tests/WherePredicateApiTest.java b/source/test-java/org/alfresco/rest/api/tests/WherePredicateApiTest.java
new file mode 100644
index 0000000000..40af85d372
--- /dev/null
+++ b/source/test-java/org/alfresco/rest/api/tests/WherePredicateApiTest.java
@@ -0,0 +1,306 @@
+/*
+ * #%L
+ * Alfresco Remote API
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.rest.api.tests;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.alfresco.rest.AbstractSingleNetworkSiteTest;
+import org.alfresco.rest.api.Nodes;
+import org.alfresco.rest.api.QuickShareLinks;
+import org.alfresco.rest.api.Renditions;
+import org.alfresco.rest.api.tests.client.PublicApiClient.Paging;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class WherePredicateApiTest extends AbstractSingleNetworkSiteTest
+{
+ private static final boolean NOT = true;
+ private String folder0Id;
+ private String file0Id;
+ private Paging paging;
+
+ @Before
+ public void setup() throws Exception
+ {
+ super.setup();
+
+ setRequestContext(user1);
+
+ String myNodeId = getMyNodeId();
+
+ String folder0Name = "folder " + RUNID;
+ folder0Id = createFolder(myNodeId, folder0Name, null).getId();
+
+ String file0Name = "file " + RUNID;
+ file0Id = createEmptyTextFile(folder0Id, file0Name).getId();
+
+ paging = getPaging(0, 100);
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ /**
+ * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/nodes//children}
+ */
+ @Test
+ public void testLogicalOperatorsGetChildren() throws Exception
+ {
+ String childrenUrl = getNodeChildrenUrl(folder0Id);
+ // SIMPLE (+ve)
+ String clause = new WhereClauseBuilder()
+ .predicate(Nodes.PARAM_ISFOLDER + "=true")
+ .build();
+ getAll(childrenUrl, paging, getWhereClause(clause), 200);
+ //AND (+ve)
+ clause = new WhereClauseBuilder()
+ .predicate(Nodes.PARAM_ISFOLDER + "=true")
+ .and(Nodes.PARAM_ISFILE + "=false")
+ .build();
+ getAll(childrenUrl, paging, getWhereClause(clause), 200);
+ //NOT (-ve)
+ clause = new WhereClauseBuilder()
+ .predicate(Nodes.PARAM_ISFOLDER + "=true", NOT)
+ .build();
+ getAll(childrenUrl, paging, getWhereClause(clause), 400);
+ // OR (-ve)
+ clause = new WhereClauseBuilder()
+ .predicate(Nodes.PARAM_ISFOLDER + "=true")
+ .or(Nodes.PARAM_ISFILE + "=false")
+ .build();
+ getAll(childrenUrl, paging, getWhereClause(clause), 400);
+ // NOT + AND (-ve)
+ clause = new WhereClauseBuilder()
+ .predicate(Nodes.PARAM_ISFOLDER + "=true", NOT)
+ .and(Nodes.PARAM_ISFILE + "=false")
+ .build();
+ getAll(childrenUrl, paging, getWhereClause(clause), 400);
+ }
+
+ /**
+ * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/nodes//secondary-children}
+ */
+ @Test
+ public void testLogicalOperatorsGetSecondaryChildren() throws Exception
+ {
+ // SIMPLE (+ve)
+ String clause = new WhereClauseBuilder()
+ .predicate(Nodes.PARAM_ASSOC_TYPE + "=cm:contains")
+ .build();
+ getAll(getNodeSecondaryChildrenUrl(folder0Id), paging, getWhereClause(clause), 200);
+ // NOT (-ve)
+ clause = new WhereClauseBuilder()
+ .predicate(Nodes.PARAM_ASSOC_TYPE + "=cm:contains", NOT)
+ .build();
+ getAll(getNodeSecondaryChildrenUrl(folder0Id), paging, getWhereClause(clause), 400);
+ }
+
+ /**
+ * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/nodes//renditions}
+ */
+ @Test
+ public void testLogicalOperatorsGetRenditions() throws Exception
+ {
+ // SIMPLE (+ve)
+ String clause = new WhereClauseBuilder()
+ .predicate(Renditions.PARAM_STATUS + "=CREATED")
+ .build();
+ getAll(getNodeRenditionsUrl(file0Id), paging, getWhereClause(clause), 200);
+ // NOT (-ve)
+ clause = new WhereClauseBuilder()
+ .predicate(Renditions.PARAM_STATUS + "=CREATED", NOT)
+ .build();
+ getAll(getNodeRenditionsUrl(file0Id), paging, getWhereClause(clause), 400);
+ }
+
+ /**
+ * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/shared-links}
+ */
+ @Test
+ public void testLogicalOperatorsGetShareLinks() throws Exception
+ {
+ // SIMPLE (+ve)
+ String clause = new WhereClauseBuilder()
+ .predicate(QuickShareLinks.PARAM_SHAREDBY + "='-me-'")
+ .build();
+ getAll("shared-links", paging, getWhereClause(clause), 200);
+ // NOT (-ve)
+ clause = new WhereClauseBuilder()
+ .predicate(QuickShareLinks.PARAM_SHAREDBY + "='-me-'", NOT)
+ .build();
+ getAll("shared-links", paging, getWhereClause(clause), 400);
+ }
+
+ /**
+ * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/people//favorites}
+ */
+ @Test
+ public void testLogicalOperatorsGetFavorites() throws Exception
+ {
+ String favoritesUrl = getFavoritesUrl("-me-");
+ // SIMPLE (+ve)
+ String clause = new WhereClauseBuilder()
+ .predicate("EXISTS(target/file)")
+ .build();
+ getAll(favoritesUrl, paging, getWhereClause(clause), 200);
+ // OR (+ve)
+ clause = new WhereClauseBuilder()
+ .predicate("EXISTS(target/file)").or("EXISTS(target/folder)")
+ .build();
+ getAll(favoritesUrl, paging, getWhereClause(clause), 200);
+ // AND (-ve)
+ clause = new WhereClauseBuilder()
+ .predicate("EXISTS(target/file)").and("EXISTS(target/folder)")
+ .build();
+ getAll(favoritesUrl, paging, getWhereClause(clause), 400);
+
+ // NOT (-ve): uncomment when REPO-1249 is done
+ /*clause = new WhereClauseBuilder()
+ .predicate("EXISTS(target/file)", NOT)
+ .build();
+ getAll(favoritesUrl, paging, getWhereClause(clause), 400);*/
+ }
+
+ private Map getWhereClause(String whereparams)
+ {
+ Map params = new HashMap<>();
+ params.put("where", whereparams);
+
+ return params;
+ }
+
+ private class WhereClauseBuilder
+ {
+ private WhereClause whereClause;
+
+ public WhereClauseBuilder predicate(String predicate, boolean negated)
+ {
+ whereClause = new WhereClause(predicate, negated);
+ return this;
+ }
+ public WhereClauseBuilder predicate(String predicate)
+ {
+ return this.predicate(predicate, false);
+ }
+
+ public WhereClauseBuilder and(String predicate, boolean negated)
+ {
+ whereClause.and(new WhereClause(predicate, negated));
+ return this;
+ }
+ public WhereClauseBuilder and(String predicate)
+ {
+ return and(predicate, false);
+ }
+
+ public WhereClauseBuilder or(String predicate, boolean negated)
+ {
+ whereClause.or(new WhereClause(predicate, negated));
+ return this;
+ }
+ public WhereClauseBuilder or(String predicate)
+ {
+ return or(predicate, false);
+ }
+
+ public WhereClauseBuilder not()
+ {
+ whereClause.negate();
+ return this;
+ }
+
+ public String build()
+ {
+ whereClause.group();
+ return whereClause.toString();
+ }
+
+ private class WhereClause
+ {
+ private final String[] operators = new String[] { "MATCHES", "IN", "BETWEEN" };
+
+ private String clause;
+
+ public WhereClause(String clause, boolean negated)
+ {
+ this.clause = clause;
+ if (negated)
+ {
+ negate();
+ }
+ }
+
+ private void group()
+ {
+ clause = "(" + clause + ")";
+ }
+
+ private void and(WhereClause otherClause)
+ {
+ this.clause += " AND " + otherClause;
+ }
+
+ private void or(WhereClause otherClause)
+ {
+ this.clause += " OR " + otherClause;
+ }
+
+ public void negate()
+ {
+ for (String op : operators)
+ {
+ if (clause.contains(op))
+ {
+ clause.replace(op, "NOT " + op);
+ return;
+ }
+ }
+ clause = "NOT " + clause;
+ }
+
+ @Override
+ public String toString()
+ {
+ return clause;
+ }
+ }
+ }
+
+ private String getNodeSecondaryChildrenUrl(String nodeId)
+ {
+ return URL_NODES + "/" + nodeId + "/" + "secondary-children";
+ }
+
+ private String getFavoritesUrl(String nodeId)
+ {
+ return "people" + "/" + nodeId + "/" + "favorites";
+ }
+}