From a03081c0dea62b4f25e18969e1afa8b893a6464c Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Wed, 27 Jan 2021 19:21:12 +0300 Subject: [PATCH 1/6] utility to sort jcr nodes --- .../commons/sorter/SortNodesOperation.java | 177 ++++++++++++++++++ .../sorter/SortNodesOperationTest.java | 155 +++++++++++++++ .../utilities/sort-nodes/.content.xml | 8 + .../sort-nodes/clientlibs/.content.xml | 25 +++ .../utilities/sort-nodes/clientlibs/js.txt | 3 + .../utilities/sort-nodes/clientlibs/js/app.js | 45 +++++ .../utilities/sort-nodes/sort-nodes.html | 89 +++++++++ .../content/sort-nodes/.content.xml | 8 + .../tools/acs-commons/sort-nodes/.content.xml | 9 + 9 files changed, 519 insertions(+) create mode 100755 bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java create mode 100755 bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java create mode 100755 ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js.txt create mode 100755 ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js/app.js create mode 100755 ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html create mode 100755 ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml create mode 100755 ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/acs-commons/sort-nodes/.content.xml diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java new file mode 100755 index 0000000000..2b391f2566 --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java @@ -0,0 +1,177 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.adobe.acs.commons.sorter; + +import com.day.cq.commons.JcrLabeledResource; +import org.apache.jackrabbit.JcrConstants; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.servlets.post.PostOperation; +import org.apache.sling.servlets.post.PostResponse; +import org.apache.sling.servlets.post.SlingPostProcessor; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletResponse; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static org.apache.sling.servlets.post.PostOperation.PROP_OPERATION_NAME; + +/** + * The SortNodesOperation class implements the + * {@link #OPERATION_SORT} operation for the Sling POST servlet. + */ +@Component(property = { + PROP_OPERATION_NAME + "=" + SortNodesOperation.OPERATION_SORT +}) +public class SortNodesOperation implements PostOperation { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + /** + * Name of the sort operation + */ + public static final String OPERATION_SORT = "sort"; + + /** + * Name of the request parameter indicating whether to sort nodes by jcr:title + * + * If this request parameter is missing then nodes will be sorted by node name. + */ + public static final String RP_SORT_BY_TITLE = ":byTitle"; + + /** + * Name of the request parameter indicating whether the sort should be case sensitive + * + * If this request parameter is missing then the sort will be case insensitive + */ + public static final String RP_CASE_SENSITIVE = ":caseSensitive"; + + @Override + public void run(SlingHttpServletRequest slingRequest, PostResponse response, SlingPostProcessor[] processors) { + try { + long t0 = System.currentTimeMillis(); + boolean byTitle = Boolean.parseBoolean(slingRequest.getParameter(RP_SORT_BY_TITLE)); + boolean caseSensitive = Boolean.parseBoolean(slingRequest.getParameter(RP_CASE_SENSITIVE)); + + Node targetNode = slingRequest.getResource().adaptTo(Node.class); + if (targetNode == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND,"Missing target node to sort: " + slingRequest.getResource().getPath()); + return; + } + response.setLocation(targetNode.getPath()); + + Comparator comparator = createComparator(byTitle, caseSensitive); + List children = getSortedNodes(targetNode, comparator); + + Node prev = null; + for (int i = 0; i < children.size(); i++) { + Node n = children.get(children.size() - 1 - i); + if (prev != null) { + log.trace("orderBefore: {}, {}", n.getName(), prev.getName()); + + targetNode.orderBefore(n.getName(), prev.getName()); + } + response.onChange("ordered", n.getPath(), prev == null ? "" : prev.getName()); + prev = n; + } + targetNode.getSession().save(); + + response.setTitle("Content sorted " + targetNode.getPath()); + + log.info("all done, " + children.size() + " nodes sorted in " + (System.currentTimeMillis()-t0) + " ms"); + } catch (RepositoryException e) { + response.setError(e); + } + } + + /** + * Collect child nodes and sort them using the supplied Comparator + * + * @param node the node who's children need to be sorted + * @param comparator the comparator to sort nodes + * @return list of sorted nodes + * @throws RepositoryException if something went wrong + */ + List getSortedNodes(Node node, Comparator comparator) throws RepositoryException { + List children = new ArrayList<>(); + NodeIterator it = node.getNodes(); + while (it.hasNext()) { + Node child = it.nextNode(); + children.add(child); + } + + children.sort(comparator); + return children; + } + + /** + * Create a comparator to sort nodes + * + * @param byTitle whether to sort by jcr:title. If false then sort by node name. + * @param caseSensitive whether comparison should be case-sensitive + * @return comparator + */ + static Comparator createComparator(boolean byTitle, boolean caseSensitive) { + + Comparator comparator = (n1, n2) -> { + try { + return Boolean.compare( + n1.isNodeType(JcrConstants.NT_HIERARCHYNODE), + n2.isNodeType(JcrConstants.NT_HIERARCHYNODE)); + } catch (RepositoryException e) { + return 0; + } + }; + + if (byTitle) { + comparator = comparator.thenComparing((n1, n2) -> { + try { + String title1 = new JcrLabeledResource(n1).getTitle(); + if (title1 == null) title1 = n1.getName(); + String title2 = new JcrLabeledResource(n2).getTitle(); + if (title2 == null) title2 = n2.getName(); + return caseSensitive ? title1.compareTo(title2) : + title1.compareToIgnoreCase(title2); + } catch (RepositoryException e) { + return 0; + } + }); + } else { + comparator = comparator.thenComparing((n1, n2) -> { + try { + String name1 = n1.getName(); + String name2 = n2.getName(); + return caseSensitive ? name1.compareTo(name2) : + name1.compareToIgnoreCase(name2); + } catch (RepositoryException e) { + return 0; + } + }); + } + return comparator; + } + +} diff --git a/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java new file mode 100755 index 0000000000..2c89125150 --- /dev/null +++ b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java @@ -0,0 +1,155 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.adobe.acs.commons.sorter; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.servlets.post.PostResponse; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Spy; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class SortNodesOperationTest { + @Rule + public final SlingContext context = new SlingContext(ResourceResolverType.JCR_OAK); + + @Spy + private SortNodesOperation sorter = new SortNodesOperation(); + + @Before + public void setUp(){ + context.build() + .resource("/sortable", JCR_PRIMARYTYPE, "cq:Page") + .resource("/sortable/page2", JCR_PRIMARYTYPE, "cq:Page") + .resource("/sortable/page2/jcr:content", JCR_PRIMARYTYPE, "cq:PageContent", "jcr:title", "A: Page-2") + .resource("/sortable/jcr:content", JCR_PRIMARYTYPE, "nt:unstructured") + .resource("/sortable/page1", JCR_PRIMARYTYPE, "cq:Page") + .resource("/sortable/Page3/jcr:content", JCR_PRIMARYTYPE, "cq:PageContent", "jcr:title", "C: Page-3") + .resource("/sortable/Page3", JCR_PRIMARYTYPE, "cq:Page") + .resource("/sortable/page1/jcr:content", JCR_PRIMARYTYPE, "cq:PageContent", "jcr:title", "a: Page-1") + .resource("/sortable/rep:policy", JCR_PRIMARYTYPE, "rep:ACL"); + } + + /** + * by node name, case insensitive + */ + @Test + public void testSortByNameCaseInsensitive() throws RepositoryException { + Comparator comparator = SortNodesOperation.createComparator(false, false); + Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); + List list = sorter.getSortedNodes(node, comparator); + + assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page1", "page2", "Page3"), list); + + } + + /** + * by node name, case sensitive + */ + @Test + public void testSortByNameCaseSensitive() throws RepositoryException { + Comparator comparator = SortNodesOperation.createComparator(false, true); + Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); + List list = sorter.getSortedNodes(node, comparator); + + assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "Page3", "page1", "page2"), list); + + } + + /** + * by title, case insensitive + */ + @Test + public void testSortByTitleCaseInsensitive() throws RepositoryException { + Comparator comparator = SortNodesOperation.createComparator(true, false); + Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); + List list = sorter.getSortedNodes(node, comparator); + + assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page1" /*a: Page-1*/, "page2" /*A: Page-2*/, "Page3" /*C: Page-3*/), list); + + } + + /** + * by title, case sensitive + */ + @Test + public void testSortByTitleCaseSensitive() throws RepositoryException { + Comparator comparator = SortNodesOperation.createComparator(true, true); + Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); + List list = sorter.getSortedNodes(node, comparator); + + assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page2" /*A: Page-2*/, "Page3" /*C: Page-3*/, "page1" /*a: Page-1*/), list); + } + + @Test + public void testDefaultRequestParameters() throws RepositoryException { + Resource resource = context.resourceResolver().getResource("/sortable"); + MockSlingHttpServletRequest request = context.request(); + request.setResource(resource); + sorter.run(context.request(), mock(PostResponse.class), null); + + List children = new ArrayList<>(); + resource.getChildren().forEach(r -> children.add(r.adaptTo(Node.class))); + + assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page1", "page2", "Page3"), children); + } + + @Test + public void testRequestParameters() throws RepositoryException { + Resource resource = context.resourceResolver().getResource("/sortable"); + MockSlingHttpServletRequest request = context.request(); + request.addRequestParameter(SortNodesOperation.RP_SORT_BY_TITLE, "true"); + request.addRequestParameter(SortNodesOperation.RP_CASE_SENSITIVE, "true"); + request.setResource(resource); + sorter.run(context.request(), mock(PostResponse.class), null); + + List children = new ArrayList<>(); + resource.getChildren().forEach(r -> children.add(r.adaptTo(Node.class))); + + assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page2" /*A: Page-2*/, "Page3" /*C: Page-3*/, "page1" /*a: Page-1*/), children); + } + + private static void assertSortOrder(List expected, List list){ + List actual = list.stream().map(n -> { + try { + return n.getName(); + } catch (RepositoryException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + assertEquals(expected, actual); + + } + } diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/.content.xml b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/.content.xml new file mode 100755 index 0000000000..7290708270 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/.content.xml @@ -0,0 +1,8 @@ + + diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/.content.xml b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/.content.xml new file mode 100755 index 0000000000..e5b1de9e76 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/.content.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js.txt b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js.txt new file mode 100755 index 0000000000..941b3e5069 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js.txt @@ -0,0 +1,3 @@ +#base=js + +app.js diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js/app.js b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js/app.js new file mode 100755 index 0000000000..f83a678eda --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/clientlibs/js/app.js @@ -0,0 +1,45 @@ +(function (window, document, $) { + "use strict"; + + $(document).on("click", ".sort-nodes-button", function (e) { + e.preventDefault(); + var form = $(this).closest('form'); + var data = form.serialize(); + var pathInput = $(form).find(".js-coral-pathbrowser-input"); + var path = pathInput[0].value; + if(!path){ + pathInput.attr("required", "true"); + return; + } + $.ajax({ + url: path, + type: "POST", + data: data + }).success(function(response){ + var changes = response.changes; + var html = changes.reverse().map(function(c){ return "
  • " + c.argument[0] + "
  • "; } ).join(""); + + var dialog = new Coral.Dialog(); + dialog.id = 'dialogSuccess'; + dialog.header.innerHTML = 'Success'; + dialog.content.innerHTML = html; + dialog.footer.innerHTML = ''; + dialog.variant = 'success'; + dialog.closable = "on"; + dialog.show(); + + }).error(function(response){ + var dialog = new Coral.Dialog(); + dialog.id = 'dialogFailure'; + dialog.header.innerHTML = 'Failure'; + dialog.content.innerHTML = response.responseJSON["status.message"]; + dialog.footer.innerHTML = ''; + dialog.variant = 'error'; + dialog.closable = "on"; + dialog.show(); + }); + + + }); + +})(window, document, $); diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html new file mode 100755 index 0000000000..b80c95d0da --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + Adobe Experience Manager + + + / ACS AEM Commons / ${properties.jcr:title} + + + + +
    +
    + + + ${properties.jcr:title} + + + + + +
    +
    + + + +
    + + +
    +
    + + + + + + + + + +
    + +
    + + Sort By Title + +
    +
    + + Case Sensitive + +
    + +
    + + + + +
    +
    +
    + diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml b/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml new file mode 100755 index 0000000000..040187ce9c --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml @@ -0,0 +1,8 @@ + + + + diff --git a/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/acs-commons/sort-nodes/.content.xml b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/acs-commons/sort-nodes/.content.xml new file mode 100755 index 0000000000..05ee0c2be3 --- /dev/null +++ b/ui.apps/src/main/content/jcr_root/apps/cq/core/content/nav/tools/acs-commons/sort-nodes/.content.xml @@ -0,0 +1,9 @@ + + From f428bdcd572ab1e0f7d5dcaf4337c0da0b76143c Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Thu, 28 Jan 2021 18:01:45 +0300 Subject: [PATCH 2/6] utility to sort jcr nodes --- .../commons/sorter/SortNodesOperation.java | 85 +++++++++++++++---- .../sorter/SortNodesOperationTest.java | 63 ++++++++++++-- .../utilities/sort-nodes/sort-nodes.html | 24 +++--- .../content/sort-nodes/.content.xml | 2 +- 4 files changed, 138 insertions(+), 36 deletions(-) diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java index 2b391f2566..18662bc0a2 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java @@ -41,8 +41,21 @@ import static org.apache.sling.servlets.post.PostOperation.PROP_OPERATION_NAME; /** - * The SortNodesOperation class implements the - * {@link #OPERATION_SORT} operation for the Sling POST servlet. + * The SortNodesOperation class implements the {@link #OPERATION_SORT} operation for the Sling POST servlet. + *

    + * Use this operation to alphabetize JCR nodes. For example, the following command line sorts the children of the /content/sample page: + *

    + *     curl -u admin:admin -F":operation=acs-commons:sortNodes" http://localhost:4502/content/sample
    + * 
    + *

    + * + * You can use optional {@link #RP_CASE_SENSITIVE} and {@link #RP_SORT_BY_TITLE} parameters to control whether to sort by + * by node name (default) or by jcr:title and whether the sort should be case-sensitive: + *
    + *     curl -u admin:admin -F":operation=acs-commons:sortNodes" \
    + *     -F":byTitle:true" -F":caseSensitive:true" \
    + *     http://localhost:4502/content/someFolder
    + * 
    */ @Component(property = { PROP_OPERATION_NAME + "=" + SortNodesOperation.OPERATION_SORT @@ -51,9 +64,10 @@ public class SortNodesOperation implements PostOperation { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); /** - * Name of the sort operation + * Name of the sort operation. + * The acs-commons: prefix is to void name clash with other PostOperations . */ - public static final String OPERATION_SORT = "sort"; + public static final String OPERATION_SORT = "acs-commons:sortNodes"; /** * Name of the request parameter indicating whether to sort nodes by jcr:title @@ -69,21 +83,51 @@ public class SortNodesOperation implements PostOperation { */ public static final String RP_CASE_SENSITIVE = ":caseSensitive"; + /** + * Name of the request parameter indicating whether the sort should move non-hierarchy nodes to the top + * + * The default value is true which means jcr:content, rep:policy and such will be sorted first by their node names + * followed by other,hierarchy nodes, e.g : + *
    +     *   +  /parent
    +     *      -  jcr:content
    +     *      -  rep:policy
    +     *      -  a
    +     *      -  b
    +     *      -  c
    +     *      -  p
    +     * 
    + * If user forces it to false then nodes will be sorted regardless of their hierarchy type: + *
    +     *   +  /parent
    +     *      -  a
    +     *      -  b
    +     *      -  c
    +     *      -  jcr:content
    +     *      -  p
    +     *      -  rep:policy
    +     * 
    + */ + public static final String RP_NOT_HIERARCHY_FIRST = ":nonHierarchyFirst"; + @Override public void run(SlingHttpServletRequest slingRequest, PostResponse response, SlingPostProcessor[] processors) { try { long t0 = System.currentTimeMillis(); boolean byTitle = Boolean.parseBoolean(slingRequest.getParameter(RP_SORT_BY_TITLE)); boolean caseSensitive = Boolean.parseBoolean(slingRequest.getParameter(RP_CASE_SENSITIVE)); + boolean nonHierarchyFirst = slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST) == null || Boolean.parseBoolean(slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST)); Node targetNode = slingRequest.getResource().adaptTo(Node.class); if (targetNode == null) { response.setStatus(HttpServletResponse.SC_NOT_FOUND,"Missing target node to sort: " + slingRequest.getResource().getPath()); return; } + response.setPath(targetNode.getPath()); response.setLocation(targetNode.getPath()); + if(targetNode.getParent() != null) response.setParentLocation(targetNode.getParent().getPath()); - Comparator comparator = createComparator(byTitle, caseSensitive); + Comparator comparator = createComparator(nonHierarchyFirst, byTitle, caseSensitive); List children = getSortedNodes(targetNode, comparator); Node prev = null; @@ -99,9 +143,9 @@ public void run(SlingHttpServletRequest slingRequest, PostResponse response, Sli } targetNode.getSession().save(); - response.setTitle("Content sorted " + targetNode.getPath()); + response.setTitle("Content sorted " + response.getPath()); - log.info("all done, " + children.size() + " nodes sorted in " + (System.currentTimeMillis()-t0) + " ms"); + log.info("{} nodes sorted in {} ms", children.size(), System.currentTimeMillis()-t0 ); } catch (RepositoryException e) { response.setError(e); } @@ -130,21 +174,26 @@ List getSortedNodes(Node node, Comparator comparator) throws Reposit /** * Create a comparator to sort nodes * + * @param nonHierarchyFirst whether non-hierarchy nodes should go first * @param byTitle whether to sort by jcr:title. If false then sort by node name. * @param caseSensitive whether comparison should be case-sensitive * @return comparator */ - static Comparator createComparator(boolean byTitle, boolean caseSensitive) { - - Comparator comparator = (n1, n2) -> { - try { - return Boolean.compare( - n1.isNodeType(JcrConstants.NT_HIERARCHYNODE), - n2.isNodeType(JcrConstants.NT_HIERARCHYNODE)); - } catch (RepositoryException e) { - return 0; - } - }; + static Comparator createComparator(boolean nonHierarchyFirst, boolean byTitle, boolean caseSensitive) { + + Comparator comparator = (n1, n2) -> 0; + + if(nonHierarchyFirst){ + comparator = comparator.thenComparing((n1, n2) -> { + try { + return Boolean.compare( + n1.isNodeType(JcrConstants.NT_HIERARCHYNODE), + n2.isNodeType(JcrConstants.NT_HIERARCHYNODE)); + } catch (RepositoryException e) { + return 0; + } + }); + } if (byTitle) { comparator = comparator.thenComparing((n1, n2) -> { diff --git a/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java index 2c89125150..2b05e33cc9 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java @@ -19,6 +19,7 @@ */ package com.adobe.acs.commons.sorter; +import org.apache.sling.api.resource.NonExistingResource; import org.apache.sling.api.resource.Resource; import org.apache.sling.servlets.post.PostResponse; import org.apache.sling.testing.mock.sling.ResourceResolverType; @@ -27,10 +28,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Spy; import javax.jcr.Node; import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -40,6 +43,7 @@ import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class SortNodesOperationTest { @Rule @@ -67,7 +71,7 @@ public void setUp(){ */ @Test public void testSortByNameCaseInsensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(false, false); + Comparator comparator = SortNodesOperation.createComparator(true,false, false); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -75,12 +79,25 @@ public void testSortByNameCaseInsensitive() throws RepositoryException { } + /** + * don't move non-hierarchy nodes to the top, sort them along with other nodes + */ + @Test + public void testDisableNonHierarchyFirst() throws RepositoryException { + Comparator comparator = SortNodesOperation.createComparator(false,false, false); + Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); + List list = sorter.getSortedNodes(node, comparator); + + assertSortOrder(Arrays.asList("jcr:content", "page1", "page2", "Page3", "rep:policy"), list); + + } + /** * by node name, case sensitive */ @Test public void testSortByNameCaseSensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(false, true); + Comparator comparator = SortNodesOperation.createComparator(true,false, true); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -93,7 +110,7 @@ public void testSortByNameCaseSensitive() throws RepositoryException { */ @Test public void testSortByTitleCaseInsensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(true, false); + Comparator comparator = SortNodesOperation.createComparator(true,true, false); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -106,7 +123,7 @@ public void testSortByTitleCaseInsensitive() throws RepositoryException { */ @Test public void testSortByTitleCaseSensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(true, true); + Comparator comparator = SortNodesOperation.createComparator(true,true, true); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -114,7 +131,7 @@ public void testSortByTitleCaseSensitive() throws RepositoryException { } @Test - public void testDefaultRequestParameters() throws RepositoryException { + public void testDefaultRequestParameters() { Resource resource = context.resourceResolver().getResource("/sortable"); MockSlingHttpServletRequest request = context.request(); request.setResource(resource); @@ -127,7 +144,7 @@ public void testDefaultRequestParameters() throws RepositoryException { } @Test - public void testRequestParameters() throws RepositoryException { + public void testRequestParameters() { Resource resource = context.resourceResolver().getResource("/sortable"); MockSlingHttpServletRequest request = context.request(); request.addRequestParameter(SortNodesOperation.RP_SORT_BY_TITLE, "true"); @@ -141,6 +158,40 @@ public void testRequestParameters() throws RepositoryException { assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page2" /*A: Page-2*/, "Page3" /*C: Page-3*/, "page1" /*a: Page-1*/), children); } + @Test + public void testInvalidTargetResource() { + String targetPath = "/unknown"; + PostResponse response = mock(PostResponse.class); + MockSlingHttpServletRequest request = context.request(); + request.setResource(new NonExistingResource(context.resourceResolver(), targetPath)); + ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(Integer.class); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + sorter.run(request, response, null); + + verify(response).setStatus(statusCaptor.capture(), msgCaptor.capture()); + assertEquals(HttpServletResponse.SC_NOT_FOUND, (int)statusCaptor.getValue()); + assertEquals("Missing target node to sort: " + targetPath, msgCaptor.getValue()); + } + + @Test + public void testNotSortableTarget() { + String targetPath = "/invalid"; + context.build() + .resource(targetPath, JCR_PRIMARYTYPE, "sling:Folder") + .resource(targetPath + "/node1", JCR_PRIMARYTYPE, "sling:Folder") + .resource(targetPath + "/node1", JCR_PRIMARYTYPE, "sling:Folder") + .resource(targetPath + "/jcr:content", JCR_PRIMARYTYPE, "nt:unstructured"); + PostResponse response = mock(PostResponse.class); + MockSlingHttpServletRequest request = context.request(); + request.setResource(context.resourceResolver().getResource(targetPath)); + ArgumentCaptor errCaptor = ArgumentCaptor.forClass(Throwable.class); + sorter.run(request, response, null); + + verify(response).setError(errCaptor.capture()); + assertEquals("Child node ordering is not supported on this node", + errCaptor.getValue().getMessage()); + } + private static void assertSortOrder(List expected, List list){ List actual = list.stream().map(n -> { try { diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html index b80c95d0da..dc70c5ddd6 100755 --- a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html @@ -44,29 +44,31 @@
    - +
    - - - - - - - + data-picker-src="/libs/wcm/core/content/common/pathbrowser/column.html/content?predicate=hierarchyNotFile" + data-crumb-root="content" data-picker-multiselect="false" data-root-path-valid-selection="true"> + + + + + + +
    - Sort By Title + Sort By Title. If unchecked nodes will be sorted by name
    - Case Sensitive + Case Sensitive Sort
    diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml b/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml index 040187ce9c..2337cf7fef 100755 --- a/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/content/sort-nodes/.content.xml @@ -3,6 +3,6 @@ jcr:primaryType="cq:Page"> From 352623dee8feeff0ac673f8e57c09528377bd60f Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Sun, 28 Feb 2021 14:59:55 +0300 Subject: [PATCH 3/6] sort-nodes tool: fixing code climate warnings --- .../commons/sorter/SortNodesOperation.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java index 18662bc0a2..3e6fcd8d1a 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java @@ -113,10 +113,10 @@ public class SortNodesOperation implements PostOperation { @Override public void run(SlingHttpServletRequest slingRequest, PostResponse response, SlingPostProcessor[] processors) { try { - long t0 = System.currentTimeMillis(); - boolean byTitle = Boolean.parseBoolean(slingRequest.getParameter(RP_SORT_BY_TITLE)); - boolean caseSensitive = Boolean.parseBoolean(slingRequest.getParameter(RP_CASE_SENSITIVE)); - boolean nonHierarchyFirst = slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST) == null || Boolean.parseBoolean(slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST)); + final long t0 = System.currentTimeMillis(); + final boolean byTitle = Boolean.parseBoolean(slingRequest.getParameter(RP_SORT_BY_TITLE)); + final boolean caseSensitive = Boolean.parseBoolean(slingRequest.getParameter(RP_CASE_SENSITIVE)); + final boolean nonHierarchyFirst = slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST) == null || Boolean.parseBoolean(slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST)); Node targetNode = slingRequest.getResource().adaptTo(Node.class); if (targetNode == null) { @@ -125,7 +125,9 @@ public void run(SlingHttpServletRequest slingRequest, PostResponse response, Sli } response.setPath(targetNode.getPath()); response.setLocation(targetNode.getPath()); - if(targetNode.getParent() != null) response.setParentLocation(targetNode.getParent().getPath()); + if(targetNode.getParent() != null) { + response.setParentLocation(targetNode.getParent().getPath()); + } Comparator comparator = createComparator(nonHierarchyFirst, byTitle, caseSensitive); List children = getSortedNodes(targetNode, comparator); @@ -199,9 +201,13 @@ static Comparator createComparator(boolean nonHierarchyFirst, boolean byTi comparator = comparator.thenComparing((n1, n2) -> { try { String title1 = new JcrLabeledResource(n1).getTitle(); - if (title1 == null) title1 = n1.getName(); + if (title1 == null) { + title1 = n1.getName(); + } String title2 = new JcrLabeledResource(n2).getTitle(); - if (title2 == null) title2 = n2.getName(); + if (title2 == null) { + title2 = n2.getName(); + } return caseSensitive ? title1.compareTo(title2) : title1.compareToIgnoreCase(title2); } catch (RepositoryException e) { From c962d3f6dabe3405c3cb97cada1b8fa0abdac59c Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Sat, 6 Mar 2021 17:37:24 +0300 Subject: [PATCH 4/6] sort-nodes: support for registering custom sorters --- .../adobe/acs/commons/sorter/NodeSorter.java | 49 ++++++ .../commons/sorter/SortNodesOperation.java | 143 ++++++------------ .../adobe/acs/commons/sorter/SortersPojo.java | 45 ++++++ .../sorter/impl/HierarchyNodeComparator.java | 45 ++++++ .../commons/sorter/impl/NodeNameSorter.java | 63 ++++++++ .../commons/sorter/impl/NodeTitleSorter.java | 70 +++++++++ .../acs/commons/sorter/package-info.java | 26 ++++ .../sorter/SortNodesOperationTest.java | 56 ++++--- .../utilities/sort-nodes/sort-nodes.html | 54 +++++-- 9 files changed, 416 insertions(+), 135 deletions(-) create mode 100755 bundle/src/main/java/com/adobe/acs/commons/sorter/NodeSorter.java create mode 100755 bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java create mode 100755 bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java create mode 100755 bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java create mode 100755 bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java create mode 100755 bundle/src/main/java/com/adobe/acs/commons/sorter/package-info.java diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/NodeSorter.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/NodeSorter.java new file mode 100755 index 0000000000..1d4c62ee54 --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/NodeSorter.java @@ -0,0 +1,49 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.adobe.acs.commons.sorter; + +import javax.jcr.Node; +import javax.servlet.http.HttpServletRequest; +import java.util.Comparator; + +public interface NodeSorter { + + /** + * The name of a sorter, e.g. 'byTitle' or 'byMyCustomProperty' + * It will be used to find the actual sorter from the {@link SortNodesOperation#RP_SORTER_NAME} + * request parameter + */ + String getName(); + + /** + * Optional label to show in the UI drop-down to select a sorter. + */ + default String getLabel(){ + return getName(); + } + + /** + * Create a comparator to sort nodes. + * Implementations can read additional parameters from request, e.g. + * whether search should be case-sensitive, ascending/descending, etc. + */ + Comparator createComparator(HttpServletRequest request); + + } diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java index 3e6fcd8d1a..e9fd29af72 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java @@ -19,13 +19,15 @@ */ package com.adobe.acs.commons.sorter; -import com.day.cq.commons.JcrLabeledResource; -import org.apache.jackrabbit.JcrConstants; +import com.adobe.acs.commons.sorter.impl.NodeNameSorter; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.servlets.post.PostOperation; import org.apache.sling.servlets.post.PostResponse; import org.apache.sling.servlets.post.SlingPostProcessor; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,8 +37,11 @@ import javax.servlet.http.HttpServletResponse; import java.lang.invoke.MethodHandles; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static org.apache.sling.servlets.post.PostOperation.PROP_OPERATION_NAME; @@ -49,7 +54,7 @@ * *

    * - * You can use optional {@link #RP_CASE_SENSITIVE} and {@link #RP_SORT_BY_TITLE} parameters to control whether to sort by + * You can use optional {@link NodeNameSorter#RP_CASE_SENSITIVE} and {@link #RP_SORTER_NAME} parameters to control whether to sort by * by node name (default) or by jcr:title and whether the sort should be case-sensitive: *
      *     curl -u admin:admin -F":operation=acs-commons:sortNodes" \
    @@ -65,7 +70,7 @@ public class SortNodesOperation implements PostOperation {
     
         /**
          * Name of the sort operation.
    -     * The acs-commons: prefix is to void name clash with other PostOperations .
    +     * The acs-commons: prefix is to avoid name clash with other PostOperations .
          */
         public static final String OPERATION_SORT = "acs-commons:sortNodes";
     
    @@ -74,50 +79,33 @@ public class SortNodesOperation implements PostOperation {
          *
          * If this request parameter is missing then nodes will be sorted by node name.
          */
    -    public static final String RP_SORT_BY_TITLE = ":byTitle";
    +    public static final String RP_SORTER_NAME = ":sorterName";
     
    -    /**
    -     * Name of the request parameter indicating whether the sort  should be case sensitive
    -     *
    -     * If this request parameter is missing then the sort will be case insensitive
    -     */
    -    public static final String RP_CASE_SENSITIVE = ":caseSensitive";
    +    public static final String DEFAULT_SORTER_NAME = NodeNameSorter.SORTER_NAME;
     
    -    /**
    -     * Name of the request parameter indicating whether the sort  should move non-hierarchy nodes to the top
    -     *
    -     * The default value is true which means jcr:content, rep:policy  and such will be sorted first by their node names
    -     * followed by other,hierarchy nodes, e.g :
    -     * 
    -     *   +  /parent
    -     *      -  jcr:content
    -     *      -  rep:policy
    -     *      -  a
    -     *      -  b
    -     *      -  c
    -     *      -  p
    -     * 
    - * If user forces it to false then nodes will be sorted regardless of their hierarchy type: - *
    -     *   +  /parent
    -     *      -  a
    -     *      -  b
    -     *      -  c
    -     *      -  jcr:content
    -     *      -  p
    -     *      -  rep:policy
    -     * 
    - */ - public static final String RP_NOT_HIERARCHY_FIRST = ":nonHierarchyFirst"; + + private final Map nodeSorters = Collections.synchronizedMap(new LinkedHashMap<>()); + + @Reference(service = NodeSorter.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.DYNAMIC) + protected void bindNodeSorter(NodeSorter sorter, Map properties){ + if (sorter != null ) { + String sorterName = sorter.getName(); + log.debug("registering node sorter: {} -> {}", sorterName, sorter.getClass().getName()); + nodeSorters.put(sorterName, sorter); + } + } + + protected void unbindNodeSorter(NodeSorter sorter, Map properties){ + String sorterName = sorter.getName(); + log.debug("un-registering node sorter: {} -> {}", sorterName, sorter.getClass().getName()); + nodeSorters.remove(sorterName); + } @Override public void run(SlingHttpServletRequest slingRequest, PostResponse response, SlingPostProcessor[] processors) { try { - final long t0 = System.currentTimeMillis(); - final boolean byTitle = Boolean.parseBoolean(slingRequest.getParameter(RP_SORT_BY_TITLE)); - final boolean caseSensitive = Boolean.parseBoolean(slingRequest.getParameter(RP_CASE_SENSITIVE)); - final boolean nonHierarchyFirst = slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST) == null || Boolean.parseBoolean(slingRequest.getParameter(RP_NOT_HIERARCHY_FIRST)); - Node targetNode = slingRequest.getResource().adaptTo(Node.class); if (targetNode == null) { response.setStatus(HttpServletResponse.SC_NOT_FOUND,"Missing target node to sort: " + slingRequest.getResource().getPath()); @@ -129,7 +117,8 @@ public void run(SlingHttpServletRequest slingRequest, PostResponse response, Sli response.setParentLocation(targetNode.getParent().getPath()); } - Comparator comparator = createComparator(nonHierarchyFirst, byTitle, caseSensitive); + long t0 = System.currentTimeMillis(); + Comparator comparator = getNodeSorter(slingRequest); List children = getSortedNodes(targetNode, comparator); Node prev = null; @@ -137,18 +126,16 @@ public void run(SlingHttpServletRequest slingRequest, PostResponse response, Sli Node n = children.get(children.size() - 1 - i); if (prev != null) { log.trace("orderBefore: {}, {}", n.getName(), prev.getName()); - targetNode.orderBefore(n.getName(), prev.getName()); } response.onChange("ordered", n.getPath(), prev == null ? "" : prev.getName()); prev = n; } targetNode.getSession().save(); - response.setTitle("Content sorted " + response.getPath()); log.info("{} nodes sorted in {} ms", children.size(), System.currentTimeMillis()-t0 ); - } catch (RepositoryException e) { + } catch (RepositoryException | IllegalArgumentException e) { response.setError(e); } } @@ -162,6 +149,7 @@ public void run(SlingHttpServletRequest slingRequest, PostResponse response, Sli * @throws RepositoryException if something went wrong */ List getSortedNodes(Node node, Comparator comparator) throws RepositoryException { + List children = new ArrayList<>(); NodeIterator it = node.getNodes(); while (it.hasNext()) { @@ -173,60 +161,17 @@ List getSortedNodes(Node node, Comparator comparator) throws Reposit return children; } - /** - * Create a comparator to sort nodes - * - * @param nonHierarchyFirst whether non-hierarchy nodes should go first - * @param byTitle whether to sort by jcr:title. If false then sort by node name. - * @param caseSensitive whether comparison should be case-sensitive - * @return comparator - */ - static Comparator createComparator(boolean nonHierarchyFirst, boolean byTitle, boolean caseSensitive) { - - Comparator comparator = (n1, n2) -> 0; - - if(nonHierarchyFirst){ - comparator = comparator.thenComparing((n1, n2) -> { - try { - return Boolean.compare( - n1.isNodeType(JcrConstants.NT_HIERARCHYNODE), - n2.isNodeType(JcrConstants.NT_HIERARCHYNODE)); - } catch (RepositoryException e) { - return 0; - } - }); + Comparator getNodeSorter(SlingHttpServletRequest slingRequest){ + String sorterId = slingRequest.getParameter(RP_SORTER_NAME); + if(sorterId == null){ + sorterId = DEFAULT_SORTER_NAME; } - - if (byTitle) { - comparator = comparator.thenComparing((n1, n2) -> { - try { - String title1 = new JcrLabeledResource(n1).getTitle(); - if (title1 == null) { - title1 = n1.getName(); - } - String title2 = new JcrLabeledResource(n2).getTitle(); - if (title2 == null) { - title2 = n2.getName(); - } - return caseSensitive ? title1.compareTo(title2) : - title1.compareToIgnoreCase(title2); - } catch (RepositoryException e) { - return 0; - } - }); - } else { - comparator = comparator.thenComparing((n1, n2) -> { - try { - String name1 = n1.getName(); - String name2 = n2.getName(); - return caseSensitive ? name1.compareTo(name2) : - name1.compareToIgnoreCase(name2); - } catch (RepositoryException e) { - return 0; - } - }); + NodeSorter sorter = nodeSorters.get(sorterId); + if(sorter == null){ + String msg = "NodeSorter was not found: " + sorterId + + ". Available sorters are: " + nodeSorters.keySet().toString(); + throw new IllegalArgumentException(msg); } - return comparator; + return sorter.createComparator(slingRequest); } - } diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java new file mode 100755 index 0000000000..a7603888cc --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java @@ -0,0 +1,45 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.adobe.acs.commons.sorter; + +import com.adobe.cq.sightly.WCMUsePojo; +import org.osgi.annotation.versioning.ProviderType; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * Produce a list of available sorters to populate the drop-down on the sort-nodes.html page. + */ +@ProviderType +public class SortersPojo extends WCMUsePojo { + + @Override + public void activate(){ + + } + + public Collection getAvailableSorters(){ + NodeSorter[] sorters = getSlingScriptHelper().getServices(NodeSorter.class, null); + return sorters == null ? Collections.emptyList() : Arrays.asList(sorters); + } + +} diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java new file mode 100755 index 0000000000..b1559215f8 --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java @@ -0,0 +1,45 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.adobe.acs.commons.sorter.impl; + +import org.apache.jackrabbit.JcrConstants; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import java.util.Comparator; + +class HierarchyNodeComparator implements Comparator { + public static final String RP_NOT_HIERARCHY_FIRST = ":nonHierarchyFirst"; + + public static HierarchyNodeComparator INSTANCE = new HierarchyNodeComparator(); + + private HierarchyNodeComparator(){} + + @Override + public int compare(Node n1, Node n2) { + try { + return Boolean.compare( + n1.isNodeType(JcrConstants.NT_HIERARCHYNODE), + n2.isNodeType(JcrConstants.NT_HIERARCHYNODE)); + } catch (RepositoryException e) { + return 0; + } + } +} diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java new file mode 100755 index 0000000000..740a885f09 --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java @@ -0,0 +1,63 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.adobe.acs.commons.sorter.impl; + +import com.adobe.acs.commons.sorter.NodeSorter; +import org.osgi.service.component.annotations.Component; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletRequest; +import java.util.Comparator; + +import static com.adobe.acs.commons.sorter.impl.HierarchyNodeComparator.RP_NOT_HIERARCHY_FIRST; + +@Component +public class NodeNameSorter implements NodeSorter { + public static final String SORTER_NAME = "byName"; + public static final String RP_CASE_SENSITIVE = ":caseSensitive"; + + public String getName(){ + return SORTER_NAME; + } + + public String getLabel(){ + return "By Node Name"; + } + + @Override + public Comparator createComparator(HttpServletRequest request) { + boolean caseSensitive = Boolean.parseBoolean(request.getParameter(RP_CASE_SENSITIVE)); + boolean nonHierarchyFirst = request.getParameter(RP_NOT_HIERARCHY_FIRST) == null || + Boolean.parseBoolean(request.getParameter(RP_NOT_HIERARCHY_FIRST)); + Comparator parentComparator = nonHierarchyFirst ? HierarchyNodeComparator.INSTANCE : (n1, n2) -> 0; + return parentComparator.thenComparing((n1, n2) -> { + try { + String name1 = n1.getName(); + String name2 = n2.getName(); + return caseSensitive ? name1.compareTo(name2) : + name1.compareToIgnoreCase(name2); + } catch (RepositoryException e) { + return 0; + } + }); + } + +} diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java new file mode 100755 index 0000000000..41323dc55f --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java @@ -0,0 +1,70 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.adobe.acs.commons.sorter.impl; + +import com.adobe.acs.commons.sorter.NodeSorter; +import com.day.cq.commons.JcrLabeledResource; +import org.osgi.service.component.annotations.Component; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletRequest; +import java.util.Comparator; + +import static com.adobe.acs.commons.sorter.impl.HierarchyNodeComparator.RP_NOT_HIERARCHY_FIRST; + +@Component +public class NodeTitleSorter implements NodeSorter { + + public static final String SORTER_NAME = "byTitle"; + public static final String RP_CASE_SENSITIVE = ":caseSensitive"; + + public String getName(){ + return SORTER_NAME; + } + + public String getLabel(){ + return "By Node Title"; + } + + @Override + public Comparator createComparator(HttpServletRequest request) { + boolean caseSensitive = Boolean.parseBoolean(request.getParameter(RP_CASE_SENSITIVE)); + boolean nonHierarchyFirst = request.getParameter(RP_NOT_HIERARCHY_FIRST) == null || + Boolean.parseBoolean(request.getParameter(RP_NOT_HIERARCHY_FIRST)); + Comparator parentComparator = nonHierarchyFirst ? HierarchyNodeComparator.INSTANCE : (n1, n2) -> 0; + return parentComparator.thenComparing((n1, n2) -> { + try { + String title1 = new JcrLabeledResource(n1).getTitle(); + if (title1 == null) { + title1 = n1.getName(); + } + String title2 = new JcrLabeledResource(n2).getTitle(); + if (title2 == null) { + title2 = n2.getName(); + } + return caseSensitive ? title1.compareTo(title2) : + title1.compareToIgnoreCase(title2); + } catch (RepositoryException e) { + return 0; + } + }); + } +} diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/package-info.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/package-info.java new file mode 100755 index 0000000000..4375bc2176 --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/package-info.java @@ -0,0 +1,26 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2013 Adobe + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +/** + * Miscellaneous Utilities. + */ +@Version("1.0.0") + +package com.adobe.acs.commons.sorter; +import org.osgi.annotation.versioning.Version; diff --git a/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java index 2b05e33cc9..9927bd4ed6 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java @@ -19,6 +19,8 @@ */ package com.adobe.acs.commons.sorter; +import com.adobe.acs.commons.sorter.impl.NodeNameSorter; +import com.adobe.acs.commons.sorter.impl.NodeTitleSorter; import org.apache.sling.api.resource.NonExistingResource; import org.apache.sling.api.resource.Resource; import org.apache.sling.servlets.post.PostResponse; @@ -36,12 +38,15 @@ import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import static com.adobe.acs.commons.sorter.SortNodesOperation.RP_SORTER_NAME; import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -54,6 +59,8 @@ public class SortNodesOperationTest { @Before public void setUp(){ + sorter.bindNodeSorter(new NodeNameSorter(), Collections.emptyMap()); + sorter.bindNodeSorter(new NodeTitleSorter(), Collections.emptyMap()); context.build() .resource("/sortable", JCR_PRIMARYTYPE, "cq:Page") .resource("/sortable/page2", JCR_PRIMARYTYPE, "cq:Page") @@ -66,12 +73,14 @@ public void setUp(){ .resource("/sortable/rep:policy", JCR_PRIMARYTYPE, "rep:ACL"); } + /** * by node name, case insensitive */ @Test public void testSortByNameCaseInsensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(true,false, false); + MockSlingHttpServletRequest request = context.request(); + Comparator comparator = sorter.getNodeSorter(request); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -84,7 +93,9 @@ public void testSortByNameCaseInsensitive() throws RepositoryException { */ @Test public void testDisableNonHierarchyFirst() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(false,false, false); + MockSlingHttpServletRequest request = context.request(); + request.addRequestParameter(":nonHierarchyFirst", "false"); + Comparator comparator = sorter.getNodeSorter(request); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -97,7 +108,9 @@ public void testDisableNonHierarchyFirst() throws RepositoryException { */ @Test public void testSortByNameCaseSensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(true,false, true); + MockSlingHttpServletRequest request = context.request(); + request.addRequestParameter(NodeTitleSorter.RP_CASE_SENSITIVE, "true"); + Comparator comparator = sorter.getNodeSorter(request); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -110,7 +123,8 @@ public void testSortByNameCaseSensitive() throws RepositoryException { */ @Test public void testSortByTitleCaseInsensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(true,true, false); + MockSlingHttpServletRequest request = context.request(); + Comparator comparator = sorter.getNodeSorter(request); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -123,7 +137,10 @@ public void testSortByTitleCaseInsensitive() throws RepositoryException { */ @Test public void testSortByTitleCaseSensitive() throws RepositoryException { - Comparator comparator = SortNodesOperation.createComparator(true,true, true); + MockSlingHttpServletRequest request = context.request(); + request.addRequestParameter(RP_SORTER_NAME, NodeTitleSorter.SORTER_NAME); + request.addRequestParameter(NodeTitleSorter.RP_CASE_SENSITIVE, "true"); + Comparator comparator = sorter.getNodeSorter(request); Node node = context.resourceResolver().getResource("/sortable").adaptTo(Node.class); List list = sorter.getSortedNodes(node, comparator); @@ -143,21 +160,6 @@ public void testDefaultRequestParameters() { assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page1", "page2", "Page3"), children); } - @Test - public void testRequestParameters() { - Resource resource = context.resourceResolver().getResource("/sortable"); - MockSlingHttpServletRequest request = context.request(); - request.addRequestParameter(SortNodesOperation.RP_SORT_BY_TITLE, "true"); - request.addRequestParameter(SortNodesOperation.RP_CASE_SENSITIVE, "true"); - request.setResource(resource); - sorter.run(context.request(), mock(PostResponse.class), null); - - List children = new ArrayList<>(); - resource.getChildren().forEach(r -> children.add(r.adaptTo(Node.class))); - - assertSortOrder(Arrays.asList("jcr:content", "rep:policy", "page2" /*A: Page-2*/, "Page3" /*C: Page-3*/, "page1" /*a: Page-1*/), children); - } - @Test public void testInvalidTargetResource() { String targetPath = "/unknown"; @@ -192,6 +194,20 @@ public void testNotSortableTarget() { errCaptor.getValue().getMessage()); } + @Test + public void testUnknownSorter() { + PostResponse response = mock(PostResponse.class); + MockSlingHttpServletRequest request = context.request(); + request.addRequestParameter(RP_SORTER_NAME, "sorterNA"); + request.setResource(context.resourceResolver().getResource("/sortable")); + ArgumentCaptor errCaptor = ArgumentCaptor.forClass(Throwable.class); + sorter.run(request, response, null); + + verify(response).setError(errCaptor.capture()); + assertEquals("NodeSorter was not found: sorterNA. Available sorters are: [:nodeNameSorter, :nodeTitleSorter]", + errCaptor.getValue().getMessage()); + } + private static void assertSortOrder(List expected, List list){ List actual = list.stream().map(n -> { try { diff --git a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html index dc70c5ddd6..5061e7a653 100755 --- a/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html +++ b/ui.apps/src/main/content/jcr_root/apps/acs-commons/components/utilities/sort-nodes/sort-nodes.html @@ -48,27 +48,49 @@
    - - - - - - + + + + + + + + - -
    + + -
    - - Sort By Title. If unchecked nodes will be sorted by name - +
    +
    + + + ${item.label} +
    - Case Sensitive Sort + Case Sensitive
    From eecb29110e5547edf1b65e022979431ef0533d1d Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Sat, 6 Mar 2021 17:43:30 +0300 Subject: [PATCH 5/6] sort-nodes: trying to make code climate happy --- .../com/adobe/acs/commons/sorter/SortNodesOperation.java | 6 +++--- .../main/java/com/adobe/acs/commons/sorter/SortersPojo.java | 2 +- .../acs/commons/sorter/impl/HierarchyNodeComparator.java | 4 +++- .../com/adobe/acs/commons/sorter/impl/NodeNameSorter.java | 4 ++-- .../com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java index e9fd29af72..04b2fc13d6 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortNodesOperation.java @@ -117,7 +117,7 @@ public void run(SlingHttpServletRequest slingRequest, PostResponse response, Sli response.setParentLocation(targetNode.getParent().getPath()); } - long t0 = System.currentTimeMillis(); + final long t0 = System.currentTimeMillis(); Comparator comparator = getNodeSorter(slingRequest); List children = getSortedNodes(targetNode, comparator); @@ -168,8 +168,8 @@ Comparator getNodeSorter(SlingHttpServletRequest slingRequest){ } NodeSorter sorter = nodeSorters.get(sorterId); if(sorter == null){ - String msg = "NodeSorter was not found: " + sorterId + - ". Available sorters are: " + nodeSorters.keySet().toString(); + String msg = "NodeSorter was not found: " + sorterId + + ". Available sorters are: " + nodeSorters.keySet().toString(); throw new IllegalArgumentException(msg); } return sorter.createComparator(slingRequest); diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java index a7603888cc..637714d064 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/SortersPojo.java @@ -34,7 +34,7 @@ public class SortersPojo extends WCMUsePojo { @Override public void activate(){ - + // no op } public Collection getAvailableSorters(){ diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java index b1559215f8..889d8dd91f 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/HierarchyNodeComparator.java @@ -30,7 +30,9 @@ class HierarchyNodeComparator implements Comparator { public static HierarchyNodeComparator INSTANCE = new HierarchyNodeComparator(); - private HierarchyNodeComparator(){} + private HierarchyNodeComparator(){ + // ensure singleton + } @Override public int compare(Node n1, Node n2) { diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java index 740a885f09..9ac65fb7b3 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeNameSorter.java @@ -45,8 +45,8 @@ public String getLabel(){ @Override public Comparator createComparator(HttpServletRequest request) { boolean caseSensitive = Boolean.parseBoolean(request.getParameter(RP_CASE_SENSITIVE)); - boolean nonHierarchyFirst = request.getParameter(RP_NOT_HIERARCHY_FIRST) == null || - Boolean.parseBoolean(request.getParameter(RP_NOT_HIERARCHY_FIRST)); + boolean nonHierarchyFirst = request.getParameter(RP_NOT_HIERARCHY_FIRST) == null + || Boolean.parseBoolean(request.getParameter(RP_NOT_HIERARCHY_FIRST)); Comparator parentComparator = nonHierarchyFirst ? HierarchyNodeComparator.INSTANCE : (n1, n2) -> 0; return parentComparator.thenComparing((n1, n2) -> { try { diff --git a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java index 41323dc55f..ed4f091593 100755 --- a/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java +++ b/bundle/src/main/java/com/adobe/acs/commons/sorter/impl/NodeTitleSorter.java @@ -47,8 +47,8 @@ public String getLabel(){ @Override public Comparator createComparator(HttpServletRequest request) { boolean caseSensitive = Boolean.parseBoolean(request.getParameter(RP_CASE_SENSITIVE)); - boolean nonHierarchyFirst = request.getParameter(RP_NOT_HIERARCHY_FIRST) == null || - Boolean.parseBoolean(request.getParameter(RP_NOT_HIERARCHY_FIRST)); + boolean nonHierarchyFirst = request.getParameter(RP_NOT_HIERARCHY_FIRST) == null + || Boolean.parseBoolean(request.getParameter(RP_NOT_HIERARCHY_FIRST)); Comparator parentComparator = nonHierarchyFirst ? HierarchyNodeComparator.INSTANCE : (n1, n2) -> 0; return parentComparator.thenComparing((n1, n2) -> { try { From b9ae40d71807c67e29448bc718ae2f1d7159ca29 Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Sun, 7 Mar 2021 13:55:02 +0300 Subject: [PATCH 6/6] sort-nodes: fixed junit errros --- .../com/adobe/acs/commons/sorter/SortNodesOperationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java index 9927bd4ed6..4b1832fe36 100755 --- a/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/sorter/SortNodesOperationTest.java @@ -204,7 +204,7 @@ public void testUnknownSorter() { sorter.run(request, response, null); verify(response).setError(errCaptor.capture()); - assertEquals("NodeSorter was not found: sorterNA. Available sorters are: [:nodeNameSorter, :nodeTitleSorter]", + assertEquals("NodeSorter was not found: sorterNA. Available sorters are: [byName, byTitle]", errCaptor.getValue().getMessage()); }