Author: npeltier
Date: Tue Aug 29 13:22:11 2017
New Revision: 1806590

URL: http://svn.apache.org/viewvc?rev=1806590&view=rev
Log:
SLING-7076 introducing traverse pipe

- pipe,
- integration in pipe builder,
- unit test,
- integration test

Added:
    
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/TraversePipe.java
    
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/internal/TraversePipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/traverse.json
Modified:
    sling/trunk/contrib/extensions/sling-pipes/pom.xml
    
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBuilder.java
    
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
    
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
    
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/it/PlumberTestIT.java

Modified: sling/trunk/contrib/extensions/sling-pipes/pom.xml
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/pom.xml?rev=1806590&r1=1806589&r2=1806590&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/pom.xml (original)
+++ sling/trunk/contrib/extensions/sling-pipes/pom.xml Tue Aug 29 13:22:11 2017
@@ -163,6 +163,12 @@
       <scope>provided</scope>
     </dependency>
     <dependency>
+      <groupId>commons-collections</groupId>
+      <artifactId>commons-collections</artifactId>
+      <version>3.2.2</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
       <groupId>commons-httpclient</groupId>
       <artifactId>commons-httpclient</artifactId>
       <version>3.1</version>

Modified: 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBuilder.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBuilder.java?rev=1806590&r1=1806589&r2=1806590&view=diff
==============================================================================
--- 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBuilder.java
 (original)
+++ 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBuilder.java
 Tue Aug 29 13:22:11 2017
@@ -109,6 +109,13 @@ public interface PipeBuilder {
     PipeBuilder echo(String path) throws IllegalAccessException;
 
     /**
+     * attach a traverse pipe to the current context
+     * @return
+     * @throws IllegalAccessException
+     */
+    PipeBuilder traverse() throws IllegalAccessException;
+
+    /**
      * attach a parent pipe to the current context
      * @return updated instance of PipeBuilder
      */

Modified: 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java?rev=1806590&r1=1806589&r2=1806590&view=diff
==============================================================================
--- 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
 (original)
+++ 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
 Tue Aug 29 13:22:11 2017
@@ -108,6 +108,11 @@ public class PipeBuilderImpl implements
     }
 
     @Override
+    public PipeBuilder traverse() {
+        return pipe(TraversePipe.RESOURCE_TYPE);
+    }
+
+    @Override
     public PipeBuilder json(String expr) throws IllegalAccessException {
         return pipe(JsonPipe.RESOURCE_TYPE).expr(expr);
     }

Modified: 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java?rev=1806590&r1=1806589&r2=1806590&view=diff
==============================================================================
--- 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
 (original)
+++ 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
 Tue Aug 29 13:22:11 2017
@@ -118,6 +118,7 @@ public class PlumberImpl implements Plum
         registerPipe(PathPipe.RESOURCE_TYPE, PathPipe.class);
         registerPipe(FilterPipe.RESOURCE_TYPE, FilterPipe.class);
         registerPipe(NotPipe.RESOURCE_TYPE, NotPipe.class);
+        registerPipe(TraversePipe.RESOURCE_TYPE, TraversePipe.class);
     }
 
     @Reference(policy= ReferencePolicy.DYNAMIC, cardinality= 
ReferenceCardinality.OPTIONAL)

Added: 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/TraversePipe.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/TraversePipe.java?rev=1806590&view=auto
==============================================================================
--- 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/TraversePipe.java
 (added)
