You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2015/12/07 15:19:52 UTC

svn commit: r1718377 [1/3] - in /sling/trunk/contrib/extensions/sling-pipes: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/sling/ src/main/java/org/apache/sling/pipes/ src/main/java/org/apache/sl...

Author: bdelacretaz
Date: Mon Dec  7 14:19:52 2015
New Revision: 1718377

URL: http://svn.apache.org/viewvc?rev=1718377&view=rev
Log:
SLING-5134 - new Sling Pipes module, donated by Nicolas Peltier, thanks!

Added:
    sling/trunk/contrib/extensions/sling-pipes/   (with props)
    sling/trunk/contrib/extensions/sling-pipes/.gitignore
    sling/trunk/contrib/extensions/sling-pipes/README.md
    sling/trunk/contrib/extensions/sling-pipes/pom.xml
    sling/trunk/contrib/extensions/sling-pipes/src/
    sling/trunk/contrib/extensions/sling-pipes/src/main/
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/AuthorizablePipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/BasePipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ContainerPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/FilterPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/JsonPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MovePipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MultiPropertyPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ParentPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PathPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Pipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBindings.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Plumber.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PlumberServlet.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ReferencePipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/RemovePipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/SlingQueryPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/WritePipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/XPathPipe.java
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/impl/
    sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/impl/PlumberImpl.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/AbstractPipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/ContainerPipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/FilterPipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/JsonPipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MovePipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MultiPropertyPipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PipeBindingsTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PlumberServletTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/RemovePipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/SlingQueryPipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/WritePipeTest.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/dummies/
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/dummies/DummyNull.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/dummies/DummySearch.java
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/container.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/filter.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/fruits.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/json.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/move.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/multiProperty.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/plumber.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/remove.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/slingQuery.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/testSum.js
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/users.json
    sling/trunk/contrib/extensions/sling-pipes/src/test/resources/write.json

Propchange: sling/trunk/contrib/extensions/sling-pipes/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Dec  7 14:19:52 2015
@@ -0,0 +1,14 @@
+target
+bin
+derby.log
+*.iml
+*.ipr
+*.iws
+.settings
+.project
+.classpath
+.externalToolBuilders
+maven-eclipse.xml
+
+
+

Added: sling/trunk/contrib/extensions/sling-pipes/.gitignore
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/.gitignore?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/.gitignore (added)
+++ sling/trunk/contrib/extensions/sling-pipes/.gitignore Mon Dec  7 14:19:52 2015
@@ -0,0 +1,8 @@
+target
+.idea
+.classpath
+.project
+.settings
+*.iml
+bin
+.DS_Store
\ No newline at end of file

