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