+++ 
sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/internal/TraversePipe.java
 Tue Aug 29 13:22:11 2017
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.pipes.internal;
+
+import org.apache.commons.collections.IteratorUtils;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.pipes.BasePipe;
+import org.apache.sling.pipes.Plumber;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Traverse either nodes or properties, in breadth first or depth first, for 
properties, they can be white listed
+ */
+public class TraversePipe extends BasePipe {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(TraversePipe.class);
+    public static final String RESOURCE_TYPE = RT_PREFIX + "traverse";
+
+    /**
+     * Pipe Constructor
+     *
+     * @param plumber  plumber
+     * @param resource configuration resource
+     * @throws Exception in case configuration is not working
+     */
+    public TraversePipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+    }
+
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        return new TraversingIterator(getInput(), getResource().getValueMap());
+    }
+
+    /**
+     * iterative DFS or BFS jcr node tree iterator, transforming each visited 
node in a configured set of resources
+     */
+    public class TraversingIterator implements Iterator<Resource>{
+        protected final static String PN_PROPERTIES = "properties";
+        protected final static String PN_NAMEGLOBS = "nameGlobs";
+        protected final static String PN_BREADTH = "breadthFirst";
+        protected final static String PN_DEPTH = "depth";
+        boolean properties;
+        int initialLevel;
+        int maxLevel;
+        String[] nameGlobs;
+        boolean breadthFirst;
+        Iterator<Resource> currentResources;
+        List<Node> nodesToVisit = new ArrayList<>();
+
+        /**
+         * From a given node, refresh resources extracted out of it depending 
on configuration
+         * @param node
+         * @throws RepositoryException
+         */
+        void refreshResourceIterator(Node node) throws RepositoryException {
+            if (properties){
+                PropertyIterator it = nameGlobs != null ? 
node.getProperties(nameGlobs) : node.getProperties();
+                currentResources = IteratorUtils.transformedIterator(it, o -> {
+                    try {
+                        return resolver.getResource(((Property) o).getPath());
+                    } catch (RepositoryException e) {
+                        LOGGER.error("unable to read property", e);
+                    }
+                    return null;
+                });
+            } else {
+                currentResources = 
IteratorUtils.singletonIterator(resolver.getResource(node.getPath()));
+            }
+        }
+
+        int getDepth(String path) {
+            return path.split("/").length;
+        }
+
+        boolean isBeforeLastLevel(Node node) throws RepositoryException {
+            return maxLevel < 0 || getDepth(node.getPath()) < maxLevel;
+        }
+
+        /**
+         * Constructor with root node, & configuration
+         * @param root
+         * @param configuration
+         */
+        TraversingIterator(Resource root, ValueMap configuration){
+            properties = configuration.get(PN_PROPERTIES, false);
+            if (properties) {
+                nameGlobs = configuration.get(PN_NAMEGLOBS, String[].class);
+            }
+            breadthFirst = configuration.get(PN_BREADTH, false);
+            maxLevel = configuration.get(PN_DEPTH, -1);
+            if (maxLevel > 0){
+                initialLevel = getDepth(root.getPath());
+                maxLevel = initialLevel + maxLevel;
+            }
+            nodesToVisit.add(root.adaptTo(Node.class));
+        }
+
+        /**
+         * Navigate up to the next node that have resources out of it
+         * @return
+         */
+        boolean goToNextElligibleNode() {
+            try {
+                while ((currentResources == null || 
!currentResources.hasNext()) && nodesToVisit.size() > 0) {
+                    Node node = nodesToVisit.remove(0);
+                    LOGGER.debug("visiting {}", node.getPath());
+                    refreshResourceIterator(node);
+                    int indexAdd = breadthFirst ? nodesToVisit.size() : 0;
+                    if (isBeforeLastLevel(node)) {
+                        nodesToVisit.addAll(indexAdd, 
IteratorUtils.toList(node.getNodes()));
+                    }
+                }
+            } catch (RepositoryException e) {
+                LOGGER.error("unable to read node children", e);
+            }
+            return currentResources != null && currentResources.hasNext();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return (currentResources != null && currentResources.hasNext()) || 
goToNextElligibleNode();
+        }
+
+        @Override
+        public Resource next() {
+            return currentResources.next();
+        }
+    }
+}

Added: 
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/internal/TraversePipeTest.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/internal/TraversePipeTest.java?rev=1806590&view=auto
==============================================================================
--- 
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/internal/TraversePipeTest.java
 (added)