Added: sling/trunk/contrib/extensions/sling-pipes/README.md
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/README.md?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/README.md (added)
+++ sling/trunk/contrib/extensions/sling-pipes/README.md Mon Dec  7 14:19:52 2015
@@ -0,0 +1,344 @@
+# sling-pipes
+tool for doing extract - transform - load operations through a resource tree configuration
+
+often one-shot data transformations need sample code to be written & executed. This tiny tool set intends to provide ability to do such transformations with proven & reusable blocks called pipes, streaming resources from one to the other.
+
+## What is a pipe
+
+```
+        getOutputBinding       
+              ^                
+              |                
+getInput  +---+---+   getOutput
+          |       |            
+     +----> Pipe  +---->       
+          |       |            
+          +-------+            
+```
+A sling pipe is essentially a sling resource stream:
+* it provides an output as a sling resource iterator
+* it gets its input either from a configured path, either, if its chained (see container pipes below), from another pipe's output
+* each pipe can have additional dynamic inputs using other's bindings, and outputing its own bindings
+ 
+At the moment, there are 3 types of pipes to consider:
+* "reader" pipes, that will just output a set of resource depending on the input
+* "writer" pipes, that write to the repository, depending on configuration and output
+* "container" pipes, that contains pipes, and whose job is to chain their execution : input is the input of their first pipe,
+ output is the output of the last pipe it contains.
+ 
+A `Plumber` osgi service is provided to help getting & executing pipes.
+
+## Registered Pipes
+a pipe configuration is a jcr node, with:
+* `sling:resourceType` property, which must be a pipe type registered by the plumber 
+* `name` property, that will be used in bindings as an id, and will be the key for the output bindings (default value being a value map of the current output resource). Note that the node name will be used in case no name is provided.
+* `path` property, if configured, will override upstream's pipe output as an input.
+* `expr` property, expression through which the pipe will execute (depending on the type) 
+* `additionalBinding` is a node you can add to set "global" bindings (property=value) in pipe execution
+* `additionalScripts` is a multi value property to declare scripts that can be reused in expressions
+* `conf` optional child node that contains addition configuration of the pipe (depending on the type)
+
+### readers
+
+#### Base pipe
+rather dummy pipe, outputs what is in input (so what is configured in path). Handy for doing some test mostly, and giving basic functionalities to others that inherit from it
+* `sling:resourceType` is `slingPipes/base`
+
+#### SlingQuery Pipe
+executes $(getInput()).children(expression)
+* `sling:resourceType` is `slingPipes/slingQuery`
+* `expr` mandatory property, contains slingQuery expression through which getInput()'s children will be computed to getOutput()
+
+#### JsonPipe
+feeds bindings with remote json
+* `sling:resourceType` is `slingPipes/json`
+* `expr` mandatory property contains url that will be called, the json be sent to the output bindings, getOutput = getInput.
+An empty url or a failing url will block the pipe at that given place.
+
+#### MultiPropertyPipe
+iterates through values of input multi value property and write them to bindings 
+* `sling:resourceType` is `slingPipes/multiProperty`
+* `path` should be the path of a mv property
+
+#### XPathPipe
+retrieve resources resulting of an xpath query
+* `sling:resourceType` is `slingPipes/xpath`
+* `expr` should be a valid xpath query
+
+### JsonPipe
+feeds bindings with remote json
+* `sling:resourceType` is `slingPipes/json`
+* `expr` mandatory property contains url that will be called, the json be sent to the output bindings, getOutput = getInput.
+An empty url or a failing url will block the pipe at that given place.
+
+#### AuthorizablePipe
+retrieve authorizable resource corresponding to the id passed in expression, or if not found (or void expression),
+from the input path, output the found authorizable's resource
+* `sling:resourceType` is `slingPipes/authorizable`
+* `expr` should be an authorizable id, or void (but then input should be an authorizable)
+* `autoCreateGroup` (boolean) if autorizable id is here, but the authorizable not present, then create group with given id (in that case, considered as a write pipe)
+* `addMembers` (stringified json array) if authorizable is a group, add instanciated members to it (in that case, considered as a write pipe)
+* `addToGroup` (expression) add found authorizable to instanciated group (in that case, considered as a write pipe)
+* `bindMembers` (boolean) if found authorizable is a group, bind the members (in that case, considered as a write pipe)
+
+#### ParentPipe
+outputs the parent resource of input resource
+* `sling:resourceType` is `slingPipes/parent`
+
+#### FilterPipe
+outputs the input resource if its matches its configuration
+* `sling:resourceType` is `slingPipes/filter`
+* `conf` node tree that will be tested against the current input of the pipe, each `/conf/sub@prop=value` will triggers a test
+on `./sub@prop` property of the current input, testing if its value matches `value` regex. If the special `slingPipesFilter_noChildren=${true}`
+property is there with the value instantiated as a true boolean, then filter will pass if corresponding node has no children.
+
+### containers
+#### Container Pipe
+assemble a sequence of pipes
+* `sling:resourceType` is `slingPipes/container`
+* `conf` node contains child pipes' configurations, that will be configured in the order they are found (note you should use sling:OrderedFolder)
+
+#### ReferencePipe
+execute the pipe referenced in path property
+* `sling:resourceType` is `slingPipes/reference`
+* `path` path of the referenced pipe
+
+### writers
+
+#### Write Pipe
+writes given properties to current input
+* `sling:resourceType` is `slingPipes/slingQuery`
+* `conf` node tree that will be copied to the current input of the pipe, each node's properties 
+names and value will be written to the input resource. Input resource will be outputed. 
+
+### MovePipe
+JCR move of current input to target path (can be a node or a property)
+* `sling:resourceType` is `slingPipes/mv`
+* `expr` target path, note that parent path must exists
+
+#### RemovePipe
+removes the input resource, returns the parent, regardless of the resource being a node, or
+a property
+* `sling:resourceType` is `slingPipes/rm`
+* `conf` node tree that will be used to filter relative properties & subtrees to the current resource to remove.
+A subnode is considered to be removed if it has no property configured, nore any child.
+
+#### PathPipe
+get or create path given in expression
+* `sling:resourceType` is `slingPipes/path`
+* `nodeType` node type of the intermediate nodes to create
+* `autosave` should save at each creation (will make things slow, but sometimes you don't have choice)
+
+## Making configuration dynamic with pipe bindings
+in order to make things interesting, most of the configurations are javascript template strings, hence valid js expressions reusing bindings (from configuration, or other pipes).
+
+Following configurations are evaluated:
+* `path`
+* `expr`
+* name/value of each property of some pipes (write, remove)
+
+you can use name of previous pipes in the pipe container, or the special binding `path`, where `path.previousPipe` 
+is the path of the current resource of previous pipe named `previousPipe`
+
+global bindings can be set at pipe execution, external scripts can be added to the execution as well (see pipe
+ configurations)
+
+## How to execute a pipe
+for now it's possible to execute Pipes through GET (read) or POST (read/write) commands:
+
+### Request Path
+- either you'll need to create a slingPipes/plumber resource, say `etc/pipes` and then to execute
+```
+curl -u admin:admin -F "path=/etc/pipes/mySamplePipe" http://localhost:8080/etc/pipes.json
+```
+- either you execute the request directly on the pipe Path, e.g.
+```
+curl -u admin:admin http://localhost:8080/etc/pipes/mySamplePipe.json
+```
+which will return you the path of the pipes that have been through the output of the configured pipe.
+
+### Request Parameter `binding`
+
+you can add as `bindings` parameter a json object of global bindings you want to add for the execution of the pipe
+ 
+e.g. 
+
+```
+ curl -u admin:admin -F "path=/etc/pipes/test" -F "bindings={testBinding:'foo'}" http://localhost:4502/etc/pipes.json
+```
+
+will returns something like
+
+```
+["/one/output/resource", "another/one"]
+```
+
+### Request Parameter `writer`
+
+you can add as `writer` parameter a json object as a pattern to the result you want to have. The values of the json
+object are expressions and can reuse each pipe's subpipe binding. 
+Note 
+this works only if the pipe called is a container
+pipe.
+
+e.g.
+
+```
+curl -u admin:admin http://localhost:4502/etc/pipes/users.json?writer={"user":"${user.fullName}"}
+```
+
+will returns something similar to
+ 
+```
+[{'user':'John Smith','path':'/home/users/q/q123jk1UAZS'},{'user':'John Doe','path':'/home/users/q/q153jk1UAZS'}]
+```
+
+### Request Parameter `dryRun`
+if parameter dryRun is set to true, and the executed pipe is supposed to modify content, it will log (at best it can) the change it *would* have done, without doing anything
+
+## sample configurations 
+
+### slingQuery | write
+this pipe parse all profile nodes, and 
+```
+{
+  "sling:resourceType":"slingPipes/container",
+  "name":"Dummy User prefix Sample",
+  "jcr:description":"prefix all full names of profile with "Mr" or "Ms" depending on gender",
+  "conf":{
+    "profile": {
+        "sling:resourceType":"slingPipes/slingQuery",
+        "expr":"nt:unstructured#profile",
+        "path":"/home/users"
+    },
+    "writeFullName": {       
+        "sling:resourceType":"slingPipes/write",
+        "conf": {
+            "fullName":"${(profile.gender === 'female' ? 'Ms ' + profile.fullName : 'Mr ' + profile.fullName)}",
+            "generatedBy":"slingPipes"
+        }
+    }
+  }
+}
+```
+
+### slingQuery | multiProperty | authorizable | write
+```
+{
+  "jcr:primaryType": "sling:Folder",
+  "jcr:description": "move badge<->user relation ship from badge MV property to a user MV property"
+  "name": "badges",
+  "sling:resourceType": "slingPipes/container",
+  "conf": {
+    "jcr:primaryType": "sling:OrderedFolder",
+    "badge": {
+      "jcr:primaryType": "sling:Folder",
+      "jcr:description": "outputs all badge component resources",
+      "expr": "[sling:resourceType=myApp/components/badge]",
+      "path": "/etc/badges/badges-admin/jcr:content",
+      "sling:resourceType": "slingPipes/slingQuery"
+      },
+    "profile": {
+      "jcr:primaryType": "sling:Folder",
+      "jcr:description": "retrieve all user ids from a mv property",
+      "path": "${path.badge}/profiles",
+      "sling:resourceType": "slingPipes/multiProperty"
+      },
+    "user": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "jcr:description": "outputs user resource",
+      "expr": "profile",
+      "sling:resourceType": "slingPipes/authorizable"
+      },
+    "write": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "jcr:descritption": "patches the badge path to the badges property of the user profile"
+      "path": "${path.user}/profile",
+      "sling:resourceType": "slingPipes/write",
+      "conf": {
+        "jcr:primaryType": "nt:unstructured",
+        "badges": "+[${path.badge}]"
+        }
+      }
+    }
+  }
+```
+
+### xpath | json | write
+this use case is for completing repository profiles with external system's data (that has an json api)
+```
+{
+  "jcr:primaryType": "nt:unstructured",
+  "jcr:description": "this pipe retrieves json info from an external system and writes them to the user profile, uses moment.js, it
+  distributes modified resources using publish distribution agent",
+  "sling:resourceType": "slingPipes/container",
+  "distribution.agent": "publish",
+  "additionalScripts": "/etc/source/moment.js",
+  "conf": {
+    "jcr:primaryType": "sling:OrderedFolder",
+    "profile": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "expr": "/jcr:root/home/users//element(profile,nt:unstructured)[@uid]",
+      "jcr:description": "query all user profile nodes",
+      "sling:resourceType": "slingPipes/xpath"
+      },
+    "json": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "expr": "${(profile.uid ? 'https://my.external.system.corp.com/profiles/' + profile.uid.substr(0,2) + '/' + profile.uid + '.json' : '')",
+      "jcr:description": "retrieves json information relative to the given profile, if the uid is not found, expr is empty: the pipe will do nothing",
+      "sling:resourceType": "slingPipes/json"
+      },
+    "write": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "path": "path.profile",
+      "jcr:description": "write json information to the profile node",
+      "sling:resourceType": "slingPipes/write",
+      "conf": {
+        "jcr:primaryType": "sling:OrderedFolder",
+        "jcr:createdBy": "admin",
+        "background": "${json.opt('background')}",
+        "about": "${json.opt('about')}",
+        "jcr:created": "Fri Jul 03 2015 15:32:22 GMT+0200",
+        "birthday": "${(json.opt('birthday') ? moment(json.opt('birthday'), \"MMMM DD\").toDate() : '')}",
+        "mobile": "${json.opt('mobile')}"
+        }
+      }
+    }
+  }
+```
+
+### xpath | parent | rm
+```
+{
+  "jcr:primaryType": "nt:unstructured",
+  "jcr:description": "this pipe removes user with bad property in their profile",
+  "sling:resourceType": "slingPipes/container",
+  "conf": {
+    "jcr:primaryType": "sling:OrderedFolder",
+    "profile": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "expr": "/jcr:root/home/users//element(profile,nt:unstructured)[@bad]",
+      "jcr:description": "query all user profile nodes with bad properties",
+      "sling:resourceType": "slingPipes/xpath"
+      },
+    "parent": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "jcr:description": "get the parent node (user node)",
+      "sling:resourceType": "slingPipes/parent"
+      },
+    "rm": {
+      "jcr:primaryType": "sling:OrderedFolder",
+      "jcr:description": "remove it",
+      "sling:resourceType": "slingPipes/rm",
+      }
+   }
+}
+```
+
+some other samples are in https://github.com/npeltier/sling-pipes/tree/master/src/test/
+
+# Compatibility
+For running this tool on a sling instance you need:
+- java 8 (Nashorn is used for expression)
+- slingQuery (3.0.0) (used in SlingQueryPipe)
+- jackrabbit api (2.7.5+) (used in AuthorizablePipe)

Added: sling/trunk/contrib/extensions/sling-pipes/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/pom.xml?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/pom.xml (added)
+++ sling/trunk/contrib/extensions/sling-pipes/pom.xml Mon Dec  7 14:19:52 2015
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>23</version>
+        <relativePath/>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>org.apache.sling.pipes</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.0.8-SNAPSHOT</version>
+
+    <name>Apache Sling Pipes</name>
+    <description>bulk content changes tool</description>
+    <properties>
+        <sling.java.version>8</sling.java.version>
+    </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            org.apache.sling.distribution;resolution:=optional,
+                            org.apache.sling.query;version=3.0.0,
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.9.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.6.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <version>1.9.8</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.query</artifactId>
+            <version>3.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-api</artifactId>
+            <version>2.7.5</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.distribution.api</artifactId>
+            <version>0.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- testing dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+            <version>1.3.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.6</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/AuthorizablePipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/AuthorizablePipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/AuthorizablePipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/AuthorizablePipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,204 @@
+/*
+ * 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;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.commons.json.JSONArray;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * pipe that outputs an authorizable resource based on the id set in expr
+ */
+public class AuthorizablePipe extends BasePipe {
+    private static Logger logger = LoggerFactory.getLogger(AuthorizablePipe.class);
+    public static final String RESOURCE_TYPE = "slingPipes/authorizable";
+    public static final String PN_AUTOCREATEGROUP = "createGroup";
+    public static final String PN_ADDTOGROUP = "addToGroup";
+    public static final String PN_ADDMEMBERS = "addMembers";
+    public static final String PN_BINDMEMBERS = "bindMembers";
+
+    UserManager userManager;
+    ResourceResolver resolver;
+    boolean autoCreateGroup;
+    boolean bindMembers;
+    String addToGroup;
+    String addMembers;
+    Object outputBinding;
+
+    @Override
+    public Object getOutputBinding() {
+        if (outputBinding != null) {
+            return outputBinding;
+        }
+        return super.getOutputBinding();
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        return autoCreateGroup || StringUtils.isNotBlank(addToGroup) || StringUtils.isNotBlank(addMembers);
+    }
+
+    public AuthorizablePipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+        resolver = resource.getResourceResolver();
+        userManager = resolver.adaptTo(UserManager.class);
+        if (getConfiguration() != null) {
+            ValueMap properties = getConfiguration().adaptTo(ValueMap.class);
+            autoCreateGroup = properties.get(PN_AUTOCREATEGROUP, false);
+            bindMembers = properties.get(PN_BINDMEMBERS, false);
+            addToGroup = properties.get(PN_ADDTOGROUP, String.class);
+            addMembers = properties.get(PN_ADDMEMBERS, String.class);
+        }
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        try {
+            Authorizable auth = getAuthorizable();
+            if (auth != null) {
+                logger.debug("Retrieved authorizable {}", auth.getID());
+                if (StringUtils.isNotBlank(addToGroup)){
+                    addToGroup(auth);
+                }
+                if (StringUtils.isNotBlank(addMembers)){
+                    addMembers(auth);
+                }
+                if (bindMembers){
+                    bindMembers(auth);
+                }
+                Resource resource = resolver.getResource(auth.getPath());
+                return Collections.singleton(resource).iterator();
+            }
+        } catch (Exception e){
+        }
+        return EMPTY_ITERATOR;
+    }
+
+    /**
+     * Returns the authorizable configured by its expression, creating it if
+     * not present and if <code>autoCreateGroup</code> is set to true, or, if
+     * no expression, tries to resolve getInput() as an authorizable
+     * @return
+     * @throws RepositoryException
+     */
+    protected Authorizable getAuthorizable() {
+        Authorizable auth = null;
+        try {
+            String authId = getExpr();
+            if (StringUtils.isNotBlank(authId)) {
+                logger.debug("try to find authorizable {}", authId);
+                auth = userManager.getAuthorizable(authId);
+                if (auth == null && autoCreateGroup) {
+                    logger.info("authorizable {} does not exist, creating", authId);
+                    auth = userManager.createGroup(authId);
+                }
+            } else {
+                Resource resource = getInput();
+                if (resource != null) {
+                    auth = userManager.getAuthorizableByPath(resource.getPath());
+                }
+            }
+        } catch (Exception e){
+            logger.error("unable to output authorizable based on configuration", e);
+        }
+        return auth;
+    }
+
+    /**
+     * Add current authorizable to configured addToGroup expression (should resolve as a group id)
+     * @param auth
+     */
+    protected void addToGroup(Authorizable auth){
+        try {
+            //if addToGroup is set to true, we try to find the corresponding
+            //group and to add current auth to it as a member
+            String groupId = bindings.instantiateExpression(addToGroup);
+            Authorizable groupAuth = (Group) userManager.getAuthorizable(groupId);
+            if (groupAuth != null && groupAuth.isGroup()) {
+                logger.info("adding {} to {}", auth.getID(), groupId);
+                if (! isDryRun()) {
+                    ((Group) groupAuth).addMember(auth);
+                }
+            }
+        } catch (Exception e){
+            logger.error("Unable to add current authorizable to group {}", addToGroup, e);
+        }
+    }
+
+    /**
+     * Add to current authorizable (that should be a group) the configured members in addMembers expression
+     * @param auth
+     */
+    protected void addMembers(Authorizable auth) {
+        try {
+            if (auth.isGroup()) {
+                Group group = (Group)auth;
+                String uids = bindings.instantiateExpression(addMembers);
+                JSONArray array = new JSONArray(uids);
+                for (int index = 0; index < array.length(); index ++){
+                    String uid = array.getString(index);
+                    Authorizable member = userManager.getAuthorizable(uid);
+                    if (member != null) {
+                        logger.info("adding {} to group {}", member.getID(), group.getID());
+                        if (!isDryRun()) {
+                            group.addMember(member);
+                        }
+                    } else {
+                        logger.error("computed uid {} doesn't exist, doing nothing", uid);
+                    }
+                }
+            } else {
+                logger.error("{} is not a group, can't add members", auth.getID());
+            }
+        } catch (Exception e){
+            logger.error("unable to add members {}", addMembers, e);
+        }
+    }
+
+    /**
+     * add current group's members to the bindings
+     * @param auth
+     */
+    protected void bindMembers(Authorizable auth){
+        try {
+            if (auth.isGroup()){
+                Group group = (Group)auth;
+                Iterator<Authorizable> memberIterator = group.getMembers();
+                JSONArray array = new JSONArray();
+                while (memberIterator.hasNext()){
+                    array.put(memberIterator.next().getID());
+                }
+                outputBinding = array.toString();
+            } else {
+                logger.error("{} is not a group, unable to bind members", auth.getID());
+            }
+        } catch (Exception e){
+            logger.error("unable to bind members");
+        }
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/BasePipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/BasePipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/BasePipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/BasePipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,186 @@
+/*
+ * 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;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * provides generic utilities for a pipe
+ */
+public class BasePipe implements Pipe {
+    Logger logger = LoggerFactory.getLogger(BasePipe.class);
+    public static final String RESOURCE_TYPE = "slingPipes/base";
+    protected static final String DRYRUN_KEY = "dryRun";
+    protected static final String DRYRUN_EXPR = "${" + DRYRUN_KEY + "}";
+
+    protected ResourceResolver resolver;
+    protected ValueMap properties;
+    protected Resource resource;
+    protected ContainerPipe parent;
+    protected String distributionAgent;
+    protected PipeBindings bindings;
+
+    // used by pipes using complex JCR configurations
+    public static final List<String> IGNORED_PROPERTIES = Arrays.asList(new String[]{"jcr:lastModified", "jcr:primaryType", "jcr:created", "jcr:createdBy"});
+
+
+    protected Boolean dryRunObject;
+
+    @Override
+    public ContainerPipe getParent() {
+        return parent;
+    }
+
+    @Override
+    public void setParent(ContainerPipe parent) {
+        this.parent = parent;
+    }
+
+    protected Plumber plumber;
+
+    private String name;
+
+    public BasePipe(Plumber plumber, Resource resource) throws Exception {
+        this.resource = resource;
+        properties = resource.adaptTo(ValueMap.class);
+        resolver = resource.getResourceResolver();
+        this.plumber = plumber;
+        name = properties.get(PN_NAME, resource.getName());
+        distributionAgent = properties.get(PN_DISTRIBUTION_AGENT, String.class);
+        bindings = new PipeBindings(resource);
+    }
+
+    public boolean isDryRun() {
+        if (dryRunObject == null) {
+            Object run =  bindings.isBindingDefined(DRYRUN_KEY) ? bindings.instantiateObject(DRYRUN_EXPR) : false;
+            dryRunObject =  run != null && run instanceof Boolean ? (Boolean)run : false;
+        }
+        boolean dryRun = dryRunObject != null ? dryRunObject : false;
+        return dryRun;
+    }
+
+    public String toString() {
+        return name + " " + "(path: " + resource.getPath() + ", dryRun: " + isDryRun() + ", modifiesContent: " + modifiesContent() + ")";
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        return false;
+    }
+
+    public String getName(){
+        return name;
+    }
+
+    /**
+     * Get pipe's expression, instanciated or not
+     * @return
+     */
+    public String getExpr(){
+        String rawExpression = properties.get(PN_EXPR, "");
+        return bindings.instantiateExpression(rawExpression);
+    }
+
+    /**
+     * Get pipe's path, instanciated or not
+     * @return
+     */
+    public String getPath() {
+        String rawPath = properties.get(PN_PATH, "");
+        return bindings.instantiateExpression(rawPath);
+    }
+
+    @Override
+    public Resource getConfiguredInput() {
+        Resource configuredInput = null;
+        String path = getPath();
+        if (StringUtils.isNotBlank(path)){
+            configuredInput = resolver.getResource(path);
+            if (configuredInput == null) {
+                logger.warn("configured path {} is not found, expect some troubles...", path);
+            }
+        }
+        return configuredInput;
+    }
+
+    @Override
+    public Resource getInput() {
+        Resource resource = getConfiguredInput();
+        if (resource == null && parent != null){
+            Pipe previousPipe = parent.getPreviousPipe(this);
+            if (previousPipe != null) {
+                return bindings.getExecutedResource(previousPipe.getName());
+            }
+        }
+        return resource;
+    }
+
+
+    @Override
+    public Object getOutputBinding() {
+        if (parent != null){
+            Resource resource = bindings.getExecutedResource(getName());
+            if (resource != null) {
+                return resource.adaptTo(ValueMap.class);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public PipeBindings getBindings() {
+        return bindings;
+    }
+
+    @Override
+    public void setBindings(PipeBindings bindings) {
+        this.bindings = bindings;
+    }
+
+    /**
+     * default execution, just returns current resource
+     * @return
+     */
+    public Iterator<Resource> getOutput(){
+        return Collections.singleton(getInput()).iterator();
+    }
+
+    /**
+     * Get configuration node
+     * @return
+     */
+    public Resource getConfiguration() {
+        return resource.getChild(NN_CONF);
+    }
+
+    @Override
+    public String getDistributionAgent() {
+        return distributionAgent;
+    }
+
+    public static final Iterator<Resource> EMPTY_ITERATOR = Collections.emptyIterator();
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ContainerPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ContainerPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ContainerPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ContainerPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,227 @@
+/*
+ * 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;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.script.Bindings;
+import javax.script.Invocable;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import javax.script.SimpleScriptContext;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This pipe executes the pipes it has in its configuration, chaining their result, and
+ * modifying each contained pipe's expression with its context
+ */
+public class ContainerPipe extends BasePipe {
+    private static final Logger log = LoggerFactory.getLogger(ContainerPipe.class);
+
+    public static final String RESOURCE_TYPE = "slingPipes/container";
+
+    Map<String, Pipe> pipes = new HashMap<>();
+
+    List<Pipe> pipeList = new ArrayList<>();
+
+    List<Pipe> reversePipeList = new ArrayList<>();
+
+    public ContainerPipe(Plumber plumber, Resource resource) throws Exception{
+        super(plumber, resource);
+        for (Iterator<Resource> childPipeResources = getConfiguration().listChildren(); childPipeResources.hasNext();){
+            Resource pipeResource = childPipeResources.next();
+            Pipe pipe = plumber.getPipe(pipeResource);
+            if (pipe == null) {
+                log.error("configured pipe {} is either not registered, or not computable by the plumber", pipeResource.getPath());
+            } else {
+                pipe.setParent(this);
+                pipe.setBindings(bindings);
+                pipes.put(pipe.getName(), pipe);
+                pipeList.add(pipe);
+                reversePipeList.add(pipe);
+            }
+        }
+        Collections.reverse(reversePipeList);
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        for (Pipe pipe : pipes.values()){
+            if (pipe.modifiesContent()){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Iterator<Resource> getOutput()  {
+        if (pipeList.size() == 1) {
+            //corner case with only one element: no need to have a container resource iterator
+            return pipeList.iterator().next().getOutput();
+        }
+        return new ContainerResourceIterator(this);
+    }
+
+    /**
+     * Returns the pipe immediately before the given pipe, null if it's the first
+     * @param pipe
+     * @return
+     */
+    public Pipe getPreviousPipe(Pipe pipe){
+        Pipe previousPipe = null;
+        for (Pipe candidate : pipeList){
+            if (candidate.equals(pipe)){
+                return previousPipe;
+            }
+            previousPipe = candidate;
+        }
+        return null;
+    }
+
+    /**
+     * Return the first pipe in the container
+     * @return
+     */
+    public Pipe getFirstPipe() {
+        return pipeList.iterator().next();
+    }
+
+    /**
+     * Return the last pipe in the container
+     * @return
+     */
+    public Pipe getLastPipe() {
+        return reversePipeList.iterator().next();
+    }
+
+    public Resource getOuputResource() {
+        return bindings.getExecutedResource(getLastPipe().getName());
+    }
+
+    /**
+     * Container Iterator, that iterates through the whole chain
+     * of pipes, returning the result resources of the end of the chain
+     */
+    static class ContainerResourceIterator implements Iterator<Resource> {
+        /**
+         * map name -> iterator
+         */
+        Map<Pipe, Iterator<Resource>> iterators;
+
+        /**
+         * container pipe
+         */
+        ContainerPipe container;
+
+        PipeBindings bindings;
+
+        boolean computedCursor = false;
+        boolean hasNext = false;
+        int cursor = 0;
+
+        ContainerResourceIterator(ContainerPipe containerPipe) {
+            container = containerPipe;
+            bindings = container.bindings;
+            iterators = new HashMap<>();
+            Pipe firstPipe = container.getFirstPipe();
+            //we initialize the first iterator the only one not to be updated
+            iterators.put(firstPipe, firstPipe.getOutput());
+        }
+
+        /**
+         * go up and down the container iterators until cursor is at 0 (first pipe) with no
+         * more resources, or at length - 1 (last pipe) with a next one
+         * @return
+         */
+        private boolean updateCursor(){
+            Pipe currentPipe = container.pipeList.get(cursor);
+            Iterator<Resource> it = iterators.get(currentPipe);
+            do {
+                // go up to at best reach the last pipe, updating iterators & bindings of the
+                // all intermediates, if an intermediate pipe is not outputing anything
+                // anymore, stop.
+                while (it.hasNext() && cursor < container.pipeList.size() - 1) {
+                    Resource resource = it.next();
+                    bindings.updateBindings(currentPipe, resource);
+                    //now we update the following pipe output with that new context
+                    Pipe nextPipe = container.pipeList.get(++cursor);
+                    iterators.put(nextPipe, nextPipe.getOutput());
+                    currentPipe = nextPipe;
+                    it = iterators.get(currentPipe);
+                }
+                //go down (or stay) to the first pipe having a next item
+                while (!it.hasNext() && cursor > 0) {
+                    currentPipe = container.pipeList.get(--cursor);
+                    it = iterators.get(currentPipe);
+                }
+            } while (it.hasNext() && cursor < container.pipeList.size() - 1);
+            //2 choices here:
+            // either cursor is at 0 with no resource left: end,
+            // either cursor is on last pipe with a resource left: hasNext
+            return cursor > 0;
+        }
+
+        /**
+         * we need to find the first "path" from first pipe to the last
+         * where each pipe returns something, if no "path", this pipe is
+         * done, other wise we must have updated iterators (next is allowed
+         * up to the pipe before the last), and return true
+         * @return
+         */
+        @Override
+        public boolean hasNext() {
+            if (! computedCursor) {
+                hasNext = updateCursor();
+            }
+            return hasNext;
+        }
+
+        @Override
+        public Resource next() {
+            hasNext = computedCursor && hasNext || hasNext();
+            if (hasNext) {
+                computedCursor = false;
+                hasNext = false;
+                Resource resource =  iterators.get(container.getLastPipe()).next();
+                bindings.updateBindings(container.getLastPipe(), resource);
+                return resource;
+            }
+            return null;
+        }
+
+        @Override
+        public void remove() {
+
+        }
+    }
+
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/FilterPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/FilterPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/FilterPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/FilterPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+/**
+ * intends to output the input only if configured conditions are fulfilled
+ */
+public class FilterPipe extends BasePipe {
+    private static Logger logger = LoggerFactory.getLogger(FilterPipe.class);
+    public static final String RESOURCE_TYPE = "slingPipes/filter";
+    public static final String PREFIX_FILTER = "slingPipesFilter_";
+    public static final String PN_NOCHILDREN = PREFIX_FILTER + "noChildren";
+    public static final String PN_TEST = PREFIX_FILTER + "test";
+
+    public FilterPipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+    }
+
+    boolean propertiesPass(ValueMap current, ValueMap filter){
+        if (filter.containsKey(PN_TEST)){
+            if (!(Boolean) bindings.instantiateObject(filter.get(PN_TEST, "${false}"))){
+                return false;
+            }
+        }
+        for (String key : filter.keySet()){
+            if (! IGNORED_PROPERTIES.contains(key) && !key.startsWith(PREFIX_FILTER)){
+                Pattern pattern = Pattern.compile(filter.get(key, String.class));
+                if (!pattern.matcher(current.get(key, String.class)).matches()){
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    boolean filterPasses(Resource currentResource, Resource filterResource){
+        try {
+            ValueMap current = currentResource.adaptTo(ValueMap.class);
+            ValueMap filter = filterResource.adaptTo(ValueMap.class);
+            if (propertiesPass(current, filter)) {
+                Node currentNode = currentResource.adaptTo(Node.class);
+                boolean noChildren = (Boolean) bindings.instantiateObject(filter.get(PN_NOCHILDREN, "${false}"));
+                if (noChildren) {
+                    return !currentNode.hasNodes();
+                } else {
+                    Node filterNode = filterResource.adaptTo(Node.class);
+                    boolean returnValue = true;
+                    for (NodeIterator children = filterNode.getNodes(); returnValue && children.hasNext();){
+                        String childName = children.nextNode().getName();
+                        if (!currentNode.hasNode(childName)){
+                            return false;
+                        } else {
+                            returnValue &= filterPasses(currentResource.getChild(childName), filterResource.getChild(childName));
+                        }
+                    }
+                    return returnValue;
+                }
+            }
+        } catch (Exception e){
+            logger.error("error when executing filter", e);
+        }
+        return false;
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        Resource resource = getInput();
+        if (resource != null){
+            if (filterPasses(resource, getConfiguration())){
+                logger.debug("filter passes for {}", resource.getPath());
+                return super.getOutput();
+            } else {
+                logger.info("{} got filtered out", resource.getPath());
+            }
+        }
+        return Collections.emptyIterator();
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/JsonPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/JsonPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/JsonPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/JsonPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,164 @@
+/*
+ * 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;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpConnectionManager;
+import org.apache.commons.httpclient.HttpState;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.commons.json.JSONTokener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.Iterator;
+
+/**
+ * Pipe outputing binding related to a json stream: either an object
+ */
+public class JsonPipe extends BasePipe {
+    private static Logger logger = LoggerFactory.getLogger(JsonPipe.class);
+    public static final String RESOURCE_TYPE = "slingPipes/json";
+
+    HttpClient client;
+
+    JSONArray array;
+    Object binding;
+    int index = -1;
+
+    public final String REMOTE_START = "http";
+
+    public JsonPipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+        configureHttpClient();
+    }
+
+    /**
+     * Configure http client
+     */
+    private void configureHttpClient(){
+        HttpConnectionManager manager = new MultiThreadedHttpConnectionManager();
+        HttpConnectionManagerParams params = new HttpConnectionManagerParams();
+        manager.setParams(params);
+        client = new HttpClient(manager);
+        client.getParams().setAuthenticationPreemptive(false);
+    }
+
+    @Override
+    public Object getOutputBinding() {
+        return binding;
+    }
+
+    /**
+     * Retrieve remote JSON String, or null if any problem occurs
+     * @return
+     */
+    private String retrieveJSONString()  {
+        String json = null;
+        String expression = getExpr();
+        if (expression.startsWith(REMOTE_START)){
+            GetMethod method = null;
+            HttpState httpState = new HttpState();
+            InputStream responseInputStream = null;
+            try {
+                String url = getExpr();
+                if (StringUtils.isNotBlank(url)) {
+                    method = new GetMethod(url);
+                    logger.debug("Executing GET {}", url);
+                    int status = client.executeMethod(null, method, httpState);
+                    if (status == HttpStatus.SC_OK) {
+                        logger.debug("200 received, streaming content");
+                        responseInputStream = method.getResponseBodyAsStream();
+                        StringWriter writer = new StringWriter();
+                        IOUtils.copy(responseInputStream, writer, "utf-8");
+                        json = writer.toString();
+                    }
+                }
+            }
+            catch(Exception e){
+                logger.error("unable to retrieve the data", e);
+            }finally{
+                if (method != null) {
+                    method.releaseConnection();
+                }
+                IOUtils.closeQuietly(responseInputStream);
+            }
+        } else {
+            //other try: given expression *is* json
+            json = expression;
+        }
+        return json;
+    }
+
+
+    /**
+     * in case there is no successful retrieval of some JSON data, we cut the pipe here
+     * @return
+     */
+    public Iterator<Resource> getOutput() {
+        Iterator<Resource> output = EMPTY_ITERATOR;
+        binding = null;
+        String jsonString = retrieveJSONString();
+        if (StringUtils.isNotBlank(jsonString)){
+            try {
+                JSONTokener tokener = new JSONTokener(jsonString);
+                char firstChar = tokener.next();
+                if (firstChar == '[') {
+                    binding = array = new JSONArray(jsonString);
+                    index = 0;
+                    output = new Iterator<Resource>() {
+                        @Override
+                        public boolean hasNext() {
+                            return index < array.length();
+                        }
+
+                        @Override
+                        public Resource next() {
+                            try {
+                                binding = array.get(index);
+                            } catch(Exception e){
+                                logger.error("Unable to retrieve {}nth item of jsonarray", index, e);
+                            }
+                            index++;
+                            return getInput();
+                        }
+                    };
+                } else if (firstChar == '{') {
+                    binding = new JSONObject(jsonString);
+                    output = super.getOutput();
+                } else {
+                    //simple string
+                    binding = jsonString;
+                    output = super.getOutput();
+                }
+            } catch (JSONException e) {
+                logger.error("unable to parse JSON {} ", jsonString, e);
+            }
+        }
+        return output;
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MovePipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MovePipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MovePipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MovePipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import org.apache.sling.api.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * Does a JCR Move of a node, returns the resource corresponding to the moved node
+ */
+public class MovePipe extends BasePipe {
+    Logger logger = LoggerFactory.getLogger(MovePipe.class);
+
+    public static final String RESOURCE_TYPE = "slingPipes/mv";
+
+    public MovePipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        return true;
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        Iterator<Resource> output = Collections.emptyIterator();
+        Resource resource = getInput();
+        if (resource != null && resource.adaptTo(Item.class) != null) {
+            String targetPath = getExpr();
+            try {
+                Session session = resolver.adaptTo(Session.class);
+                if (session.itemExists(targetPath)){
+                    logger.warn("{} already exists, nothing will be done here, nothing outputed");
+                } else {
+                    logger.info("moving resource {} to {}", resource.getPath(), targetPath);
+                    if (!isDryRun()) {
+                        if (resource.adaptTo(Node.class) != null) {
+                            session.move(resource.getPath(), targetPath);
+                        } else {
+                            int lastLevel = targetPath.lastIndexOf("/");
+                            // /a/b/c will get cut in /a/b for parent path, and c for name
+                            String parentPath = targetPath.substring(0, lastLevel);
+                            String name = targetPath.substring(lastLevel + 1, targetPath.length());
+                            Property sourceProperty = resource.adaptTo(Property.class);
+                            Node destNode = session.getNode(parentPath);
+                            if (sourceProperty.isMultiple()){
+                                destNode.setProperty(name, sourceProperty.getValues(), sourceProperty.getType());
+                            } else {
+                                destNode.setProperty(name, sourceProperty.getValue(), sourceProperty.getType());
+                            }
+                            sourceProperty.remove();
+                        }
+                        Resource target = resolver.getResource(targetPath);
+                        output = Collections.singleton(target).iterator();
+                    }
+                }
+            } catch (RepositoryException e){
+                logger.error("unable to move the resource", e);
+            }
+        } else {
+            logger.warn("bad configuration of the pipe, will do nothing");
+        }
+        return output;
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MultiPropertyPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MultiPropertyPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MultiPropertyPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/MultiPropertyPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+import org.apache.sling.api.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Value;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * reads input MV property, outputs N times the input parent node resource, where N is the number of
+ * values in the property, outputs each value in the bindings
+ */
+public class MultiPropertyPipe extends BasePipe {
+    private static Logger logger = LoggerFactory.getLogger(MultiPropertyPipe.class);
+    public static final String RESOURCE_TYPE = "slingPipes/multiProperty";
+
+    public MultiPropertyPipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+    }
+
+    MVResourceIterator iterator;
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        iterator = new MVResourceIterator(getInput());
+        return iterator;
+    }
+
+    @Override
+    public Object getOutputBinding() {
+        if (iterator != null) {
+            Value value = iterator.getCurrentValue();
+            try {
+                switch (value.getType()) {
+                    case PropertyType.STRING: {
+                        return value.getString();
+                    }
+                    default: {
+                        return value.toString();
+                    }
+                }
+            } catch (Exception e) {
+                logger.error("current value format is not supported", e);
+            }
+            return value.toString();
+        }
+        return null;
+    }
+
+    static class MVResourceIterator implements Iterator<Resource> {
+        Resource resource;
+        Value currentValue;
+        Iterator<Value> itValue = Collections.emptyIterator();
+
+        public MVResourceIterator(Resource resource){
+            this.resource = resource;
+            try {
+                Property mvProperty = resource.adaptTo(Property.class);
+                if (mvProperty == null) {
+                    throw new Exception("input resource " + resource.getPath() + " is supposed to be a property");
+                }
+                if (!mvProperty.isMultiple()) {
+                    throw new Exception("given property " + resource.getPath() + " is supposed to be multiple");
+                }
+                itValue = Arrays.asList(mvProperty.getValues()).iterator();
+            } catch (Exception e) {
+                logger.warn("unable to setup mv iterator for resource, will return nothing", e);
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            return itValue.hasNext();
+        }
+
+        public Value getCurrentValue() {
+            return currentValue;
+        }
+
+        @Override
+        public Resource next() {
+            if (itValue.hasNext()) {
+                currentValue = itValue.next();
+            }
+            return resource;
+        }
+
+        @Override
+        public void remove() {
+
+        }
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ParentPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ParentPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ParentPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ParentPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import org.apache.sling.api.resource.Resource;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * very simple pipe, returning parent resource of input resource
+ */
+public class ParentPipe extends BasePipe {
+
+    public static final String RESOURCE_TYPE = "slingPipes/parent";
+
+    public ParentPipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        Resource resource = getInput();
+        return Collections.singleton(resource.getParent()).iterator();
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PathPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PathPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PathPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PathPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import org.apache.sling.api.resource.Resource;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+/**
+ * creates or get given expression's path and returns corresponding resource
+ */
+public class PathPipe extends BasePipe {
+
+    public static final String RESOURCE_TYPE = "slingPipes/path";
+    public static final String PN_NODETYPE = "nodeType";
+    public static final String PN_AUTOSAVE = "autosave";
+
+    String nodeType;
+
+    boolean autosave;
+
+    public PathPipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+        nodeType = properties.get(PN_NODETYPE, "sling:Folder");
+        autosave = properties.get(PN_AUTOSAVE, true);
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        Iterator<Resource> output = Collections.emptyIterator();
+        String expression = getExpr();
+        Node leaf = null;
+        boolean transientChange = false;
+        try {
+            String relativePath = expression.substring(1);
+            Node parentNode = resolver.adaptTo(Session.class).getRootNode();
+            if (!parentNode.hasNode(relativePath)) {
+                Node node = parentNode;
+                int pos = relativePath.lastIndexOf('/');
+                if (pos != -1) {
+                    final StringTokenizer st = new StringTokenizer(relativePath.substring(0, pos), "/");
+                    while (st.hasMoreTokens()) {
+                        final String token = st.nextToken();
+                        if (!node.hasNode(token)) {
+                            try {
+                                node.addNode(token, nodeType);
+                                transientChange = true;
+                            } catch (RepositoryException re) {
+                                // we ignore this as this folder might be created from a different task
+                                node.refresh(false);
+                            }
+                        }
+                        node = node.getNode(token);
+                    }
+                    relativePath = relativePath.substring(pos + 1);
+                }
+                if (!node.hasNode(relativePath)) {
+                    node.addNode(relativePath, nodeType);
+                    transientChange = true;
+                }
+                leaf = node.getNode(relativePath);
+            }
+            if (leaf == null) {
+                leaf = parentNode.getNode(relativePath);
+            }
+            if (transientChange && autosave){
+                resolver.adaptTo(Session.class).save();
+            }
+            output =  Collections.singleton(resolver.getResource(leaf.getPath())).iterator();
+        } catch (RepositoryException e){
+            logger.error ("Not able to create path {}", expression, e);
+        }
+        return output;
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Pipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Pipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Pipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Pipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+import org.apache.sling.api.resource.Resource;
+
+import java.util.Iterator;
+
+/**
+ * Pipe interface
+ */
+public interface Pipe {
+    /**
+     * Name of the pipe
+     */
+    public static final String PN_NAME = "name";
+
+    /**
+     * expression of the pipe, usage depends on the pipe implementation
+     */
+    public static final String PN_EXPR = "expr";
+
+    /**
+     * resource's path associated to the path, usage depends on the pipe implementation
+     */
+    public static final String PN_PATH = "path";
+
+    /**
+     * Node name for the pipe's configuration
+     */
+    public static final String NN_CONF = "conf";
+
+    public static final String PN_DISTRIBUTION_AGENT = "distribution.agent";
+
+    /**
+     * returns true if that pipe will modify content during its execution
+     * @return
+     */
+    boolean modifiesContent();
+
+    /**
+     * returns true if that pipe is set not to write content
+     * @return
+     */
+    boolean isDryRun();
+
+    /**
+     * Return the name of that pipe
+     * @return
+     */
+    String getName();
+
+    /**
+     * Set parent
+     */
+    void setParent(ContainerPipe parent);
+
+    /**
+     * Return parent's pipe (can be null)
+     * @return
+     */
+    ContainerPipe getParent();
+
+    /**
+     * Get the pipe's optional configured resource or null
+     * @return
+     */
+    Resource getConfiguredInput();
+
+    /**
+     * Get pipe current's resource *before* next execution, meaning either the
+     * configured resource, either previous' pipe output resource
+     * @return
+     */
+    Resource getInput();
+
+    /**
+     * returns the binding output used in container pipe's expression
+     * @return
+     */
+    Object getOutputBinding();
+
+    /**
+     * returns the pipe's bindings
+     * @return
+     */
+    PipeBindings getBindings();
+
+    /**
+     * set the pipe's bindings
+     */
+    void setBindings(PipeBindings bindings);
+
+    /**
+     * Executes the pipe, can be contained in a parent or not
+     * @return
+     */
+    Iterator<Resource> getOutput();
+
+    /**
+     * Get Distribution agent
+     */
+    String getDistributionAgent();
+}
\ No newline at end of file