mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
ACS-4023: Update GET /tags to support getting tags by name (#1766)
* ACS-4023: Update GET /tags to support getting tags by name
This commit is contained in:
committed by
GitHub
parent
0cb03c2a38
commit
f2fdf958f2
@@ -45,6 +45,13 @@ public class ListBackedPagingResults<R> implements PagingResults<R>
|
|||||||
size = list.size();
|
size = list.size();
|
||||||
hasMore = false;
|
hasMore = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListBackedPagingResults(List<R> list, boolean hasMore)
|
||||||
|
{
|
||||||
|
this(list);
|
||||||
|
this.hasMore = hasMore;
|
||||||
|
}
|
||||||
|
|
||||||
public ListBackedPagingResults(List<R> list, PagingRequest paging)
|
public ListBackedPagingResults(List<R> list, PagingRequest paging)
|
||||||
{
|
{
|
||||||
// Excerpt
|
// Excerpt
|
||||||
|
@@ -30,7 +30,10 @@ import static org.alfresco.utility.report.log.Step.STEP;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.alfresco.rest.core.IRestModelsCollection;
|
import org.alfresco.rest.core.IRestModelsCollection;
|
||||||
import org.alfresco.utility.exception.TestConfigurationException;
|
import org.alfresco.utility.exception.TestConfigurationException;
|
||||||
@@ -143,6 +146,53 @@ public class ModelsCollectionAssertion<C>
|
|||||||
return (C) modelCollection;
|
return (C) modelCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public C entrySetContains(String key, String... expectedValues)
|
||||||
|
{
|
||||||
|
return entrySetContains(key, Arrays.stream(expectedValues).collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public C entrySetContains(String key, Collection<String> expectedValues)
|
||||||
|
{
|
||||||
|
Collection<String> actualValues = ((List<Model>) modelCollection.getEntries()).stream()
|
||||||
|
.map(model -> extractValueAsString(model, key))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Assert.assertTrue(actualValues.containsAll(expectedValues), String.format("Entry with key: \"%s\" is expected to contain values: %s, but actual values are: %s",
|
||||||
|
key, expectedValues, actualValues));
|
||||||
|
|
||||||
|
return (C) modelCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public C entrySetMatches(String key, Collection<String> expectedValues)
|
||||||
|
{
|
||||||
|
Collection<String> actualValues = ((List<Model>) modelCollection.getEntries()).stream()
|
||||||
|
.map(model -> extractValueAsString(model, key))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Assert.assertEqualsNoOrder(actualValues, expectedValues, String.format("Entry with key: \"%s\" is expected to match values: %s, but actual values are: %s",
|
||||||
|
key, expectedValues, actualValues));
|
||||||
|
|
||||||
|
return (C) modelCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractValueAsString(Model model, String key)
|
||||||
|
{
|
||||||
|
String fieldValue;
|
||||||
|
Object modelObject = loadModel(model);
|
||||||
|
try {
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
String jsonInString = mapper.writeValueAsString(modelObject);
|
||||||
|
fieldValue = JsonPath.with(jsonInString).get(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new TestConfigurationException(String.format(
|
||||||
|
"You try to assert field [%s] that doesn't exist in class: [%s]. Exception: %s, Please check your code!",
|
||||||
|
key, getClass().getCanonicalName(), e.getMessage()));
|
||||||
|
}
|
||||||
|
return fieldValue;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public C entriesListDoesNotContain(String key)
|
public C entriesListDoesNotContain(String key)
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
package org.alfresco.rest.tags;
|
package org.alfresco.rest.tags;
|
||||||
|
|
||||||
|
import static org.alfresco.utility.report.log.Step.STEP;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.alfresco.rest.model.RestErrorModel;
|
import org.alfresco.rest.model.RestErrorModel;
|
||||||
import org.alfresco.rest.model.RestTagModel;
|
import org.alfresco.rest.model.RestTagModel;
|
||||||
import org.alfresco.rest.model.RestTagModelsCollection;
|
import org.alfresco.rest.model.RestTagModelsCollection;
|
||||||
@@ -9,19 +13,11 @@ import org.alfresco.utility.model.TestGroup;
|
|||||||
import org.alfresco.utility.testrail.ExecutionType;
|
import org.alfresco.utility.testrail.ExecutionType;
|
||||||
import org.alfresco.utility.testrail.annotation.TestRail;
|
import org.alfresco.utility.testrail.annotation.TestRail;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.testng.annotations.BeforeClass;
|
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
@Test(groups = {TestGroup.REQUIRE_SOLR})
|
@Test(groups = {TestGroup.REQUIRE_SOLR})
|
||||||
public class GetTagsTests extends TagsDataPrep
|
public class GetTagsTests extends TagsDataPrep
|
||||||
{
|
{
|
||||||
|
|
||||||
@BeforeClass(alwaysRun = true)
|
|
||||||
public void dataPreparation() throws Exception
|
|
||||||
{
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Verify user with Manager role gets tags using REST API and status code is OK (200)")
|
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Verify user with Manager role gets tags using REST API and status code is OK (200)")
|
||||||
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
|
||||||
public void getTagsWithManagerRole() throws Exception
|
public void getTagsWithManagerRole() throws Exception
|
||||||
@@ -192,7 +188,7 @@ public class GetTagsTests extends TagsDataPrep
|
|||||||
.and().field("hasMoreItems").is("false")
|
.and().field("hasMoreItems").is("false")
|
||||||
.and().field("count").is("0")
|
.and().field("count").is("0")
|
||||||
.and().field("skipCount").is(20000)
|
.and().field("skipCount").is(20000)
|
||||||
.and().field("totalItems").isNull();
|
.and().field("totalItems").is(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
|
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
|
||||||
@@ -219,11 +215,128 @@ public class GetTagsTests extends TagsDataPrep
|
|||||||
RestTagModel deletedTag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
|
RestTagModel deletedTag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
|
||||||
.withCoreAPI().usingResource(document).addTag(removedTag);
|
.withCoreAPI().usingResource(document).addTag(removedTag);
|
||||||
|
|
||||||
restClient.withCoreAPI().usingResource(document).deleteTag(deletedTag);
|
restClient.authenticateUser(adminUserModel).withCoreAPI().usingTag(deletedTag).deleteTag();
|
||||||
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
|
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
|
||||||
|
|
||||||
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
|
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
|
||||||
returnedCollection.assertThat().entriesListIsNotEmpty()
|
returnedCollection.assertThat().entriesListIsNotEmpty()
|
||||||
.and().entriesListDoesNotContain("tag", removedTag.toLowerCase());
|
.and().entriesListDoesNotContain("tag", removedTag.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if exact name filter can be applied.
|
||||||
|
*/
|
||||||
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||||
|
public void testGetTags_withSingleNameFilter()
|
||||||
|
{
|
||||||
|
STEP("Get tags with names filter using EQUALS and expect one item in result");
|
||||||
|
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||||
|
.withParams("where=(tag='" + documentTag.getTag() + "')")
|
||||||
|
.withCoreAPI()
|
||||||
|
.getTags();
|
||||||
|
|
||||||
|
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||||
|
returnedCollection.assertThat()
|
||||||
|
.entrySetMatches("tag", Set.of(documentTagValue.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if multiple names can be applied as a filter.
|
||||||
|
*/
|
||||||
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||||
|
public void testGetTags_withTwoNameFilters()
|
||||||
|
{
|
||||||
|
STEP("Get tags with names filter using IN and expect two items in result");
|
||||||
|
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||||
|
.withParams("where=(tag IN ('" + documentTag.getTag() + "', '" + folderTag.getTag() + "'))")
|
||||||
|
.withCoreAPI()
|
||||||
|
.getTags();
|
||||||
|
|
||||||
|
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||||
|
returnedCollection.assertThat()
|
||||||
|
.entrySetMatches("tag", Set.of(documentTagValue.toLowerCase(), folderTagValue.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if alike name filter can be applied.
|
||||||
|
*/
|
||||||
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||||
|
public void testGetTags_whichNamesStartsWithOrphan()
|
||||||
|
{
|
||||||
|
STEP("Get tags with names filter using MATCHES and expect one item in result");
|
||||||
|
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||||
|
.withParams("where=(tag MATCHES ('orphan*'))")
|
||||||
|
.withCoreAPI()
|
||||||
|
.getTags();
|
||||||
|
|
||||||
|
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||||
|
returnedCollection.assertThat()
|
||||||
|
.entrySetContains("tag", orphanTag.getTag().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that tags can be filtered by exact name and alike name at the same time.
|
||||||
|
*/
|
||||||
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||||
|
public void testGetTags_withExactNameAndAlikeFilters()
|
||||||
|
{
|
||||||
|
STEP("Get tags with names filter using EQUALS and MATCHES and expect four items in result");
|
||||||
|
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||||
|
.withParams("where=(tag='" + orphanTag.getTag() + "' OR tag MATCHES ('*tag*'))")
|
||||||
|
.withCoreAPI()
|
||||||
|
.getTags();
|
||||||
|
|
||||||
|
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||||
|
returnedCollection.assertThat()
|
||||||
|
.entrySetContains("tag", documentTagValue.toLowerCase(), documentTagValue2.toLowerCase(), folderTagValue.toLowerCase(), orphanTag.getTag().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if multiple alike filters can be applied.
|
||||||
|
*/
|
||||||
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||||
|
public void testGetTags_withTwoAlikeFilters()
|
||||||
|
{
|
||||||
|
STEP("Get tags applying names filter using MATCHES twice and expect four items in result");
|
||||||
|
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||||
|
.withParams("where=(tag MATCHES ('orphan*') OR tag MATCHES ('tag*'))")
|
||||||
|
.withCoreAPI()
|
||||||
|
.getTags();
|
||||||
|
|
||||||
|
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||||
|
returnedCollection.assertThat()
|
||||||
|
.entrySetContains("tag", documentTagValue.toLowerCase(), documentTagValue2.toLowerCase(), folderTagValue.toLowerCase(), orphanTag.getTag().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that providing incorrect field name in where query will result with 400 (Bad Request).
|
||||||
|
*/
|
||||||
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||||
|
public void testGetTags_withWrongWherePropertyNameAndExpect400()
|
||||||
|
{
|
||||||
|
STEP("Try to get tags with names filter using EQUALS and wrong property name and expect 400");
|
||||||
|
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||||
|
.withParams("where=(name=gat)")
|
||||||
|
.withCoreAPI()
|
||||||
|
.getTags();
|
||||||
|
|
||||||
|
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
|
||||||
|
.assertLastError().containsSummary("Where query error: property with name: name is not expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify tht AND operator is not supported in where query and expect 400 (Bad Request).
|
||||||
|
*/
|
||||||
|
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
|
||||||
|
public void testGetTags_queryAndOperatorNotSupported()
|
||||||
|
{
|
||||||
|
STEP("Try to get tags applying names filter using AND operator and expect 400");
|
||||||
|
returnedCollection = restClient.authenticateUser(adminUserModel)
|
||||||
|
.withParams("where=(name=tag AND name IN ('tag-', 'gat'))")
|
||||||
|
.withCoreAPI()
|
||||||
|
.getTags();
|
||||||
|
|
||||||
|
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
|
||||||
|
.assertLastError().containsSummary("An invalid WHERE query was received. Unsupported Predicate");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ public class TagsDataPrep extends RestTest
|
|||||||
protected static FileModel document;
|
protected static FileModel document;
|
||||||
protected static FolderModel folder;
|
protected static FolderModel folder;
|
||||||
protected static String documentTagValue, documentTagValue2, folderTagValue;
|
protected static String documentTagValue, documentTagValue2, folderTagValue;
|
||||||
protected static RestTagModel documentTag, documentTag2, folderTag, returnedModel;
|
protected static RestTagModel documentTag, documentTag2, folderTag, orphanTag, returnedModel;
|
||||||
protected static RestTagModelsCollection returnedCollection;
|
protected static RestTagModelsCollection returnedCollection;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
@@ -47,16 +47,17 @@ public class TagsDataPrep extends RestTest
|
|||||||
documentTag = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue);
|
documentTag = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue);
|
||||||
documentTag2 = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue2);
|
documentTag2 = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue2);
|
||||||
folderTag = restClient.withCoreAPI().usingResource(folder).addTag(folderTagValue);
|
folderTag = restClient.withCoreAPI().usingResource(folder).addTag(folderTagValue);
|
||||||
|
orphanTag = restClient.withCoreAPI().createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("orphan-tag")).create());
|
||||||
|
|
||||||
// Allow indexing to complete.
|
// Allow indexing to complete.
|
||||||
Utility.sleep(500, 60000, () ->
|
Utility.sleep(500, 60000, () ->
|
||||||
{
|
{
|
||||||
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
|
returnedCollection = restClient.withParams("maxItems=10000", "where=(tag MATCHES ('*tag*'))")
|
||||||
|
.withCoreAPI().getTags();
|
||||||
returnedCollection.assertThat().entriesListContains("tag", documentTagValue.toLowerCase())
|
returnedCollection.assertThat().entriesListContains("tag", documentTagValue.toLowerCase())
|
||||||
.and().entriesListContains("tag", documentTagValue2.toLowerCase())
|
.and().entriesListContains("tag", documentTagValue2.toLowerCase())
|
||||||
.and().entriesListContains("tag", folderTagValue.toLowerCase());
|
.and().entriesListContains("tag", folderTagValue.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected RestTagModel createTagForDocument(FileModel document)
|
protected RestTagModel createTagForDocument(FileModel document)
|
||||||
|
@@ -25,10 +25,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.rest.api.impl;
|
package org.alfresco.rest.api.impl;
|
||||||
|
|
||||||
|
import static org.alfresco.rest.antlr.WhereClauseParser.EQUALS;
|
||||||
|
import static org.alfresco.rest.antlr.WhereClauseParser.IN;
|
||||||
|
import static org.alfresco.rest.antlr.WhereClauseParser.MATCHES;
|
||||||
|
|
||||||
import java.util.AbstractList;
|
import java.util.AbstractList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -51,6 +57,9 @@ import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationE
|
|||||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.where.Query;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.where.QueryHelper;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.where.QueryImpl;
|
||||||
import org.alfresco.service.Experimental;
|
import org.alfresco.service.Experimental;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.StoreRef;
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
@@ -68,7 +77,8 @@ import org.apache.commons.collections.CollectionUtils;
|
|||||||
*/
|
*/
|
||||||
public class TagsImpl implements Tags
|
public class TagsImpl implements Tags
|
||||||
{
|
{
|
||||||
private static final Object PARAM_INCLUDE_COUNT = "count";
|
private static final String PARAM_INCLUDE_COUNT = "count";
|
||||||
|
private static final String PARAM_WHERE_TAG = "tag";
|
||||||
static final String NOT_A_VALID_TAG = "An invalid parameter has been supplied";
|
static final String NOT_A_VALID_TAG = "An invalid parameter has been supplied";
|
||||||
static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag";
|
static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag";
|
||||||
|
|
||||||
@@ -154,17 +164,18 @@ public class TagsImpl implements Tags
|
|||||||
taggingService.deleteTag(storeRef, tagValue);
|
taggingService.deleteTag(storeRef, tagValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params)
|
public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params)
|
||||||
{
|
{
|
||||||
Paging paging = params.getPaging();
|
Paging paging = params.getPaging();
|
||||||
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging));
|
Map<Integer, Collection<String>> namesFilters = resolveTagNamesQuery(params.getQuery());
|
||||||
taggingService.getPagedTags(storeRef, 0, paging.getMaxItems());
|
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging), namesFilters.get(EQUALS), namesFilters.get(MATCHES));
|
||||||
|
|
||||||
Integer totalItems = results.getTotalResultCount().getFirst();
|
Integer totalItems = results.getTotalResultCount().getFirst();
|
||||||
List<Pair<NodeRef, String>> page = results.getPage();
|
List<Pair<NodeRef, String>> page = results.getPage();
|
||||||
List<Tag> tags = new ArrayList<Tag>(page.size());
|
List<Tag> tags = new ArrayList<>(page.size());
|
||||||
List<Pair<String, Integer>> tagsByCount = null;
|
List<Pair<String, Integer>> tagsByCount;
|
||||||
Map<String, Integer> tagsByCountMap = new HashMap<String, Integer>();
|
Map<String, Integer> tagsByCountMap = new HashMap<>();
|
||||||
|
|
||||||
if (params.getInclude().contains(PARAM_INCLUDE_COUNT))
|
if (params.getInclude().contains(PARAM_INCLUDE_COUNT))
|
||||||
{
|
{
|
||||||
tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef);
|
tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef);
|
||||||
@@ -183,7 +194,7 @@ public class TagsImpl implements Tags
|
|||||||
tags.add(selectedTag);
|
tags.add(selectedTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), (totalItems == null ? null : totalItems.intValue()));
|
return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), totalItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeRef validateTag(String tagId)
|
public NodeRef validateTag(String tagId)
|
||||||
@@ -291,4 +302,38 @@ public class TagsImpl implements Tags
|
|||||||
throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG);
|
throw new PermissionDeniedException(NO_PERMISSION_TO_MANAGE_A_TAG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method resolves where query looking for clauses: EQUALS, IN or MATCHES.
|
||||||
|
* Expected values for EQUALS and IN will be merged under EQUALS clause.
|
||||||
|
* @param namesQuery Where query with expected tag name(s).
|
||||||
|
* @return Map of expected exact and alike tag names.
|
||||||
|
*/
|
||||||
|
private Map<Integer, Collection<String>> resolveTagNamesQuery(final Query namesQuery)
|
||||||
|
{
|
||||||
|
if (namesQuery == null || namesQuery == QueryImpl.EMPTY)
|
||||||
|
{
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<Integer, Collection<String>> properties = QueryHelper
|
||||||
|
.resolve(namesQuery)
|
||||||
|
.usingOrOperator()
|
||||||
|
.withoutNegations()
|
||||||
|
.getProperty(PARAM_WHERE_TAG)
|
||||||
|
.getExpectedValuesForAnyOf(EQUALS, IN, MATCHES)
|
||||||
|
.skipNegated();
|
||||||
|
|
||||||
|
return properties.entrySet().stream()
|
||||||
|
.collect(Collectors.groupingBy((entry) -> {
|
||||||
|
if (entry.getKey() == EQUALS || entry.getKey() == IN)
|
||||||
|
{
|
||||||
|
return EQUALS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return MATCHES;
|
||||||
|
}
|
||||||
|
}, Collectors.flatMapping((entry) -> entry.getValue().stream().map(String::toLowerCase), Collectors.toCollection(HashSet::new))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -101,36 +101,20 @@ public class Tag implements Comparable<Tag>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode()
|
public boolean equals(Object o)
|
||||||
{
|
{
|
||||||
final int prime = 31;
|
if (this == o)
|
||||||
int result = 1;
|
return true;
|
||||||
result = prime * result + ((nodeRef == null) ? 0 : nodeRef.hashCode());
|
if (o == null || getClass() != o.getClass())
|
||||||
return result;
|
return false;
|
||||||
|
Tag tag1 = (Tag) o;
|
||||||
|
return Objects.equals(nodeRef, tag1.nodeRef) && Objects.equals(tag, tag1.tag) && Objects.equals(count, tag1.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Tags are equal if they have the same NodeRef
|
|
||||||
*
|
|
||||||
* (non-Javadoc)
|
|
||||||
* @see java.lang.Object#equals(java.lang.Object)
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj)
|
public int hashCode()
|
||||||
{
|
{
|
||||||
if (this == obj)
|
return Objects.hash(nodeRef, tag, count);
|
||||||
return true;
|
|
||||||
if (obj == null)
|
|
||||||
return false;
|
|
||||||
if (getClass() != obj.getClass())
|
|
||||||
return false;
|
|
||||||
Tag other = (Tag) obj;
|
|
||||||
if (nodeRef == null) {
|
|
||||||
if (other.nodeRef != null)
|
|
||||||
return false;
|
|
||||||
} else if (!nodeRef.equals(other.nodeRef))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -59,9 +59,8 @@ public class TagsEntityResource implements EntityResourceAction.Read<Tag>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Returns a paged list of all currently used tags in the store workspace://SpacesStore for the current tenant.
|
* Returns a paged list of all currently used tags in the store workspace://SpacesStore for the current tenant.
|
||||||
*
|
* GET /tags
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@WebApiDescription(title="A paged list of all tags in the network.")
|
@WebApiDescription(title="A paged list of all tags in the network.")
|
||||||
|
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Remote API
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.framework.resource.parameters.where;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic implementation of {@link QueryHelper.WalkerCallbackAdapter} providing universal handling of Where query clauses.
|
||||||
|
* This implementation supports AND operator and all clause types.
|
||||||
|
* Be default, walker verifies strictly if expected or unexpected properties, and it's comparison types are present in query
|
||||||
|
* and throws {@link InvalidQueryException} if they are missing.
|
||||||
|
*/
|
||||||
|
public class BasicQueryWalker extends QueryHelper.WalkerCallbackAdapter
|
||||||
|
{
|
||||||
|
private static final String EQUALS_AND_IN_NOT_ALLOWED_TOGETHER = "Where query error: cannot use '=' (EQUALS) AND 'IN' clauses with same property: %s";
|
||||||
|
private static final String MISSING_PROPERTY = "Where query error: property with name: %s not present";
|
||||||
|
static final String MISSING_CLAUSE_TYPE = "Where query error: property with name: %s expects clause: %s";
|
||||||
|
static final String MISSING_ANY_CLAUSE_OF_TYPE = "Where query error: property with name: %s expects at least one of clauses: %s";
|
||||||
|
private static final String PROPERTY_NOT_EXPECTED = "Where query error: property with name: %s is not expected";
|
||||||
|
private static final String PROPERTY_NOT_NEGATABLE = "Where query error: property with name: %s cannot be negated";
|
||||||
|
private static final String PROPERTY_NAMES_EMPTY = "Cannot verify WHERE query without expected property names";
|
||||||
|
|
||||||
|
private Collection<String> expectedPropertyNames;
|
||||||
|
private final Map<String, WhereProperty> properties;
|
||||||
|
protected boolean clausesNegatable = true;
|
||||||
|
protected boolean validateStrictly = true;
|
||||||
|
|
||||||
|
public BasicQueryWalker()
|
||||||
|
{
|
||||||
|
this.properties = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicQueryWalker(final String... expectedPropertyNames)
|
||||||
|
{
|
||||||
|
this();
|
||||||
|
this.expectedPropertyNames = Set.of(expectedPropertyNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicQueryWalker(final Collection<String> expectedPropertyNames)
|
||||||
|
{
|
||||||
|
this();
|
||||||
|
this.expectedPropertyNames = expectedPropertyNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClausesNegatable(final boolean clausesNegatable)
|
||||||
|
{
|
||||||
|
this.clausesNegatable = clausesNegatable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValidateStrictly(boolean validateStrictly)
|
||||||
|
{
|
||||||
|
this.validateStrictly = validateStrictly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exists(String propertyName, boolean negated)
|
||||||
|
{
|
||||||
|
verifyPropertyExpectedness(propertyName);
|
||||||
|
verifyClausesNegatability(negated, propertyName);
|
||||||
|
addProperties(propertyName, WhereClauseParser.EXISTS, negated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void between(String propertyName, String firstValue, String secondValue, boolean negated)
|
||||||
|
{
|
||||||
|
verifyPropertyExpectedness(propertyName);
|
||||||
|
verifyClausesNegatability(negated, propertyName);
|
||||||
|
addProperties(propertyName, WhereClauseParser.BETWEEN, negated, firstValue, secondValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void comparison(int type, String propertyName, String propertyValue, boolean negated)
|
||||||
|
{
|
||||||
|
verifyPropertyExpectedness(propertyName);
|
||||||
|
verifyClausesNegatability(negated, propertyName);
|
||||||
|
if (WhereClauseParser.EQUALS == type && isAndSupported() && containsProperty(propertyName, WhereClauseParser.IN, negated))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(EQUALS_AND_IN_NOT_ALLOWED_TOGETHER, propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
addProperties(propertyName, type, negated, propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void in(String propertyName, boolean negated, String... propertyValues)
|
||||||
|
{
|
||||||
|
verifyPropertyExpectedness(propertyName);
|
||||||
|
verifyClausesNegatability(negated, propertyName);
|
||||||
|
if (isAndSupported() && containsProperty(propertyName, WhereClauseParser.EQUALS, negated))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(EQUALS_AND_IN_NOT_ALLOWED_TOGETHER, propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
addProperties(propertyName, WhereClauseParser.IN, negated, propertyValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void matches(final String propertyName, String propertyValue, boolean negated)
|
||||||
|
{
|
||||||
|
verifyPropertyExpectedness(propertyName);
|
||||||
|
verifyClausesNegatability(negated, propertyName);
|
||||||
|
addProperties(propertyName, WhereClauseParser.MATCHES, negated, propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void and()
|
||||||
|
{
|
||||||
|
// Don't need to do anything here - it's enough to enable AND operator.
|
||||||
|
// OR is not supported at the same time.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if property is expected, if not throws {@link InvalidQueryException}.
|
||||||
|
*/
|
||||||
|
protected void verifyPropertyExpectedness(final String propertyName)
|
||||||
|
{
|
||||||
|
if (validateStrictly && CollectionUtils.isNotEmpty(expectedPropertyNames) && !this.expectedPropertyNames.contains(propertyName))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(PROPERTY_NOT_EXPECTED, propertyName));
|
||||||
|
}
|
||||||
|
else if (validateStrictly && CollectionUtils.isEmpty(expectedPropertyNames))
|
||||||
|
{
|
||||||
|
throw new IllegalStateException(PROPERTY_NAMES_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if clause negations are allowed, if not throws {@link InvalidQueryException}.
|
||||||
|
*/
|
||||||
|
protected void verifyClausesNegatability(final boolean negated, final String propertyName)
|
||||||
|
{
|
||||||
|
if (!clausesNegatable && negated)
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(PROPERTY_NOT_NEGATABLE, propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAndSupported()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
and();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (InvalidQueryException ignore)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addProperties(final String propertyName, final int clauseType, final String... propertyValues)
|
||||||
|
{
|
||||||
|
this.addProperties(propertyName, clauseType, false, propertyValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addProperties(final String propertyName, final int clauseType, final boolean negated, final String... propertyValues)
|
||||||
|
{
|
||||||
|
final WhereProperty.ClauseType type = WhereProperty.ClauseType.of(clauseType, negated);
|
||||||
|
final Set<String> propertiesToAdd = Optional.ofNullable(propertyValues).map(Set::of).orElse(Collections.emptySet());
|
||||||
|
if (this.containsProperty(propertyName))
|
||||||
|
{
|
||||||
|
this.properties.get(propertyName).addValuesToType(type, propertiesToAdd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.properties.put(propertyName, new WhereProperty(propertyName, type, propertiesToAdd, validateStrictly));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean containsProperty(final String propertyName)
|
||||||
|
{
|
||||||
|
return this.properties.containsKey(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean containsProperty(final String propertyName, final int clauseType, final boolean negated)
|
||||||
|
{
|
||||||
|
return this.properties.containsKey(propertyName) && this.properties.get(propertyName).containsType(clauseType, negated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getProperty(String propertyName, int type, boolean negated)
|
||||||
|
{
|
||||||
|
return this.getProperty(propertyName).getExpectedValuesFor(type, negated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhereProperty getProperty(final String propertyName)
|
||||||
|
{
|
||||||
|
if (validateStrictly && !this.containsProperty(propertyName))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(MISSING_PROPERTY, propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.properties.get(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WhereProperty> getProperties(final String... propertyNames)
|
||||||
|
{
|
||||||
|
return Arrays.stream(propertyNames)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.distinct()
|
||||||
|
.peek(propertyName -> {
|
||||||
|
if (validateStrictly && !this.containsProperty(propertyName))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(MISSING_PROPERTY, propertyName));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(this.properties::get)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, WhereProperty> getPropertiesAsMap(final String... propertyNames)
|
||||||
|
{
|
||||||
|
return Arrays.stream(propertyNames)
|
||||||
|
.filter(StringUtils::isNotBlank)
|
||||||
|
.distinct()
|
||||||
|
.peek(propertyName -> {
|
||||||
|
if (validateStrictly && !this.containsProperty(propertyName))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(MISSING_PROPERTY, propertyName));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toMap(propertyName -> propertyName, this.properties::get));
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Remote API
|
* Alfresco Remote API
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -25,10 +25,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.rest.framework.resource.parameters.where;
|
package org.alfresco.rest.framework.resource.parameters.where;
|
||||||
|
|
||||||
|
import static org.alfresco.rest.antlr.WhereClauseParser.BETWEEN;
|
||||||
|
import static org.alfresco.rest.antlr.WhereClauseParser.EQUALS;
|
||||||
|
import static org.alfresco.rest.antlr.WhereClauseParser.EXISTS;
|
||||||
|
import static org.alfresco.rest.antlr.WhereClauseParser.IN;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.alfresco.rest.antlr.WhereClauseParser;
|
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||||
import org.antlr.runtime.tree.CommonTree;
|
import org.antlr.runtime.tree.CommonTree;
|
||||||
@@ -45,14 +54,19 @@ public abstract class QueryHelper
|
|||||||
/**
|
/**
|
||||||
* An interface used when walking a query tree. Calls are made to methods when the particular clause is encountered.
|
* An interface used when walking a query tree. Calls are made to methods when the particular clause is encountered.
|
||||||
*/
|
*/
|
||||||
public static interface WalkerCallback
|
public interface WalkerCallback
|
||||||
{
|
{
|
||||||
|
InvalidQueryException UNSUPPORTED = new InvalidQueryException("Unsupported Predicate");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called any time an EXISTS clause is encountered.
|
* Called any time an EXISTS clause is encountered.
|
||||||
* @param propertyName Name of the property
|
* @param propertyName Name of the property
|
||||||
* @param negated returns true if "NOT EXISTS" was used
|
* @param negated returns true if "NOT EXISTS" was used
|
||||||
*/
|
*/
|
||||||
void exists(String propertyName, boolean negated);
|
default void exists(String propertyName, boolean negated)
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called any time a BETWEEN clause is encountered.
|
* Called any time a BETWEEN clause is encountered.
|
||||||
@@ -61,12 +75,18 @@ public abstract class QueryHelper
|
|||||||
* @param secondValue String
|
* @param secondValue String
|
||||||
* @param negated returns true if "NOT BETWEEN" was used
|
* @param negated returns true if "NOT BETWEEN" was used
|
||||||
*/
|
*/
|
||||||
void between(String propertyName, String firstValue, String secondValue, boolean negated);
|
default void between(String propertyName, String firstValue, String secondValue, boolean negated)
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One of EQUALS LESSTHAN GREATERTHAN LESSTHANOREQUALS GREATERTHANOREQUALS;
|
* One of EQUALS LESSTHAN GREATERTHAN LESSTHANOREQUALS GREATERTHANOREQUALS;
|
||||||
*/
|
*/
|
||||||
void comparison(int type, String propertyName, String propertyValue, boolean negated);
|
default void comparison(int type, String propertyName, String propertyValue, boolean negated)
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called any time an IN clause is encountered.
|
* Called any time an IN clause is encountered.
|
||||||
@@ -74,7 +94,10 @@ public abstract class QueryHelper
|
|||||||
* @param negated returns true if "NOT IN" was used
|
* @param negated returns true if "NOT IN" was used
|
||||||
* @param propertyValues the property values
|
* @param propertyValues the property values
|
||||||
*/
|
*/
|
||||||
void in(String property, boolean negated, String... propertyValues);
|
default void in(String property, boolean negated, String... propertyValues)
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called any time a MATCHES clause is encountered.
|
* Called any time a MATCHES clause is encountered.
|
||||||
@@ -82,42 +105,37 @@ public abstract class QueryHelper
|
|||||||
* @param propertyValue String
|
* @param propertyValue String
|
||||||
* @param negated returns true if "NOT MATCHES" was used
|
* @param negated returns true if "NOT MATCHES" was used
|
||||||
*/
|
*/
|
||||||
void matches(String property, String propertyValue, boolean negated);
|
default void matches(String property, String propertyValue, boolean negated)
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called any time an AND is encountered.
|
* Called any time an AND is encountered.
|
||||||
*/
|
*/
|
||||||
void and();
|
default void and()
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Called any time an OR is encountered.
|
* Called any time an OR is encountered.
|
||||||
*/
|
*/
|
||||||
void or();
|
default void or()
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Collection<String> getProperty(String propertyName, int type, boolean negated)
|
||||||
|
{
|
||||||
|
throw UNSUPPORTED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation. Override the methods you are interested in. If you don't
|
* Default implementation. Override the methods you are interested in. If you don't
|
||||||
* override the methods then an InvalidQueryException will be thrown.
|
* override the methods then an InvalidQueryException will be thrown.
|
||||||
*/
|
*/
|
||||||
public static class WalkerCallbackAdapter implements WalkerCallback
|
public static class WalkerCallbackAdapter implements WalkerCallback {}
|
||||||
{
|
|
||||||
private static final String UNSUPPORTED_TEXT = "Unsupported Predicate";
|
|
||||||
protected static final InvalidQueryException UNSUPPORTED = new InvalidQueryException(UNSUPPORTED_TEXT);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exists(String propertyName, boolean negated) { throw UNSUPPORTED;}
|
|
||||||
@Override
|
|
||||||
public void between(String propertyName, String firstValue, String secondValue, boolean negated) { throw UNSUPPORTED;}
|
|
||||||
@Override
|
|
||||||
public void comparison(int type, String propertyName, String propertyValue, boolean negated) { throw UNSUPPORTED;}
|
|
||||||
@Override
|
|
||||||
public void in(String propertyName, boolean negated, String... propertyValues) { throw UNSUPPORTED;}
|
|
||||||
@Override
|
|
||||||
public void matches(String property, String value, boolean negated) { throw UNSUPPORTED;}
|
|
||||||
@Override
|
|
||||||
public void and() {throw UNSUPPORTED;}
|
|
||||||
@Override
|
|
||||||
public void or() {throw UNSUPPORTED;}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walks a query with a callback for each operation
|
* Walks a query with a callback for each operation
|
||||||
@@ -146,7 +164,7 @@ public abstract class QueryHelper
|
|||||||
if (tree != null)
|
if (tree != null)
|
||||||
{
|
{
|
||||||
switch (tree.getType()) {
|
switch (tree.getType()) {
|
||||||
case WhereClauseParser.EXISTS:
|
case EXISTS:
|
||||||
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
||||||
{
|
{
|
||||||
callback.exists(tree.getChild(0).getText(), negated);
|
callback.exists(tree.getChild(0).getText(), negated);
|
||||||
@@ -160,7 +178,7 @@ public abstract class QueryHelper
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WhereClauseParser.IN:
|
case IN:
|
||||||
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
||||||
{
|
{
|
||||||
List<Tree> children = getChildren(tree);
|
List<Tree> children = getChildren(tree);
|
||||||
@@ -174,14 +192,14 @@ public abstract class QueryHelper
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WhereClauseParser.BETWEEN:
|
case BETWEEN:
|
||||||
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
if (WhereClauseParser.PROPERTYNAME == tree.getChild(0).getType())
|
||||||
{
|
{
|
||||||
callback.between(tree.getChild(0).getText(), stripQuotes(tree.getChild(1).getText()), stripQuotes(tree.getChild(2).getText()), negated);
|
callback.between(tree.getChild(0).getText(), stripQuotes(tree.getChild(1).getText()), stripQuotes(tree.getChild(2).getText()), negated);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case WhereClauseParser.EQUALS: //fall through (comparison)
|
case EQUALS: //fall through (comparison)
|
||||||
case WhereClauseParser.LESSTHAN: //fall through (comparison)
|
case WhereClauseParser.LESSTHAN: //fall through (comparison)
|
||||||
case WhereClauseParser.GREATERTHAN: //fall through (comparison)
|
case WhereClauseParser.GREATERTHAN: //fall through (comparison)
|
||||||
case WhereClauseParser.LESSTHANOREQUALS: //fall through (comparison)
|
case WhereClauseParser.LESSTHANOREQUALS: //fall through (comparison)
|
||||||
@@ -286,4 +304,180 @@ public abstract class QueryHelper
|
|||||||
}
|
}
|
||||||
return toBeStripped; //default to return the String unchanged.
|
return toBeStripped; //default to return the String unchanged.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static QueryResolver.WalkerSpecifier resolve(final Query query)
|
||||||
|
{
|
||||||
|
return new QueryResolver.WalkerSpecifier(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class allowing WHERE query resolving using query walker. By default {@link BasicQueryWalker} is used, but different walker can be supplied.
|
||||||
|
*/
|
||||||
|
public static abstract class QueryResolver<S extends QueryResolver<?>>
|
||||||
|
{
|
||||||
|
private final Query query;
|
||||||
|
protected WalkerCallback queryWalker;
|
||||||
|
protected Function<Collection<String>, BasicQueryWalker> orQueryWalkerSupplier;
|
||||||
|
protected boolean clausesNegatable = true;
|
||||||
|
protected boolean validateLeniently = false;
|
||||||
|
protected abstract S self();
|
||||||
|
|
||||||
|
public QueryResolver(Query query)
|
||||||
|
{
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get property expected values.
|
||||||
|
* @param propertyName Property name.
|
||||||
|
* @param clauseType Property comparison type.
|
||||||
|
* @param negated Comparison type negation.
|
||||||
|
* @return Map composed of all comparators and compared values.
|
||||||
|
*/
|
||||||
|
public Collection<String> getProperty(final String propertyName, final int clauseType, final boolean negated)
|
||||||
|
{
|
||||||
|
processQuery(propertyName);
|
||||||
|
return queryWalker.getProperty(propertyName, clauseType, negated);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processQuery(final String... propertyNames)
|
||||||
|
{
|
||||||
|
if (queryWalker == null)
|
||||||
|
{
|
||||||
|
if (orQueryWalkerSupplier != null)
|
||||||
|
{
|
||||||
|
queryWalker = orQueryWalkerSupplier.apply(Set.of(propertyNames));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
queryWalker = new BasicQueryWalker(propertyNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queryWalker instanceof BasicQueryWalker)
|
||||||
|
{
|
||||||
|
((BasicQueryWalker) queryWalker).setClausesNegatable(clausesNegatable);
|
||||||
|
((BasicQueryWalker) queryWalker).setValidateStrictly(!validateLeniently);
|
||||||
|
}
|
||||||
|
walk(query, queryWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class providing methods related with default query walker {@link BasicQueryWalker}.
|
||||||
|
*/
|
||||||
|
public static class DefaultWalkerOperations<R extends DefaultWalkerOperations<?>> extends QueryResolver<R>
|
||||||
|
{
|
||||||
|
public DefaultWalkerOperations(Query query)
|
||||||
|
{
|
||||||
|
super(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
protected R self()
|
||||||
|
{
|
||||||
|
return (R) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that query properties and comparison types should NOT be verified strictly.
|
||||||
|
*/
|
||||||
|
public R leniently()
|
||||||
|
{
|
||||||
|
this.validateLeniently = true;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that clause types negations are not allowed in query.
|
||||||
|
*/
|
||||||
|
public R withoutNegations()
|
||||||
|
{
|
||||||
|
this.clausesNegatable = false;
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get property with expected values.
|
||||||
|
* @param propertyName Property name.
|
||||||
|
* @return Map composed of all comparators and compared values.
|
||||||
|
*/
|
||||||
|
public WhereProperty getProperty(final String propertyName)
|
||||||
|
{
|
||||||
|
processQuery(propertyName);
|
||||||
|
return ((BasicQueryWalker) this.queryWalker).getProperty(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get multiple properties with it's expected values.
|
||||||
|
* @param propertyNames Property names.
|
||||||
|
* @return List of maps composed of all comparators and compared values.
|
||||||
|
*/
|
||||||
|
public List<WhereProperty> getProperties(final String... propertyNames)
|
||||||
|
{
|
||||||
|
processQuery(propertyNames);
|
||||||
|
return ((BasicQueryWalker) this.queryWalker).getProperties(propertyNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get multiple properties with it's expected values.
|
||||||
|
* @param propertyNames Property names.
|
||||||
|
* @return Map composed of property names and maps composed of all comparators and compared values.
|
||||||
|
*/
|
||||||
|
public Map<String, WhereProperty> getPropertiesAsMap(final String... propertyNames)
|
||||||
|
{
|
||||||
|
processQuery(propertyNames);
|
||||||
|
return ((BasicQueryWalker) this.queryWalker).getPropertiesAsMap(propertyNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class allowing to specify custom {@link WalkerCallback} implementation or {@link BasicQueryWalker} extension.
|
||||||
|
*/
|
||||||
|
public static class WalkerSpecifier extends DefaultWalkerOperations<WalkerSpecifier>
|
||||||
|
{
|
||||||
|
public WalkerSpecifier(Query query)
|
||||||
|
{
|
||||||
|
super(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WalkerSpecifier self()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that OR operator instead of AND should be used while resolving the query.
|
||||||
|
*/
|
||||||
|
public DefaultWalkerOperations<? extends DefaultWalkerOperations<?>> usingOrOperator()
|
||||||
|
{
|
||||||
|
this.orQueryWalkerSupplier = (propertyNames) -> new BasicQueryWalker(propertyNames)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void or() {/*Enable OR support, disable AND support*/}
|
||||||
|
@Override
|
||||||
|
public void and() {throw UNSUPPORTED;}
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to specify custom {@link BasicQueryWalker} extension, which should be used to resolve the query.
|
||||||
|
*/
|
||||||
|
public <T extends BasicQueryWalker> DefaultWalkerOperations<? extends DefaultWalkerOperations<?>> usingWalker(final T queryWalker)
|
||||||
|
{
|
||||||
|
this.queryWalker = queryWalker;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to specify custom {@link WalkerCallback} implementation, which should be used to resolve the query.
|
||||||
|
*/
|
||||||
|
public <T extends WalkerCallback> QueryResolver<? extends QueryResolver<?>> usingWalker(final T queryWalker)
|
||||||
|
{
|
||||||
|
this.queryWalker = queryWalker;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,351 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Remote API
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.framework.resource.parameters.where;
|
||||||
|
|
||||||
|
import static java.util.function.Predicate.not;
|
||||||
|
|
||||||
|
import static org.alfresco.rest.framework.resource.parameters.where.BasicQueryWalker.MISSING_ANY_CLAUSE_OF_TYPE;
|
||||||
|
import static org.alfresco.rest.framework.resource.parameters.where.BasicQueryWalker.MISSING_CLAUSE_TYPE;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map composed of property comparison type and compared values.
|
||||||
|
* Map key is clause (comparison) type.
|
||||||
|
*/
|
||||||
|
public class WhereProperty extends HashMap<WhereProperty.ClauseType, Collection<String>>
|
||||||
|
{
|
||||||
|
private final String name;
|
||||||
|
private boolean validateStrictly;
|
||||||
|
|
||||||
|
public WhereProperty(final String name, final ClauseType clauseType, final Collection<String> values)
|
||||||
|
{
|
||||||
|
super(Map.of(clauseType, new HashSet<>(values)));
|
||||||
|
this.name = name;
|
||||||
|
this.validateStrictly = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhereProperty(final String name, final ClauseType clauseType, final Collection<String> values, final boolean validateStrictly)
|
||||||
|
{
|
||||||
|
this(name, clauseType, values);
|
||||||
|
this.validateStrictly = validateStrictly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addValuesToType(final ClauseType clauseType, final Collection<String> values)
|
||||||
|
{
|
||||||
|
if (this.containsKey(clauseType))
|
||||||
|
{
|
||||||
|
this.get(clauseType).addAll(values);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.put(clauseType, new HashSet<>(values));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsType(final ClauseType clauseType)
|
||||||
|
{
|
||||||
|
return this.containsKey(clauseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsType(final int clauseType, final boolean negated)
|
||||||
|
{
|
||||||
|
return this.containsKey(ClauseType.of(clauseType, negated));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsAllTypes(final ClauseType... clauseType)
|
||||||
|
{
|
||||||
|
return Arrays.stream(clauseType).distinct().filter(this::containsKey).count() == clauseType.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsAnyOfTypes(final ClauseType... clauseType)
|
||||||
|
{
|
||||||
|
return Arrays.stream(clauseType).distinct().anyMatch(this::containsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getExpectedValuesFor(final ClauseType clauseType)
|
||||||
|
{
|
||||||
|
verifyAllClausesPresence(clauseType);
|
||||||
|
return this.get(clauseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<ClauseType, Collection<String>> getExpectedValuesForAllOf(final ClauseType... clauseTypes)
|
||||||
|
{
|
||||||
|
verifyAllClausesPresence(clauseTypes);
|
||||||
|
return Arrays.stream(clauseTypes)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toMap(type -> type, this::get, (type1, type2) -> type1, MultiTypeNegatableValuesMap::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<ClauseType, Collection<String>> getExpectedValuesForAnyOf(final ClauseType... clauseTypes)
|
||||||
|
{
|
||||||
|
verifyAnyClausesPresence(clauseTypes);
|
||||||
|
return Arrays.stream(clauseTypes)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toMap(type -> type, this::get, (type1, type2) -> type1, MultiTypeNegatableValuesMap::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getExpectedValuesFor(final int clauseType, final boolean negated)
|
||||||
|
{
|
||||||
|
verifyAllClausesPresence(ClauseType.of(clauseType, negated));
|
||||||
|
return this.get(ClauseType.of(clauseType, negated));
|
||||||
|
}
|
||||||
|
|
||||||
|
public NegatableValuesMap getExpectedValuesFor(final int clauseType)
|
||||||
|
{
|
||||||
|
verifyAllClausesPresence(clauseType);
|
||||||
|
final NegatableValuesMap values = new NegatableValuesMap();
|
||||||
|
final ClauseType type = ClauseType.of(clauseType);
|
||||||
|
final ClauseType negatedType = type.negate();
|
||||||
|
if (this.containsKey(type))
|
||||||
|
{
|
||||||
|
values.put(false, this.get(type));
|
||||||
|
}
|
||||||
|
if (this.containsKey(negatedType))
|
||||||
|
{
|
||||||
|
values.put(true, this.get(negatedType));
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiTypeNegatableValuesMap getExpectedValuesForAllOf(final int... clauseTypes)
|
||||||
|
{
|
||||||
|
verifyAllClausesPresence(clauseTypes);
|
||||||
|
return getExpectedValuesFor(clauseTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiTypeNegatableValuesMap getExpectedValuesForAnyOf(final int... clauseTypes)
|
||||||
|
{
|
||||||
|
verifyAnyClausesPresence(clauseTypes);
|
||||||
|
return getExpectedValuesFor(clauseTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiTypeNegatableValuesMap getExpectedValuesFor(final int... clauseTypes)
|
||||||
|
{
|
||||||
|
final MultiTypeNegatableValuesMap values = new MultiTypeNegatableValuesMap();
|
||||||
|
Arrays.stream(clauseTypes).distinct().forEach(clauseType -> {
|
||||||
|
final ClauseType type = ClauseType.of(clauseType);
|
||||||
|
final ClauseType negatedType = type.negate();
|
||||||
|
if (this.containsKey(type))
|
||||||
|
{
|
||||||
|
values.put(type, this.get(type));
|
||||||
|
}
|
||||||
|
if (this.containsKey(negatedType))
|
||||||
|
{
|
||||||
|
values.put(negatedType, this.get(negatedType));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if all specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||||
|
*/
|
||||||
|
private void verifyAllClausesPresence(final ClauseType... clauseTypes)
|
||||||
|
{
|
||||||
|
if (validateStrictly)
|
||||||
|
{
|
||||||
|
Arrays.stream(clauseTypes).distinct().forEach(clauseType -> {
|
||||||
|
if (!this.containsType(clauseType))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(MISSING_CLAUSE_TYPE, this.name, WhereClauseParser.tokenNames[clauseType.getTypeNumber()]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if all specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||||
|
* Exception is thrown when both, negated and non-negated types are missing.
|
||||||
|
*/
|
||||||
|
private void verifyAllClausesPresence(final int... clauseTypes)
|
||||||
|
{
|
||||||
|
if (validateStrictly)
|
||||||
|
{
|
||||||
|
Arrays.stream(clauseTypes).distinct().forEach(clauseType -> {
|
||||||
|
if (!this.containsType(clauseType, false) && !this.containsType(clauseType, true))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(MISSING_CLAUSE_TYPE, this.name, WhereClauseParser.tokenNames[clauseType]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if any of specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||||
|
*/
|
||||||
|
private void verifyAnyClausesPresence(final ClauseType... clauseTypes)
|
||||||
|
{
|
||||||
|
if (validateStrictly)
|
||||||
|
{
|
||||||
|
if (!this.containsAnyOfTypes(clauseTypes))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(MISSING_ANY_CLAUSE_OF_TYPE,
|
||||||
|
this.name, Arrays.stream(clauseTypes).map(type -> WhereClauseParser.tokenNames[type.getTypeNumber()]).collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if any of specified clause types are present in this map, if not than throw {@link InvalidQueryException}.
|
||||||
|
* Exception is thrown when both, negated and non-negated types are missing.
|
||||||
|
*/
|
||||||
|
private void verifyAnyClausesPresence(final int... clauseTypes)
|
||||||
|
{
|
||||||
|
if (validateStrictly)
|
||||||
|
{
|
||||||
|
final Collection<ClauseType> expectedTypes = Arrays.stream(clauseTypes)
|
||||||
|
.distinct()
|
||||||
|
.boxed()
|
||||||
|
.flatMap(type -> Stream.of(ClauseType.of(type), ClauseType.of(type, true)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
if (!this.containsAnyOfTypes(expectedTypes.toArray(ClauseType[]::new)))
|
||||||
|
{
|
||||||
|
throw new InvalidQueryException(String.format(MISSING_ANY_CLAUSE_OF_TYPE,
|
||||||
|
this.name, Arrays.stream(clauseTypes).mapToObj(type -> WhereClauseParser.tokenNames[type]).collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ClauseType
|
||||||
|
{
|
||||||
|
EQUALS(WhereClauseParser.EQUALS),
|
||||||
|
NOT_EQUALS(WhereClauseParser.EQUALS, true),
|
||||||
|
GREATER_THAN(WhereClauseParser.GREATERTHAN),
|
||||||
|
NOT_GREATER_THAN(WhereClauseParser.GREATERTHAN, true),
|
||||||
|
LESS_THAN(WhereClauseParser.LESSTHAN),
|
||||||
|
NOT_LESS_THAN(WhereClauseParser.LESSTHAN, true),
|
||||||
|
GREATER_THAN_OR_EQUALS(WhereClauseParser.GREATERTHANOREQUALS),
|
||||||
|
NOT_GREATER_THAN_OR_EQUALS(WhereClauseParser.GREATERTHANOREQUALS, true),
|
||||||
|
LESS_THAN_OR_EQUALS(WhereClauseParser.LESSTHANOREQUALS),
|
||||||
|
NOT_LESS_THAN_OR_EQUALS(WhereClauseParser.LESSTHANOREQUALS, true),
|
||||||
|
BETWEEN(WhereClauseParser.BETWEEN),
|
||||||
|
NOT_BETWEEN(WhereClauseParser.BETWEEN, true),
|
||||||
|
IN(WhereClauseParser.IN),
|
||||||
|
NOT_IN(WhereClauseParser.IN, true),
|
||||||
|
MATCHES(WhereClauseParser.MATCHES),
|
||||||
|
NOT_MATCHES(WhereClauseParser.MATCHES, true),
|
||||||
|
EXISTS(WhereClauseParser.EXISTS),
|
||||||
|
NOT_EXISTS(WhereClauseParser.EXISTS, true);
|
||||||
|
|
||||||
|
private final int typeNumber;
|
||||||
|
private final boolean negated;
|
||||||
|
|
||||||
|
ClauseType(final int typeNumber)
|
||||||
|
{
|
||||||
|
this.typeNumber = typeNumber;
|
||||||
|
this.negated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClauseType(final int typeNumber, final boolean negated)
|
||||||
|
{
|
||||||
|
this.typeNumber = typeNumber;
|
||||||
|
this.negated = negated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClauseType of(final int type)
|
||||||
|
{
|
||||||
|
return of(type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClauseType of(final int type, final boolean negated)
|
||||||
|
{
|
||||||
|
return Arrays.stream(ClauseType.values())
|
||||||
|
.filter(clauseType -> clauseType.typeNumber == type && clauseType.negated == negated)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClauseType negate()
|
||||||
|
{
|
||||||
|
return of(typeNumber, !negated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTypeNumber()
|
||||||
|
{
|
||||||
|
return typeNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNegated()
|
||||||
|
{
|
||||||
|
return negated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NegatableValuesMap extends HashMap<Boolean, Collection<String>>
|
||||||
|
{
|
||||||
|
public Collection<String> skipNegated()
|
||||||
|
{
|
||||||
|
return this.get(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> onlyNegated()
|
||||||
|
{
|
||||||
|
return this.get(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MultiTypeNegatableValuesMap extends HashMap<ClauseType, Collection<String>>
|
||||||
|
{
|
||||||
|
public Map<Integer, Collection<String>> skipNegated()
|
||||||
|
{
|
||||||
|
return this.keySet().stream()
|
||||||
|
.filter(not(ClauseType::isNegated))
|
||||||
|
.collect(Collectors.toMap(key -> key.typeNumber, this::get));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> skipNegated(final int clauseType)
|
||||||
|
{
|
||||||
|
return this.get(ClauseType.of(clauseType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, Collection<String>> onlyNegated()
|
||||||
|
{
|
||||||
|
return this.keySet().stream()
|
||||||
|
.filter(not(ClauseType::isNegated))
|
||||||
|
.collect(Collectors.toMap(key -> key.typeNumber, this::get));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> onlyNegated(final int clauseType)
|
||||||
|
{
|
||||||
|
return this.get(ClauseType.of(clauseType, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Remote API
|
* Alfresco Remote API
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -27,6 +27,7 @@ package org.alfresco.rest.workflow.api.impl;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -266,6 +267,15 @@ public class MapBasedQueryWalker extends WalkerCallbackAdapter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get expected value for property and comparison type. This class supports only non-negated comparisons, thus parameter negated is ignored in bellow method.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<String> getProperty(String propertyName, int type, boolean negated)
|
||||||
|
{
|
||||||
|
return Set.of(this.getProperty(propertyName, type));
|
||||||
|
}
|
||||||
|
|
||||||
public String getProperty(String propertyName, int type)
|
public String getProperty(String propertyName, int type)
|
||||||
{
|
{
|
||||||
if (type == WhereClauseParser.EQUALS)
|
if (type == WhereClauseParser.EQUALS)
|
||||||
|
@@ -27,24 +27,34 @@ package org.alfresco.rest.api.impl;
|
|||||||
|
|
||||||
import static org.alfresco.rest.api.impl.TagsImpl.NOT_A_VALID_TAG;
|
import static org.alfresco.rest.api.impl.TagsImpl.NOT_A_VALID_TAG;
|
||||||
import static org.alfresco.rest.api.impl.TagsImpl.NO_PERMISSION_TO_MANAGE_A_TAG;
|
import static org.alfresco.rest.api.impl.TagsImpl.NO_PERMISSION_TO_MANAGE_A_TAG;
|
||||||
|
import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_WORKSPACE_SPACESSTORE;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.BDDMockito.then;
|
import static org.mockito.BDDMockito.then;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.alfresco.query.PagingRequest;
|
||||||
|
import org.alfresco.query.PagingResults;
|
||||||
import org.alfresco.rest.api.Nodes;
|
import org.alfresco.rest.api.Nodes;
|
||||||
import org.alfresco.rest.api.model.Tag;
|
import org.alfresco.rest.api.model.Tag;
|
||||||
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||||
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.where.InvalidQueryException;
|
||||||
|
import org.alfresco.rest.framework.tools.RecognizedParamsExtractor;
|
||||||
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.StoreRef;
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
@@ -63,7 +73,9 @@ public class TagsImplTest
|
|||||||
{
|
{
|
||||||
private static final String TAG_ID = "tag-node-id";
|
private static final String TAG_ID = "tag-node-id";
|
||||||
private static final String TAG_NAME = "tag-dummy-name";
|
private static final String TAG_NAME = "tag-dummy-name";
|
||||||
private static final NodeRef TAG_NODE_REF = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
private static final NodeRef TAG_NODE_REF = new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(TAG_NAME));
|
||||||
|
|
||||||
|
private final RecognizedParamsExtractor queryExtractor = new RecognizedParamsExtractor() {};
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Nodes nodesMock;
|
private Nodes nodesMock;
|
||||||
@@ -73,6 +85,10 @@ public class TagsImplTest
|
|||||||
private TaggingService taggingServiceMock;
|
private TaggingService taggingServiceMock;
|
||||||
@Mock
|
@Mock
|
||||||
private Parameters parametersMock;
|
private Parameters parametersMock;
|
||||||
|
@Mock
|
||||||
|
private Paging pagingMock;
|
||||||
|
@Mock
|
||||||
|
private PagingResults<Pair<NodeRef, String>> pagingResultsMock;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private TagsImpl objectUnderTest;
|
private TagsImpl objectUnderTest;
|
||||||
@@ -81,36 +97,145 @@ public class TagsImplTest
|
|||||||
public void setup()
|
public void setup()
|
||||||
{
|
{
|
||||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
|
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
|
||||||
given(nodesMock.validateNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID)).willReturn(TAG_NODE_REF);
|
given(nodesMock.validateNode(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID)).willReturn(TAG_NODE_REF);
|
||||||
given(taggingServiceMock.getTagName(TAG_NODE_REF)).willReturn(TAG_NAME);
|
given(taggingServiceMock.getTagName(TAG_NODE_REF)).willReturn(TAG_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetTags() {
|
public void testGetTags()
|
||||||
final List<String> tagNames = List.of("testTag","tag11");
|
{
|
||||||
final List<Tag> tagsToCreate = createTags(tagNames);
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
given(taggingServiceMock.createTags(any(), any())).willAnswer(invocation -> createTagAndNodeRefPairs(invocation.getArgument(1)));
|
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||||
|
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||||
|
given(pagingResultsMock.getPage()).willReturn(List.of(new Pair<>(TAG_NODE_REF, TAG_NAME)));
|
||||||
|
|
||||||
|
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||||
|
|
||||||
|
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), isNull(), isNull());
|
||||||
|
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||||
|
final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME)).stream().peek(tag -> tag.setCount(0)).collect(Collectors.toList());
|
||||||
|
assertEquals(expectedTags, actualTags.getCollection());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTags_verifyIfCountIsZero()
|
||||||
|
{
|
||||||
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
|
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||||
|
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||||
|
given(pagingResultsMock.getPage()).willReturn(List.of(new Pair<>(TAG_NODE_REF, TAG_NAME)));
|
||||||
given(parametersMock.getInclude()).willReturn(List.of("count"));
|
given(parametersMock.getInclude()).willReturn(List.of("count"));
|
||||||
final List<Tag> actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock);
|
|
||||||
final List<Tag> expectedTags = createTagsWithNodeRefs(tagNames).stream()
|
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||||
|
|
||||||
|
then(taggingServiceMock).should().findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE);
|
||||||
|
final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME)).stream()
|
||||||
.peek(tag -> tag.setCount(0))
|
.peek(tag -> tag.setCount(0))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
assertEquals(expectedTags, actualCreatedTags);
|
assertEquals(expectedTags, actualTags.getCollection());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTags_withEqualsClauseWhereQuery()
|
||||||
|
{
|
||||||
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
|
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag=expectedName)"));
|
||||||
|
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||||
|
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||||
|
|
||||||
|
//when
|
||||||
|
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||||
|
|
||||||
|
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), eq(Set.of("expectedname")), isNull());
|
||||||
|
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||||
|
assertThat(actualTags).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTags_withInClauseWhereQuery()
|
||||||
|
{
|
||||||
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
|
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag IN (expectedName1, expectedName2))"));
|
||||||
|
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||||
|
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||||
|
|
||||||
|
//when
|
||||||
|
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||||
|
|
||||||
|
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), eq(Set.of("expectedname1", "expectedname2")), isNull());
|
||||||
|
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||||
|
assertThat(actualTags).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTags_withMatchesClauseWhereQuery()
|
||||||
|
{
|
||||||
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
|
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag MATCHES ('expectedName*'))"));
|
||||||
|
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
|
||||||
|
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
|
||||||
|
|
||||||
|
//when
|
||||||
|
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
|
||||||
|
|
||||||
|
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), isNull(), eq(Set.of("expectedname*")));
|
||||||
|
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||||
|
assertThat(actualTags).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTags_withBothInAndEqualsClausesInSingleWhereQuery()
|
||||||
|
{
|
||||||
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
|
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag=expectedName AND tag IN (expectedName1, expectedName2))"));
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
|
||||||
|
|
||||||
|
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTags_withOtherClauseInWhereQuery()
|
||||||
|
{
|
||||||
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
|
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(tag BETWEEN ('expectedName', 'expectedName2'))"));
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
|
||||||
|
|
||||||
|
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetTags_withNotEqualsClauseInWhereQuery()
|
||||||
|
{
|
||||||
|
given(parametersMock.getPaging()).willReturn(pagingMock);
|
||||||
|
given(parametersMock.getQuery()).willReturn(queryExtractor.getWhereClause("(NOT tag=expectedName)"));
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock));
|
||||||
|
|
||||||
|
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteTagById()
|
public void testDeleteTagById()
|
||||||
{
|
{
|
||||||
//when
|
//when
|
||||||
objectUnderTest.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
objectUnderTest.deleteTagById(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||||
|
|
||||||
then(authorityServiceMock).should().hasAdminAuthority();
|
then(authorityServiceMock).should().hasAdminAuthority();
|
||||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||||
|
|
||||||
then(nodesMock).should().validateNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
then(nodesMock).should().validateNode(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID);
|
||||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||||
|
|
||||||
then(taggingServiceMock).should().getTagName(TAG_NODE_REF);
|
then(taggingServiceMock).should().getTagName(TAG_NODE_REF);
|
||||||
then(taggingServiceMock).should().deleteTag(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME);
|
then(taggingServiceMock).should().deleteTag(STORE_REF_WORKSPACE_SPACESSTORE, TAG_NAME);
|
||||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +245,7 @@ public class TagsImplTest
|
|||||||
given(authorityServiceMock.hasAdminAuthority()).willReturn(false);
|
given(authorityServiceMock.hasAdminAuthority()).willReturn(false);
|
||||||
|
|
||||||
//when
|
//when
|
||||||
assertThrows(PermissionDeniedException.class, () -> objectUnderTest.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID));
|
assertThrows(PermissionDeniedException.class, () -> objectUnderTest.deleteTagById(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID));
|
||||||
|
|
||||||
then(authorityServiceMock).should().hasAdminAuthority();
|
then(authorityServiceMock).should().hasAdminAuthority();
|
||||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||||
@@ -134,12 +259,12 @@ public class TagsImplTest
|
|||||||
public void testDeleteTagById_nonExistentTag()
|
public void testDeleteTagById_nonExistentTag()
|
||||||
{
|
{
|
||||||
//when
|
//when
|
||||||
assertThrows(EntityNotFoundException.class, () -> objectUnderTest.deleteTagById(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id"));
|
assertThrows(EntityNotFoundException.class, () -> objectUnderTest.deleteTagById(STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id"));
|
||||||
|
|
||||||
then(authorityServiceMock).should().hasAdminAuthority();
|
then(authorityServiceMock).should().hasAdminAuthority();
|
||||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||||
|
|
||||||
then(nodesMock).should().validateNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id");
|
then(nodesMock).should().validateNode(STORE_REF_WORKSPACE_SPACESSTORE, "dummy-id");
|
||||||
then(nodesMock).shouldHaveNoMoreInteractions();
|
then(nodesMock).shouldHaveNoMoreInteractions();
|
||||||
|
|
||||||
then(taggingServiceMock).shouldHaveNoInteractions();
|
then(taggingServiceMock).shouldHaveNoInteractions();
|
||||||
@@ -157,11 +282,11 @@ public class TagsImplTest
|
|||||||
|
|
||||||
then(authorityServiceMock).should().hasAdminAuthority();
|
then(authorityServiceMock).should().hasAdminAuthority();
|
||||||
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
then(authorityServiceMock).shouldHaveNoMoreInteractions();
|
||||||
then(taggingServiceMock).should().createTags(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, tagNames);
|
then(taggingServiceMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, tagNames);
|
||||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||||
final List<Tag> expectedTags = createTagsWithNodeRefs(tagNames);
|
final List<Tag> expectedTags = createTagsWithNodeRefs(tagNames);
|
||||||
assertThat(actualCreatedTags)
|
assertThat(actualCreatedTags)
|
||||||
.isNotNull()
|
.isNotNull().usingRecursiveComparison()
|
||||||
.isEqualTo(expectedTags);
|
.isEqualTo(expectedTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +350,7 @@ public class TagsImplTest
|
|||||||
//when
|
//when
|
||||||
final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(List.of(createTag(TAG_NAME)), parametersMock));
|
final Throwable actualException = catchThrowable(() -> objectUnderTest.createTags(List.of(createTag(TAG_NAME)), parametersMock));
|
||||||
|
|
||||||
then(taggingServiceMock).should().createTags(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
then(taggingServiceMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
||||||
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
then(taggingServiceMock).shouldHaveNoMoreInteractions();
|
||||||
assertThat(actualException).isInstanceOf(DuplicateChildNodeNameException.class);
|
assertThat(actualException).isInstanceOf(DuplicateChildNodeNameException.class);
|
||||||
}
|
}
|
||||||
@@ -240,7 +365,7 @@ public class TagsImplTest
|
|||||||
//when
|
//when
|
||||||
final List<Tag> actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock);
|
final List<Tag> actualCreatedTags = objectUnderTest.createTags(tagsToCreate, parametersMock);
|
||||||
|
|
||||||
then(taggingServiceMock).should().createTags(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
then(taggingServiceMock).should().createTags(STORE_REF_WORKSPACE_SPACESSTORE, List.of(TAG_NAME));
|
||||||
final List<Tag> expectedTags = List.of(createTagWithNodeRef(TAG_NAME));
|
final List<Tag> expectedTags = List.of(createTagWithNodeRef(TAG_NAME));
|
||||||
assertThat(actualCreatedTags)
|
assertThat(actualCreatedTags)
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
@@ -269,7 +394,7 @@ public class TagsImplTest
|
|||||||
private static List<Pair<String, NodeRef>> createTagAndNodeRefPairs(final List<String> tagNames)
|
private static List<Pair<String, NodeRef>> createTagAndNodeRefPairs(final List<String> tagNames)
|
||||||
{
|
{
|
||||||
return tagNames.stream()
|
return tagNames.stream()
|
||||||
.map(tagName -> createPair(tagName, new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName))))
|
.map(tagName -> createPair(tagName, new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName))))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +423,7 @@ public class TagsImplTest
|
|||||||
private static Tag createTagWithNodeRef(final String tagName)
|
private static Tag createTagWithNodeRef(final String tagName)
|
||||||
{
|
{
|
||||||
return Tag.builder()
|
return Tag.builder()
|
||||||
.nodeRef(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName)))
|
.nodeRef(new NodeRef(STORE_REF_WORKSPACE_SPACESSTORE, TAG_ID.concat("-").concat(tagName)))
|
||||||
.tag(tagName)
|
.tag(tagName)
|
||||||
.create();
|
.create();
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,666 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Remote API
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2023 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.framework.resource.parameters.where;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.rest.antlr.WhereClauseParser;
|
||||||
|
import org.alfresco.rest.framework.tools.RecognizedParamsExtractor;
|
||||||
|
import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests verifying {@link QueryHelper.QueryResolver} functionality based on {@link BasicQueryWalker}.
|
||||||
|
*/
|
||||||
|
public class QueryResolverTest
|
||||||
|
{
|
||||||
|
private final RecognizedParamsExtractor queryExtractor = new RecognizedParamsExtractor() {};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_equals()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.MATCHES, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHAN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.MATCHES, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.BETWEEN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EXISTS, true)).isFalse();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_greaterThan()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName > testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHAN, false)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_greaterThanOrEquals()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName >= testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHANOREQUALS, false)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_lessThan()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName < testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHAN, false)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_lessThanOrEquals()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName <= testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHANOREQUALS, false)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_between()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName BETWEEN (testValue, testValue2))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.BETWEEN, false)).containsOnly("testValue", "testValue2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_in()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName IN (testValue, testValue2))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.IN, false)).containsOnly("testValue", "testValue2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_matches()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName MATCHES ('*Value'))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.MATCHES, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.MATCHES, false)).containsOnly("*Value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_exists()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(EXISTS (propName))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EXISTS, false)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notEquals()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName=testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, true)).isTrue();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.MATCHES, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHAN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.MATCHES, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.BETWEEN, true)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EXISTS, true)).isFalse();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, true)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notGreaterThan()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName > testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHAN, true)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notGreaterThanOrEquals()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName >= testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHANOREQUALS, true)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notLessThan()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName < testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHAN, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHAN, true)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notLessThanOrEquals()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName <= testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHANOREQUALS, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHANOREQUALS, true)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notBetween()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName BETWEEN (testValue, testValue2))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.BETWEEN, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.BETWEEN, true)).containsOnly("testValue", "testValue2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notIn()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName IN (testValue, testValue2))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.IN, true)).containsOnly("testValue", "testValue2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notMatches()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName MATCHES ('*Value'))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.MATCHES, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.MATCHES, true)).containsOnly("*Value");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_notExists()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT EXISTS (propName))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EXISTS, true)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EXISTS, true)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_propertyNotExpected()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue AND differentName>18)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("differentName"));
|
||||||
|
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_propertyNotExpectedUsingLenientApproach()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue AND differentName>18)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).leniently().getProperty("differentName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, true)).isFalse();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).isNull();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, true)).isNull();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHAN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHAN, false)).containsOnly("18");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_propertyNotPresentUsingLenientApproach()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("differentName"));
|
||||||
|
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_slashInPropertyName()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(EXISTS (prop/name/with/slashes))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("prop/name/with/slashes");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EXISTS, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EXISTS, false)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_propertyBetweenDates()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName BETWEEN ('2012-01-01', '2012-12-31'))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.BETWEEN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.BETWEEN, false)).containsOnly("2012-01-01", "2012-12-31");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_singlePropertyGreaterThanOrEqualsAndLessThan()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName >= 18 AND propName < 65)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper.resolve(query).getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.GREATERTHANOREQUALS, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.GREATERTHANOREQUALS, false)).containsOnly("18");
|
||||||
|
assertThat(property.containsType(WhereClauseParser.LESSTHAN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.LESSTHAN, false)).containsOnly("65");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_onePropertyGreaterThanAndSecondPropertyNotMatches()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName1 > 20 AND NOT propName2 MATCHES ('external*'))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final List<WhereProperty> property = QueryHelper.resolve(query).getProperties("propName1", "propName2");
|
||||||
|
|
||||||
|
assertThat(property.get(0).containsType(WhereClauseParser.GREATERTHAN, false)).isTrue();
|
||||||
|
assertThat(property.get(0).getExpectedValuesFor(WhereClauseParser.GREATERTHAN, false)).containsOnly("20");
|
||||||
|
assertThat(property.get(1).containsType(WhereClauseParser.MATCHES, true)).isTrue();
|
||||||
|
assertThat(property.get(1).getExpectedValuesFor(WhereClauseParser.MATCHES, true)).containsOnly("external*");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_negationsForbidden()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(NOT propName=testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).withoutNegations().getProperty("propName"));
|
||||||
|
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_withoutNegations()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty actualProperty = QueryHelper.resolve(query).withoutNegations().getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(actualProperty.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||||
|
assertThat(actualProperty.containsType(WhereClauseParser.EQUALS, true)).isFalse();
|
||||||
|
assertThat(actualProperty.getExpectedValuesFor(WhereClauseParser.EQUALS).onlyNegated()).isNull();
|
||||||
|
assertThat(actualProperty.getExpectedValuesFor(WhereClauseParser.EQUALS).skipNegated()).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_orNotAllowed()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName BETWEEN (testValue2, testValue3))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("propName"));
|
||||||
|
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_orAllowedInFavorOfAnd()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName=testValue2)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.usingOrOperator()
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).containsOnly("testValue", "testValue2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_usingCustomQueryWalker()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Collection<String> propertyValues = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.usingWalker(new MapBasedQueryWalker(Set.of("propName"), null))
|
||||||
|
.getProperty("propName", WhereClauseParser.EQUALS, false);
|
||||||
|
|
||||||
|
assertThat(propertyValues).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_usingCustomBasicQueryWalkerExtension()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName=testValue2)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.usingWalker(new BasicQueryWalker("propName")
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void or() {}
|
||||||
|
@Override
|
||||||
|
public void and() {throw UNSUPPORTED;}
|
||||||
|
})
|
||||||
|
.withoutNegations()
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesFor(WhereClauseParser.EQUALS, false)).containsOnly("testValue", "testValue2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_equalsAndInNotAllowedTogether()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName IN (testValue2, testValue3))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Throwable actualException = catchThrowable(() -> QueryHelper.resolve(query).getProperty("propName"));
|
||||||
|
|
||||||
|
assertThat(actualException).isInstanceOf(InvalidQueryException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_equalsOrInAllowedTogether()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue OR propName IN (testValue2, testValue3))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty whereProperty = QueryHelper.resolve(query).usingOrOperator().getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(whereProperty).isNotNull();
|
||||||
|
assertThat(whereProperty.getExpectedValuesForAllOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated())
|
||||||
|
.isEqualTo(Map.of(WhereClauseParser.EQUALS, Set.of("testValue"), WhereClauseParser.IN, Set.of("testValue2", "testValue3")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_equalsAndInAllowedTogetherWithDifferentProperties()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName2 IN (testValue2, testValue3))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final List<WhereProperty> properties = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperties("propName", "propName2");
|
||||||
|
|
||||||
|
assertThat(properties.get(0).containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||||
|
assertThat(properties.get(0).containsType(WhereClauseParser.IN, false)).isFalse();
|
||||||
|
assertThat(properties.get(0).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.EQUALS)).containsOnly("testValue");
|
||||||
|
assertThat(properties.get(0).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.IN)).isFalse();
|
||||||
|
assertThat(properties.get(1).containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||||
|
assertThat(properties.get(1).containsType(WhereClauseParser.IN, false)).isTrue();
|
||||||
|
assertThat(properties.get(1).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.EQUALS)).isFalse();
|
||||||
|
assertThat(properties.get(1).getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.IN)).containsOnly("testValue2", "testValue3");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_equalsAndInAllowedAlternately_equals()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue)");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isTrue();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, false)).isFalse();
|
||||||
|
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.EQUALS)).containsOnly("testValue");
|
||||||
|
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.IN)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_equalsAndInAllowedAlternately_in()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName IN (testValue))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.containsType(WhereClauseParser.EQUALS, false)).isFalse();
|
||||||
|
assertThat(property.containsType(WhereClauseParser.IN, false)).isTrue();
|
||||||
|
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().containsKey(WhereClauseParser.EQUALS)).isFalse();
|
||||||
|
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.IN).skipNegated().get(WhereClauseParser.IN)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_missingEqualsClauseType()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName MATCHES (testValue))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThatExceptionOfType(InvalidQueryException.class)
|
||||||
|
.isThrownBy(() -> property.getExpectedValuesForAllOf(WhereClauseParser.EQUALS, WhereClauseParser.MATCHES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_ignoreUnexpectedClauseType()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName MATCHES (testValue))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.getExpectedValuesForAllOf(WhereClauseParser.EQUALS).skipNegated(WhereClauseParser.EQUALS)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_complexAndQuery()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(a=v1 AND b>18 AND b<=65 AND NOT c BETWEEN ('2012-01-01','2012-12-31') AND d IN (v1, v2) AND e MATCHES ('*@mail.com') AND EXISTS (f/g))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final List<WhereProperty> properties = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperties("a", "b", "c", "d", "e", "f/g");
|
||||||
|
|
||||||
|
assertThat(properties).hasSize(6);
|
||||||
|
assertThat(properties.get(0).getExpectedValuesFor(WhereProperty.ClauseType.EQUALS)).containsOnly("v1");
|
||||||
|
assertThat(properties.get(1).containsAllTypes(WhereProperty.ClauseType.GREATER_THAN, WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).isTrue();
|
||||||
|
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.GREATER_THAN)).containsOnly("18");
|
||||||
|
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).containsOnly("65");
|
||||||
|
assertThat(properties.get(2).getExpectedValuesFor(WhereProperty.ClauseType.NOT_BETWEEN)).containsOnly("2012-01-01", "2012-12-31");
|
||||||
|
assertThat(properties.get(3).getExpectedValuesFor(WhereProperty.ClauseType.IN)).containsOnly("v1", "v2");
|
||||||
|
assertThat(properties.get(4).getExpectedValuesFor(WhereProperty.ClauseType.MATCHES)).containsOnly("*@mail.com");
|
||||||
|
assertThat(properties.get(5).containsType(WhereProperty.ClauseType.EXISTS)).isTrue();
|
||||||
|
assertThat(properties.get(5).getExpectedValuesFor(WhereProperty.ClauseType.EXISTS)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_complexOrQuery()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(a=v1 OR b>18 OR b<=65 OR NOT c BETWEEN ('2012-01-01','2012-12-31') OR d IN (v1, v2) OR e MATCHES ('*@mail.com') OR EXISTS (f/g))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final List<WhereProperty> properties = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.usingOrOperator()
|
||||||
|
.getProperties("a", "b", "c", "d", "e", "f/g");
|
||||||
|
|
||||||
|
assertThat(properties).hasSize(6);
|
||||||
|
assertThat(properties.get(0).getExpectedValuesFor(WhereProperty.ClauseType.EQUALS)).containsOnly("v1");
|
||||||
|
assertThat(properties.get(1).containsAllTypes(WhereProperty.ClauseType.GREATER_THAN, WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).isTrue();
|
||||||
|
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.GREATER_THAN)).containsOnly("18");
|
||||||
|
assertThat(properties.get(1).getExpectedValuesFor(WhereProperty.ClauseType.LESS_THAN_OR_EQUALS)).containsOnly("65");
|
||||||
|
assertThat(properties.get(2).getExpectedValuesFor(WhereProperty.ClauseType.NOT_BETWEEN)).containsOnly("2012-01-01", "2012-12-31");
|
||||||
|
assertThat(properties.get(3).getExpectedValuesFor(WhereProperty.ClauseType.IN)).containsOnly("v1", "v2");
|
||||||
|
assertThat(properties.get(4).getExpectedValuesFor(WhereProperty.ClauseType.MATCHES)).containsOnly("*@mail.com");
|
||||||
|
assertThat(properties.get(5).containsType(WhereProperty.ClauseType.EXISTS)).isTrue();
|
||||||
|
assertThat(properties.get(5).getExpectedValuesFor(WhereProperty.ClauseType.EXISTS)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_clauseTypeOptional()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName MATCHES (testValue))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThat(property.getExpectedValuesForAnyOf(WhereClauseParser.EQUALS, WhereClauseParser.MATCHES).skipNegated(WhereClauseParser.MATCHES)).containsOnly("testValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_optionalClauseTypesNotPresent()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName=testValue AND propName MATCHES (testValue))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final WhereProperty property = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.getProperty("propName");
|
||||||
|
|
||||||
|
assertThatExceptionOfType(InvalidQueryException.class)
|
||||||
|
.isThrownBy(() -> property.getExpectedValuesForAnyOf(WhereClauseParser.IN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResolveQuery_matchesOrMatchesAllowed()
|
||||||
|
{
|
||||||
|
final Query query = queryExtractor.getWhereClause("(propName MATCHES ('test*') OR propName MATCHES ('*value*'))");
|
||||||
|
|
||||||
|
//when
|
||||||
|
final Collection<String> expectedValues = QueryHelper
|
||||||
|
.resolve(query)
|
||||||
|
.usingOrOperator()
|
||||||
|
.getProperty("propName")
|
||||||
|
.getExpectedValuesFor(WhereClauseParser.MATCHES)
|
||||||
|
.skipNegated();
|
||||||
|
|
||||||
|
assertThat(expectedValues).containsOnly("test*", "*value*");
|
||||||
|
}
|
||||||
|
}
|
@@ -25,8 +25,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.search.impl;
|
package org.alfresco.repo.search.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -35,10 +37,14 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.query.CannedQueryPageDetails;
|
import org.alfresco.query.CannedQueryPageDetails;
|
||||||
|
import org.alfresco.query.ListBackedPagingResults;
|
||||||
import org.alfresco.query.PagingRequest;
|
import org.alfresco.query.PagingRequest;
|
||||||
import org.alfresco.query.PagingResults;
|
import org.alfresco.query.PagingResults;
|
||||||
import org.alfresco.repo.search.IndexerAndSearcher;
|
import org.alfresco.repo.search.IndexerAndSearcher;
|
||||||
@@ -64,8 +70,10 @@ import org.alfresco.service.cmr.search.SearchParameters;
|
|||||||
import org.alfresco.service.cmr.search.SearchService;
|
import org.alfresco.service.cmr.search.SearchService;
|
||||||
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
import org.alfresco.service.namespace.NamespacePrefixResolver;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||||
import org.alfresco.util.ISO9075;
|
import org.alfresco.util.ISO9075;
|
||||||
import org.alfresco.util.Pair;
|
import org.alfresco.util.Pair;
|
||||||
|
import org.alfresco.util.collections.Function;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +176,7 @@ public abstract class AbstractCategoryServiceImpl implements CategoryService
|
|||||||
|
|
||||||
public Collection<ChildAssociationRef> getChildren(NodeRef categoryRef, Mode mode, Depth depth)
|
public Collection<ChildAssociationRef> getChildren(NodeRef categoryRef, Mode mode, Depth depth)
|
||||||
{
|
{
|
||||||
return getChildren(categoryRef, mode, depth, false, null, queryFetchSize);
|
return getChildren(categoryRef, mode, depth, false, (Collection<String>) null, queryFetchSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<ChildAssociationRef> getChildren(NodeRef categoryRef, Mode mode, Depth depth, String filter)
|
public Collection<ChildAssociationRef> getChildren(NodeRef categoryRef, Mode mode, Depth depth, String filter)
|
||||||
@@ -177,6 +185,12 @@ public abstract class AbstractCategoryServiceImpl implements CategoryService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Collection<ChildAssociationRef> getChildren(NodeRef categoryRef, Mode mode, Depth depth, boolean sortByName, String filter, int fetchSize)
|
private Collection<ChildAssociationRef> getChildren(NodeRef categoryRef, Mode mode, Depth depth, boolean sortByName, String filter, int fetchSize)
|
||||||
|
{
|
||||||
|
Collection<String> matchingFilter = Optional.ofNullable(filter).map(f -> "*".concat(f).concat("*")).map(Set::of).orElse(null);
|
||||||
|
return getChildren(categoryRef, mode, depth, sortByName, matchingFilter, fetchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<ChildAssociationRef> getChildren(NodeRef categoryRef, Mode mode, Depth depth, boolean sortByName, Collection<String> namesFilter, int fetchSize)
|
||||||
{
|
{
|
||||||
if (categoryRef == null)
|
if (categoryRef == null)
|
||||||
{
|
{
|
||||||
@@ -218,12 +232,14 @@ public abstract class AbstractCategoryServiceImpl implements CategoryService
|
|||||||
luceneQuery.append("/");
|
luceneQuery.append("/");
|
||||||
}
|
}
|
||||||
luceneQuery.append("*").append("\" ");
|
luceneQuery.append("*").append("\" ");
|
||||||
luceneQuery.append("+TYPE:\"" + ContentModel.TYPE_CATEGORY.toString() + "\"");
|
luceneQuery.append("+TYPE:\"" + ContentModel.TYPE_CATEGORY + "\"");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (filter != null)
|
if (CollectionUtils.isNotEmpty(namesFilter))
|
||||||
{
|
{
|
||||||
luceneQuery.append(" " + "+@cm\\:name:\"*" + filter + "*\"");
|
final StringJoiner filterJoiner = new StringJoiner(" OR ", " +(", ")");
|
||||||
|
namesFilter.forEach(nameFilter -> filterJoiner.add("@cm\\:name:\"" + nameFilter + "\""));
|
||||||
|
luceneQuery.append(filterJoiner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a searcher that will include Categories added in this transaction
|
// Get a searcher that will include Categories added in this transaction
|
||||||
@@ -391,23 +407,58 @@ public abstract class AbstractCategoryServiceImpl implements CategoryService
|
|||||||
|
|
||||||
public PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName)
|
public PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName)
|
||||||
{
|
{
|
||||||
return getRootCategories(storeRef, aspectName, pagingRequest, sortByName, null);
|
return getRootCategories(storeRef, aspectName, pagingRequest, sortByName, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName, String filter)
|
public PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName, String filter)
|
||||||
{
|
{
|
||||||
final List<ChildAssociationRef> assocs = new LinkedList<ChildAssociationRef>();
|
final Collection<String> alikeNamesFilter = Optional.ofNullable(filter).map(f -> "*".concat(f).concat("*")).map(Set::of).orElse(null);
|
||||||
Set<NodeRef> nodeRefs = getClassificationNodes(storeRef, aspectName);
|
return getRootCategories(storeRef, aspectName, pagingRequest, sortByName, null, alikeNamesFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName,
|
||||||
|
Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
|
||||||
|
{
|
||||||
|
final Set<NodeRef> nodeRefs = getClassificationNodes(storeRef, aspectName);
|
||||||
|
final List<ChildAssociationRef> associations = new LinkedList<>();
|
||||||
final int skipCount = pagingRequest.getSkipCount();
|
final int skipCount = pagingRequest.getSkipCount();
|
||||||
final int maxItems = pagingRequest.getMaxItems();
|
final int maxItems = pagingRequest.getMaxItems();
|
||||||
final int size = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? CannedQueryPageDetails.DEFAULT_PAGE_SIZE : skipCount + maxItems);
|
final int size = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? CannedQueryPageDetails.DEFAULT_PAGE_SIZE : skipCount + maxItems);
|
||||||
int count = 0;
|
int count = 0;
|
||||||
boolean moreItems = false;
|
boolean moreItems = false;
|
||||||
|
|
||||||
OUTER: for(NodeRef nodeRef : nodeRefs)
|
final Function<NodeRef, Collection<ChildAssociationRef>> childNodesSupplier = (nodeRef) -> {
|
||||||
|
final Set<ChildAssociationRef> childNodes = new HashSet<>();
|
||||||
|
if (CollectionUtils.isEmpty(exactNamesFilter) && CollectionUtils.isEmpty(alikeNamesFilter))
|
||||||
{
|
{
|
||||||
Collection<ChildAssociationRef> children = getChildren(nodeRef, Mode.SUB_CATEGORIES, Depth.IMMEDIATE, sortByName, filter, skipCount + maxItems + 1);
|
// lookup in DB without filtering
|
||||||
|
childNodes.addAll(nodeService.getChildAssocs(nodeRef, ContentModel.ASSOC_SUBCATEGORIES, RegexQNamePattern.MATCH_ALL));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (CollectionUtils.isNotEmpty(exactNamesFilter))
|
||||||
|
{
|
||||||
|
// lookup in DB filtering by name
|
||||||
|
childNodes.addAll(nodeService.getChildrenByName(nodeRef, ContentModel.ASSOC_SUBCATEGORIES, exactNamesFilter));
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isNotEmpty(alikeNamesFilter))
|
||||||
|
{
|
||||||
|
// lookup using search engin filtering by name
|
||||||
|
childNodes.addAll(getChildren(nodeRef, Mode.SUB_CATEGORIES, Depth.IMMEDIATE, sortByName, alikeNamesFilter, skipCount + maxItems + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<ChildAssociationRef> childNodesStream = childNodes.stream();
|
||||||
|
if (sortByName)
|
||||||
|
{
|
||||||
|
childNodesStream = childNodesStream.sorted(Comparator.comparing(tag -> tag.getQName().getLocalName()));
|
||||||
|
}
|
||||||
|
return childNodesStream.collect(Collectors.toList());
|
||||||
|
};
|
||||||
|
|
||||||
|
OUTER_LOOP: for(NodeRef nodeRef : nodeRefs)
|
||||||
|
{
|
||||||
|
Collection<ChildAssociationRef> children = childNodesSupplier.apply(nodeRef);
|
||||||
for(ChildAssociationRef child : children)
|
for(ChildAssociationRef child : children)
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
@@ -420,40 +471,14 @@ public abstract class AbstractCategoryServiceImpl implements CategoryService
|
|||||||
if(count > size)
|
if(count > size)
|
||||||
{
|
{
|
||||||
moreItems = true;
|
moreItems = true;
|
||||||
break OUTER;
|
break OUTER_LOOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
assocs.add(child);
|
associations.add(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean hasMoreItems = moreItems;
|
return new ListBackedPagingResults<>(associations, moreItems);
|
||||||
return new PagingResults<ChildAssociationRef>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public List<ChildAssociationRef> getPage()
|
|
||||||
{
|
|
||||||
return assocs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasMoreItems()
|
|
||||||
{
|
|
||||||
return hasMoreItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<Integer, Integer> getTotalResultCount()
|
|
||||||
{
|
|
||||||
return new Pair<Integer, Integer>(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getQueryExecutionId()
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName)
|
public Collection<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName)
|
||||||
|
@@ -42,6 +42,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
@@ -914,51 +915,23 @@ public class TaggingServiceImpl implements TaggingService,
|
|||||||
return new EmptyPagingResults<Pair<NodeRef, String>>();
|
return new EmptyPagingResults<Pair<NodeRef, String>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest)
|
||||||
|
{
|
||||||
|
return getTags(storeRef, pagingRequest, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.service.cmr.tagging.TaggingService#getTags(org.alfresco.service.cmr.repository.StoreRef, org.alfresco.query.PagingRequest)
|
* @see org.alfresco.service.cmr.tagging.TaggingService#getTags(org.alfresco.service.cmr.repository.StoreRef, org.alfresco.query.PagingRequest)
|
||||||
*/
|
*/
|
||||||
public PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest)
|
public PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
|
||||||
{
|
{
|
||||||
ParameterCheck.mandatory("storeRef", storeRef);
|
ParameterCheck.mandatory("storeRef", storeRef);
|
||||||
|
|
||||||
PagingResults<ChildAssociationRef> rootCategories = this.categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, pagingRequest, true);
|
PagingResults<ChildAssociationRef> rootCategories = categoryService.getRootCategories(storeRef, ContentModel.ASPECT_TAGGABLE, pagingRequest, true,
|
||||||
final List<Pair<NodeRef, String>> result = new ArrayList<Pair<NodeRef, String>>(rootCategories.getPage().size());
|
exactNamesFilter, alikeNamesFilter);
|
||||||
for (ChildAssociationRef rootCategory : rootCategories.getPage())
|
|
||||||
{
|
|
||||||
String name = (String)this.nodeService.getProperty(rootCategory.getChildRef(), ContentModel.PROP_NAME);
|
|
||||||
result.add(new Pair<NodeRef, String>(rootCategory.getChildRef(), name));
|
|
||||||
}
|
|
||||||
final boolean hasMoreItems = rootCategories.hasMoreItems();
|
|
||||||
final Pair<Integer, Integer> totalResultCount = rootCategories.getTotalResultCount();
|
|
||||||
final String queryExecutionId = rootCategories.getQueryExecutionId();
|
|
||||||
rootCategories = null;
|
|
||||||
|
|
||||||
return new PagingResults<Pair<NodeRef, String>>()
|
return mapPagingResult(rootCategories,
|
||||||
{
|
(childAssociation) -> new Pair<>(childAssociation.getChildRef(), childAssociation.getQName().getLocalName()));
|
||||||
@Override
|
|
||||||
public List<Pair<NodeRef, String>> getPage()
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasMoreItems()
|
|
||||||
{
|
|
||||||
return hasMoreItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<Integer, Integer> getTotalResultCount()
|
|
||||||
{
|
|
||||||
return totalResultCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getQueryExecutionId()
|
|
||||||
{
|
|
||||||
return queryExecutionId;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1600,4 +1573,36 @@ public class TaggingServiceImpl implements TaggingService,
|
|||||||
createTagBehaviour.enable();
|
createTagBehaviour.enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <T, R> PagingResults<R> mapPagingResult(final PagingResults<T> pagingResults, final Function<T, R> mapper)
|
||||||
|
{
|
||||||
|
return new PagingResults<R>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public List<R> getPage()
|
||||||
|
{
|
||||||
|
return pagingResults.getPage().stream()
|
||||||
|
.map(mapper)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMoreItems()
|
||||||
|
{
|
||||||
|
return pagingResults.hasMoreItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<Integer, Integer> getTotalResultCount()
|
||||||
|
{
|
||||||
|
return pagingResults.getTotalResultCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQueryExecutionId()
|
||||||
|
{
|
||||||
|
return pagingResults.getQueryExecutionId();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.alfresco.api.AlfrescoPublicApi;
|
import org.alfresco.api.AlfrescoPublicApi;
|
||||||
|
import org.alfresco.query.EmptyPagingResults;
|
||||||
import org.alfresco.query.PagingRequest;
|
import org.alfresco.query.PagingRequest;
|
||||||
import org.alfresco.query.PagingResults;
|
import org.alfresco.query.PagingResults;
|
||||||
import org.alfresco.service.Auditable;
|
import org.alfresco.service.Auditable;
|
||||||
@@ -136,6 +137,24 @@ public interface CategoryService
|
|||||||
@Auditable(parameters = {"storeRef", "aspectName", "pagingRequest", "sortByName", "filter"})
|
@Auditable(parameters = {"storeRef", "aspectName", "pagingRequest", "sortByName", "filter"})
|
||||||
PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName, String filter);
|
PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName, String filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a paged list of the root categories for an aspect/classification supporting multiple name filters.
|
||||||
|
*
|
||||||
|
* @param storeRef
|
||||||
|
* @param aspectName
|
||||||
|
* @param pagingRequest
|
||||||
|
* @param sortByName
|
||||||
|
* @param exactNamesFilter
|
||||||
|
* @param alikeNamesFilter
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Auditable(parameters = {"storeRef", "aspectName", "pagingRequest", "sortByName", "exactNamesFilter", "alikeNamesFilter"})
|
||||||
|
default PagingResults<ChildAssociationRef> getRootCategories(StoreRef storeRef, QName aspectName, PagingRequest pagingRequest, boolean sortByName,
|
||||||
|
Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
|
||||||
|
{
|
||||||
|
return new EmptyPagingResults<>();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the root categories for an aspect/classification with names that start with filter
|
* Get the root categories for an aspect/classification with names that start with filter
|
||||||
*
|
*
|
||||||
|
@@ -25,10 +25,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.service.cmr.tagging;
|
package org.alfresco.service.cmr.tagging;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.alfresco.api.AlfrescoPublicApi;
|
import org.alfresco.api.AlfrescoPublicApi;
|
||||||
|
import org.alfresco.query.EmptyPagingResults;
|
||||||
import org.alfresco.query.PagingRequest;
|
import org.alfresco.query.PagingRequest;
|
||||||
import org.alfresco.query.PagingResults;
|
import org.alfresco.query.PagingResults;
|
||||||
import org.alfresco.service.Auditable;
|
import org.alfresco.service.Auditable;
|
||||||
@@ -76,6 +78,21 @@ public interface TaggingService
|
|||||||
@NotAuditable
|
@NotAuditable
|
||||||
PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest);
|
PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a paged list of tags filtered by name
|
||||||
|
*
|
||||||
|
* @param storeRef StoreRef
|
||||||
|
* @param pagingRequest PagingRequest
|
||||||
|
* @param exactNamesFilter PagingRequest
|
||||||
|
* @param alikeNamesFilter PagingRequest
|
||||||
|
* @return PagingResults
|
||||||
|
*/
|
||||||
|
@NotAuditable
|
||||||
|
default PagingResults<Pair<NodeRef, String>> getTags(StoreRef storeRef, PagingRequest pagingRequest, Collection<String> exactNamesFilter, Collection<String> alikeNamesFilter)
|
||||||
|
{
|
||||||
|
return new EmptyPagingResults<>();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the tags currently available that match the provided filter.
|
* Get all the tags currently available that match the provided filter.
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user