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"; + } +}