+++ 
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/internal/TraversePipeTest.java
 Tue Aug 29 13:22:11 2017
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.pipes.internal;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.pipes.AbstractPipeTest;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.cglib.core.CollectionUtils;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/**
+ * Testing traverse pipes and its different configurations on the same 
resource tree
+ */
+public class TraversePipeTest extends AbstractPipeTest {
+    public static final String ROOT = "/content/traverse";
+    public static final String CONF_ROOT = ROOT + "/pipes/";
+
+    @Before
+    public void setup() {
+        super.setup();
+        context.load().json("/traverse.json", ROOT);
+    }
+
+    @Test
+    public void testDefault() throws Exception{
+        assertListEquals(getResourceNameList("default"), "tree", "fruits", 
"apple", "banana", "vegetables", "leek", "carrot");
+    }
+
+    @Test
+    public void testBreadth() throws Exception{
+        assertListEquals(getResourceNameList("breadth"), "tree", "fruits", 
"vegetables", "apple", "banana", "leek", "carrot");
+    }
+
+    @Test
+    public void testProperties() throws Exception{
+        Set<String> properties = new 
HashSet<String>(getResourceNameList("properties"));
+        assertTrue("should contains all properties ",
+                properties.contains("jcr:primaryType")
+                && properties.contains("jcr:description")
+                && properties.contains("jcr:title")
+                && properties.contains("color"));
+    }
+
+    @Test
+    public void testSlim() throws Exception{
+        assertListEquals(getResourceNameList("slim"), "slim", "tree", "test");
+    }
+
+    @Test
+    @Ignore //for now nameGlobs is not implemented (see SLING-7089)
+    public void testWhiteListProperties() throws Exception {
+        List<String> colorList = 
CollectionUtils.transform(getResourceList("whiteListProperties"), o -> 
((Resource)o).adaptTo(String.class));
+        assertListEquals(colorList, "green", "yellow", "green", "orange");
+    }
+
+    @Test
+    public void testDepthLimit() throws Exception{
+        assertListEquals(getResourceNameList("depthLimit"), "tree", "fruits", 
"vegetables");
+    }
+
+    List<Resource> getResourceList(String pipeName){
+        Iterator<Resource> output = getOutput(CONF_ROOT + pipeName);
+        return IteratorUtils.toList(output);
+    }
+    List<String> getResourceNameList(String pipeName){
+        return CollectionUtils.transform(getResourceList(pipeName), o -> 
((Resource)o).getName());
+    }
+
+    private void assertListEquals(List<String> tested, String... expected){
+        assertArrayEquals("arrays should be equals", expected, 
tested.toArray(new String[tested.size()]));
+    }
+}
\ No newline at end of file

Modified: 
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/it/PlumberTestIT.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/it/PlumberTestIT.java?rev=1806590&r1=1806589&r2=1806590&view=diff
==============================================================================
--- 
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/it/PlumberTestIT.java
 (original)
+++ 
sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/it/PlumberTestIT.java
 Tue Aug 29 13:22:11 2017
@@ -25,24 +25,42 @@ import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.PaxExam;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 @RunWith(PaxExam.class)
 @ExamReactorStrategy(PerClass.class)
 public class PlumberTestIT extends PipesTestSupport {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(PlumberTestIT.class);
 
+    public static final String ROOT = "/content/my";
+    public static final String NN_TEST = "test";
+    public static final String TEST_PATH = ROOT + "/" + NN_TEST;
     @Test
     public void simpleTest() throws Exception {
         try (ResourceResolver resolver = resolver()) {
             plumber.getBuilder(resolver)
-                .mkdir("/content/my")
-                .write("test", true)
+                .mkdir(ROOT)
+                .write(NN_TEST, true)
                 .run();
-            Resource test = resolver.getResource("/content/my/test");
+            Resource test = resolver.getResource(TEST_PATH);
             assertNotNull("there should be a resource", test);
-            assertEquals("should be a boolean equals to true", true, 
test.adaptTo(Boolean.class));
+            assertTrue("should be a boolean equals to true", 
test.adaptTo(Boolean.class));
+        }
+    }
+
+    @Test
+    public void traverseTest() throws Exception {
+        try (ResourceResolver resolver = resolver()) {
+            plumber.getBuilder(resolver).mkdir(ROOT).run();
+            Set<String> results = 
plumber.getBuilder(resolver).echo(ROOT).traverse().run();
+            LOGGER.info("Following results are found {}", results);
+            assertTrue("should contain former test", results.contains(ROOT));
         }
     }
 

Added: 
sling/trunk/contrib/extensions/sling-pipes/src/test/resources/traverse.json
URL: 
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/resources/traverse.json?rev=1806590&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/resources/traverse.json 
(added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/resources/traverse.json 
Tue Aug 29 13:22:11 2017
@@ -0,0 +1,88 @@
+{
+  "jcr:primaryType":"nt:unstructured",
+  "tree":{
+    "jcr:primaryType":"nt:unstructured",
+    "jcr:description":"this tree contains fruits and vegetables",
+    "jcr:title":"test content for traverse",
+    "fruits":{
+      "jcr:primaryType":"nt:unstructured",
+      "apple":{
+        "jcr:primaryType":"nt:unstructured",
+        "color":"green"
+      },
+      "banana":{
+        "jcr:primaryType":"nt:unstructured",
+        "color":"yellow"
+      }
+    },
+    "vegetables":{
+      "jcr:primaryType":"nt:unstructured",
+      "leek":{
+        "jcr:primaryType":"nt:unstructured",
+        "color":"green"
+      },
+      "carrot":{
+        "jcr:primaryType":"nt:unstructured",
+        "color":"orange"
+      }
+    }
+  },
+  "slim":{
+    "jcr:primaryType":"nt:unstructured",
+    "tree":{
+      "jcr:primaryType":"nt:unstructured",
+      "test":{
+        "jcr:primaryType":"nt:unstructured"
+      }
+    }
+  },
+  "pipes":{
+    "jcr:primaryType":"nt:unstructured",
+    "default" : {
+      "jcr:primaryType":"nt:unstructured",
+      "jcr:description":"should be depth first, node visit",
+      "sling:resourceType":"slingPipes/traverse",
+      "path":"/content/traverse/tree"
+    },
+    "breadth" : {
+      "jcr:primaryType":"nt:unstructured",
+      "jcr:description":"should be breadth first, node visit",
+      "sling:resourceType":"slingPipes/traverse",
+      "path":"/content/traverse/tree",
+      "breadthFirst": true
+    },
+    "properties" : {
+      "jcr:primaryType":"nt:unstructured",
+      "jcr:description":"should be visiting tree's white listed properties",
+      "sling:resourceType":"slingPipes/traverse",
+      "path":"/content/traverse/tree",
+      "properties": true
+    },
+    "whiteListProperties" : {
+      "jcr:primaryType":"nt:unstructured",
+      "jcr:description":"should be visiting tree's white listed properties",
+      "sling:resourceType":"slingPipes/traverse",
+      "path":"/content/traverse/tree",
+      "properties": true,
+      "nameGlobs":"[color]"
+    },
+    "depthLimit" : {
+      "jcr:primaryType":"nt:unstructured",
+      "jcr:description":"should stop to configured level",
+      "sling:resourceType":"slingPipes/traverse",
+      "path":"/content/traverse/tree",
+      "depth": 1
+    },
+    "singleton" : {
+      "jcr:primaryType":"nt:unstructured",
+      "jcr:description":"should contain one and only one element",
+      "sling:resourceType":"slingPipes/traverse",
+      "path":"/content/traverse/tree/fruits/apple"
+    },
+    "slim": {
+      "jcr:primaryType":"nt:unstructured",
+      "sling:resourceType":"slingPipes/traverse",
+      "path":"/content/traverse/slim"
+    }
+  }
+}
\ No newline at end of file


Reply via email to