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 [2/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...

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBindings.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBindings.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBindings.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PipeBindings.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,256 @@
+/*
+ * 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.io.IOUtils;
+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 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.net.URL;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Execution bindings of a pipe
+ */
+public class PipeBindings {
+    private static final Logger log = LoggerFactory.getLogger(PipeBindings.class);
+
+    public static final String NN_ADDITIONALBINDINGS = "additionalBindings";
+
+    public static final String PN_ADDITIONALSCRIPTS = "additionalScripts";
+
+    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
+
+    ScriptContext scriptContext = new SimpleScriptContext();
+
+    public static final String PATH_BINDING = "path";
+
+    Map<String, String> pathBindings = new HashMap<>();
+
+    Map<String, Resource> outputResources = new HashMap<>();
+
+    private static final Pattern INJECTED_SCRIPT = Pattern.compile("\\$\\{([^\\}]*)\\}");
+
+    /**
+     * public constructor
+     */
+    public PipeBindings(Resource resource){
+        engine.setContext(scriptContext);
+        //add path bindings where path.MyPipe will give MyPipe current resource path
+        getBindings().put(PATH_BINDING, pathBindings);
+
+        //additional bindings (global variables to use in child pipes expressions)
+        Resource additionalBindings = resource.getChild(NN_ADDITIONALBINDINGS);
+        if (additionalBindings != null) {
+            ValueMap bindings = additionalBindings.adaptTo(ValueMap.class);
+            addBindings(bindings);
+        }
+
+        Resource scriptsResource = resource.getChild(PN_ADDITIONALSCRIPTS);
+        if (scriptsResource != null) {
+            String[] scripts = scriptsResource.adaptTo(String[].class);
+            if (scripts != null) {
+                for (String script : scripts){
+                    addScript(resource.getResourceResolver(), script);
+                }
+            }
+        }
+    }
+
+    /**
+     * add a script file to the engine
+     * @param resolver
+     * @param path
+     */
+    public void addScript(ResourceResolver resolver, String path) {
+        InputStream is = null;
+        try {
+            if (path.startsWith("http")) {
+                try {
+                    URL remoteScript = new URL(path);
+                    is = remoteScript.openStream();
+                } catch (Exception e) {
+                    log.error("unable to retrieve remote script", e);
+                }
+            } else if (path.startsWith("/")) {
+                Resource scriptResource = resolver.getResource(path);
+                if (scriptResource != null) {
+                    is = scriptResource.adaptTo(InputStream.class);
+                }
+            }
+            if (is != null) {
+                try {
+                    engine.eval(new InputStreamReader(is), scriptContext);
+                } catch (Exception e) {
+                    log.error("unable to execute {}", path);
+                }
+            }
+        } finally {
+            IOUtils.closeQuietly(is);
+        }
+    }
+
+    /**
+     * adds additional bindings (global variables to use in child pipes expressions)
+     * @param bindings
+     */
+    public void addBindings(Map bindings) {
+        log.info("Adding bindings {}", bindings);
+        getBindings().putAll(bindings);
+    }
+
+    /**
+     * Update current resource of a given pipe, and appropriate binding
+     * @param pipe
+     * @param resource
+     */
+    public void updateBindings(Pipe pipe, Resource resource) {
+        outputResources.put(pipe.getName(), resource);
+        if (resource != null) {
+            pathBindings.put(pipe.getName(), resource.getPath());
+        }
+        addBinding(pipe.getName(), pipe.getOutputBinding());
+    }
+
+    public void addBinding(String name, Object value){
+        getBindings().put(name, value);
+    }
+
+    public boolean isBindingDefined(String name){
+        return getBindings().containsKey(name);
+    }
+
+    public Bindings getBindings() {
+        return scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
+    }
+
+    /**
+     * Doesn't look like nashorn likes template strings :-(
+     * @param expr
+     * @return
+     */
+    protected String computeECMA5Expression(String expr){
+        StringBuilder expression = new StringBuilder();
+        Matcher matcher = INJECTED_SCRIPT.matcher(expr);
+        int start = 0;
+        while (matcher.find()){
+            if (matcher.start() > start){
+                if (expression.length() == 0){
+                    expression.append("'");
+                }
+                expression.append(expr.substring(start,matcher.start()));
+            }
+            if (expression.length() > 0){
+                expression.append("' + ");
+            }
+            expression.append(matcher.group(1));
+            start = matcher.end();
+            if (start < expr.length()){
+                expression.append(" + '");
+            }
+        }
+        if (start < expr.length()){
+             expression.append(expr.substring(start) + "'");
+        }
+        return expression.toString();
+    }
+
+    /**
+     *
+     * @param expr
+     * @return
+     * @throws ScriptException
+     */
+    private Object evaluate(String expr) throws ScriptException {
+        return engine.eval(computeECMA5Expression(expr), scriptContext);
+    }
+
+    /**
+     * Expression is a function of variables from execution context, that
+     * we implement here as a String
+     * @param expr
+     * @return
+     */
+    public String instantiateExpression(String expr){
+        try {
+            return (String)evaluate(expr);
+        } catch (ScriptException e) {
+            log.error("Unable to evaluate the script", e);
+        }
+        return expr;
+    }
+
+    /**
+     * Instantiate object from expression
+     * @param expr
+     * @return
+     */
+    public Object instantiateObject(String expr){
+        try {
+            Object result = evaluate(expr);
+            if (! result.getClass().getName().startsWith("java.lang.")) {
+                //special case of the date in which case jdk.nashorn.api.scripting.ScriptObjectMirror will
+                //be returned
+                JsDate jsDate = ((Invocable) engine).getInterface(result, JsDate.class);
+                if (jsDate != null ) {
+                    Date date = new Date(jsDate.getTime() + jsDate.getTimezoneOffset() * 60 * 1000);
+                    Calendar cal = Calendar.getInstance();
+                    cal.setTime(date);
+                    return cal;
+                }
+            }
+            return result;
+        } catch (ScriptException e) {
+            log.error("Unable to evaluate the script for expr {} ", expr, e);
+        }
+        return expr;
+    }
+
+    /**
+     *
+     * @param name
+     * @return
+     */
+    public Resource getExecutedResource(String name) {
+        return outputResources.get(name);
+    }
+
+    /**
+     * interface mapping a javascript date
+     */
+    public interface JsDate {
+        long getTime();
+        int getTimezoneOffset();
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Plumber.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Plumber.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Plumber.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/Plumber.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,67 @@
+/*
+ * 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.ResourceResolver;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Plumber is an osgi service aiming to make pipes available to the sling system, in order to
+ */
+public interface Plumber {
+
+    public static final String RESOURCE_TYPE = "slingPipes/plumber";
+
+    /**
+     * Instantiate a pipe from the given resource and returns it
+     * @param resource
+     * @return
+     */
+    Pipe getPipe(Resource resource);
+
+    /**
+     * Executes a pipe at a certain path
+     * @param resolver resource resolver with which pipe will be executed
+     * @param path path of a valid pipe configuration
+     * @param bindings bindings to add to the execution of the pipe, can be null
+     * @param save in case that pipe writes anything, wether the plumber should save changes or not
+     *
+     * @return
+     */
+    Set<String> execute(ResourceResolver resolver, String path, Map bindings, boolean save) throws Exception;
+
+    /**
+     * Executes a given pipe
+     * @param resolver resource resolver with which pipe will be executed
+     * @param pipe pipe to execute
+     * @param bindings bindings to add to the execution of the pipe, can be null
+     * @param save in case that pipe writes anything, wether the plumber should save changes or not
+     *
+     * @return
+     */
+    Set<String> execute(ResourceResolver resolver, Pipe pipe, Map bindings, boolean save) throws Exception;
+
+    /**
+     * Registers
+     * @param type
+     * @param pipeClass
+     */
+    void registerPipe(String type, Class<? extends BasePipe> pipeClass);
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PlumberServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PlumberServlet.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PlumberServlet.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/PlumberServlet.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,148 @@
+/*
+ * 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.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.commons.json.io.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Servlet executing plumber for a pipe path given as 'path' parameter,
+ * it can also be launched against a container pipe resource directly (no need for path parameter)
+ *
+ */
+@SlingServlet(resourceTypes = {Plumber.RESOURCE_TYPE,
+        ContainerPipe.RESOURCE_TYPE,
+        AuthorizablePipe.RESOURCE_TYPE,
+        WritePipe.RESOURCE_TYPE,
+        SlingQueryPipe.RESOURCE_TYPE},
+        methods={"GET","POST"},
+        extensions = {"json"})
+public class PlumberServlet extends SlingAllMethodsServlet {
+    Logger log = LoggerFactory.getLogger(this.getClass());
+
+    protected static final String PARAM_PATH = "path";
+
+    public static final String PATH_KEY = "path";
+
+    protected static final String PARAM_BINDINGS = "bindings";
+
+    protected static final String PARAM_WRITER = "writer";
+
+    @Reference
+    Plumber plumber;
+
+    @Override
+    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
+        execute(request, response, false);
+    }
+
+    @Override
+    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
+        execute(request, response, true);
+    }
+
+    protected void execute(SlingHttpServletRequest request, SlingHttpServletResponse response, boolean writeAllowed) throws ServletException {
+        String path = request.getResource().getResourceType().equals(Plumber.RESOURCE_TYPE) ? request.getParameter(PARAM_PATH) : request.getResource().getPath();
+        try {
+            if (StringUtils.isBlank(path)) {
+                throw new Exception("path should be provided");
+            }
+            String dryRun = request.getParameter(BasePipe.DRYRUN_KEY);
+            String paramBindings = request.getParameter(PARAM_BINDINGS);
+
+            Map additionalBindings = null;
+            if (StringUtils.isNotBlank(dryRun) && dryRun.equals(Boolean.TRUE.toString())) {
+                additionalBindings = new HashMap<>();
+                additionalBindings.put(BasePipe.DRYRUN_KEY, true);
+            }
+            if (StringUtils.isNotBlank(paramBindings)){
+                try {
+                    JSONObject bindingJSON = new JSONObject(paramBindings);
+                    additionalBindings = additionalBindings != null ? additionalBindings: new HashMap<>();
+                    for (Iterator<String> keys = bindingJSON.keys(); keys.hasNext();){
+                        String key = keys.next();
+                        additionalBindings.put(key, bindingJSON.get(key));
+                    }
+                } catch (Exception e){
+                    log.error("Unable to retrieve bindings information", e);
+                }
+            }
+
+            String paramWriter = request.getParameter(PARAM_WRITER);
+            JSONObject writerObj = null;
+            if (StringUtils.isNotBlank(paramWriter)){
+                try {
+                    writerObj = new JSONObject(paramWriter);
+                } catch (Exception e) {
+                    log.error("Unable to retrieve the writer object");
+                }
+            }
+            response.setCharacterEncoding("utf-8");
+            response.setContentType("application/json");
+            JSONWriter writer = new JSONWriter(response.getWriter());
+            ResourceResolver resolver = request.getResourceResolver();
+            Resource pipeResource = resolver.getResource(path);
+            Pipe pipe = plumber.getPipe(pipeResource);
+            if (!writeAllowed && pipe.modifiesContent()) {
+                throw new Exception("This pipe modifies content, you should use a POST request");
+            }
+            writer.array();
+            if (writerObj != null) {
+                pipe.getBindings().addBindings(additionalBindings);
+                Iterator<Resource> resourceIterator = pipe.getOutput();
+                while (resourceIterator.hasNext()){
+                    Resource resource = resourceIterator.next();
+                    writer.object();
+                    writer.key(PATH_KEY).value(resource.getPath());
+                    Iterator<String> keys = writerObj.keys();
+                    while (keys.hasNext()){
+                        String key = keys.next();
+                        writer.key(key).value(pipe.getBindings().instantiateObject(writerObj.getString(key)));
+                    }
+                    writer.endObject();
+                }
+            } else {
+                Set<String> resources = plumber.execute(resolver, pipe, additionalBindings, true);
+                for (String resource : resources) {
+                    writer.value(resource);
+                }
+            }
+            writer.endArray();
+            response.flushBuffer();
+        } catch (Exception e) {
+            throw new ServletException(e);
+        }
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ReferencePipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ReferencePipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ReferencePipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/ReferencePipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * executes a pipe referred in the configuration
+ */
+public class ReferencePipe extends BasePipe {
+    public static final String RESOURCE_TYPE = "slingPipes/reference";
+
+    Pipe reference;
+
+    public ReferencePipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+        Resource pipeResource = resolver.getResource(getPath());
+        if (pipeResource == null){
+            throw new Exception("Reference configuration error: There is no resource at " + getPath());
+        }
+        reference = plumber.getPipe(pipeResource);
+        if (reference == null){
+            throw new Exception("Unable to build out pipe out of " + getPath());
+        }
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        return reference.getOutput();
+    }
+
+    @Override
+    public void setParent(ContainerPipe parent) {
+        super.setParent(parent);
+        reference.setParent(parent);
+    }
+
+    @Override
+    public Object getOutputBinding() {
+        return reference.getOutputBinding();
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        return reference.modifiesContent();
+    }
+
+    @Override
+    public Resource getInput() {
+        return reference.getInput();
+    }
+
+    @Override
+    public Resource getConfiguredInput() {
+        return reference.getConfiguredInput();
+    }
+}
\ No newline at end of file

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/RemovePipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/RemovePipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/RemovePipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/RemovePipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,126 @@
+/*
+ * 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 javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * this pipe tries to remove the input resource, abstracting its type,
+ * returning parent of the input
+ */
+public class RemovePipe extends BasePipe {
+    private static Logger logger = LoggerFactory.getLogger(RemovePipe.class);
+    public static final String RESOURCE_TYPE = "slingPipes/rm";
+
+    /**
+     * In case input resource is a node and configuration is set, only configured properties,
+     * and subtrees will be removed
+     */
+    Resource filter;
+
+    public RemovePipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+        filter = getConfiguration();
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        return true;
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        Resource resource = getInput();
+        String parentPath = null;
+        try {
+            if (resource.adaptTo(Node.class) != null) {
+                parentPath = removeTree(resource, filter);
+            } else if (resource.adaptTo(Property.class) != null){
+                Property property = resource.adaptTo(Property.class);
+                parentPath = property.getParent().getPath();
+                logger.info("removing property {}", property.getPath());
+                if (!isDryRun()){
+                    property.remove();
+                }
+            }
+        } catch (RepositoryException e){
+            logger.error("unable to remove current resource {}", resource.getPath(), e);
+        }
+        if (parentPath != null) {
+            return Collections.singleton(resolver.getResource(parentPath)).iterator();
+        }
+        return Collections.emptyIterator();
+    }
+
+    /**
+     * remove properties, returns the number of properties that were configured to be removed
+     * @return
+     */
+    private int removeProperties(Resource resource, Resource configuration) throws RepositoryException {
+        int count = 0;
+        if (configuration != null) {
+            Node node = resource.adaptTo(Node.class);
+            ValueMap configuredProperties = configuration.adaptTo(ValueMap.class);
+            for (String key : configuredProperties.keySet()){
+                if (! IGNORED_PROPERTIES.contains(key)){
+                    count++;
+                    if (node.hasProperty(key)){
+                        logger.info("removing {}", node.getProperty(key).getPath());
+                        if (! isDryRun()){
+                            node.getProperty(key).remove();
+                        }
+                    }
+                }
+            }
+        }
+        return count;
+    }
+
+    private String removeTree(Resource resource, Resource configuration) throws RepositoryException {
+        String remainingPath = resource.getPath();
+        int configuredProperties = removeProperties(resource, configuration);
+        Node configuredNode = configuration != null ? configuration.adaptTo(Node.class) : null;
+        NodeIterator childrenConfiguration = configuredNode != null ? configuredNode.getNodes() : null;
+        if (childrenConfiguration == null || (! childrenConfiguration.hasNext() && configuredProperties == 0)){
+            //explicit configuration to remove the node altogether
+            logger.info("removing {}", resource.getPath());
+            remainingPath = resource.getParent().getPath();
+            if (! isDryRun()){
+                resource.adaptTo(Node.class).remove();
+            }
+        } else {
+            while (childrenConfiguration.hasNext()){
+                Node childConf = childrenConfiguration.nextNode();
+                Resource child = resource.getChild(childConf.getName());
+                if (child != null){
+                    removeTree(child, configuration.getChild(childConf.getName()));
+                }
+            }
+        }
+        return remainingPath;
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/SlingQueryPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/SlingQueryPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/SlingQueryPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/SlingQueryPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,56 @@
+/*
+ * 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.query.SlingQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.sling.query.SlingQuery.$;
+
+import java.util.Iterator;
+
+/**
+ * this pipe uses SlingQuery to filters children (filter defined in expr property) of
+ * a resource (defined in the path property)
+ */
+public class SlingQueryPipe extends BasePipe {
+    private static Logger logger = LoggerFactory.getLogger(SlingQueryPipe.class);
+
+    public final static String RESOURCE_TYPE = "slingPipes/slingQuery";
+
+    public SlingQueryPipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        return false;
+    }
+
+    public Iterator<Resource> getOutput() {
+        Resource resource = getInput();
+        if (resource != null) {
+            String queryExpression = getExpr();
+            SlingQuery query = $(resource).children(getExpr());
+            logger.info("[sling query]: executing $({}).children({})", resource.getPath(), queryExpression);
+            return query.iterator();
+        }
+        return EMPTY_ITERATOR;
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/WritePipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/WritePipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/WritePipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/WritePipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,172 @@
+/*
+ * 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.ModifiableValueMap;
+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 javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * pipe that writes to configured resource
+ */
+public class WritePipe extends BasePipe {
+    private static final Logger logger = LoggerFactory.getLogger(WritePipe.class);
+    public static final String RESOURCE_TYPE = "slingPipes/write";
+    Node confTree;
+    String resourceExpression;
+    Pattern addPatch = Pattern.compile("\\+\\[(.*)\\]");
+    Pattern multi = Pattern.compile("\\[(.*)\\]");
+
+    public WritePipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+        if (getConfiguration() == null){
+            throw new Exception("write pipe is misconfigured: it should have a configuration node");
+        }
+        confTree = getConfiguration().adaptTo(Node.class);
+        resourceExpression = getPath();
+    }
+
+    /**
+     * convert the configured value in an actual one
+     * @param expression
+     * @return
+     */
+    protected Object computeValue(Resource resource, String key, Object expression) {
+        if (expression instanceof String) {
+            Object value = bindings.instantiateObject((String) expression);
+            if (value != null && value instanceof String) {
+                //in that case we treat special case like MV or patches
+                String sValue = (String)value;
+                Matcher patch = addPatch.matcher(sValue);
+                if (patch.matches()) {
+                    String newValue = patch.group(1);
+                    String[] actualValues = resource.adaptTo(ValueMap.class).get(key, String[].class);
+                    List<String> newValues = actualValues != null ? new LinkedList<>(Arrays.asList(actualValues)) : new ArrayList<String>();
+                    if (!newValues.contains(newValue)) {
+                        newValues.add(newValue);
+                    }
+                    return newValues.toArray(new String[newValues.size()]);
+                }
+                Matcher multiMatcher = multi.matcher(sValue);
+                if (multiMatcher.matches()) {
+                    return multiMatcher.group(1).split(",");
+                }
+            }
+            return value;
+        }
+        return expression;
+    }
+
+    @Override
+    public boolean modifiesContent() {
+        return true;
+    }
+
+    /**
+     * Write properties from the configuration to the target resource,
+     * instantiating both property names & values
+     *
+     * @param conf
+     * @param target
+     * @throws RepositoryException
+     */
+    private void copyProperties(Resource conf, Resource target) throws RepositoryException {
+        ValueMap writeMap = conf.adaptTo(ValueMap.class);
+        ModifiableValueMap properties = target.adaptTo(ModifiableValueMap.class);
+
+        //writing current node
+        if (properties != null && writeMap != null) {
+            for (Map.Entry<String, Object> entry : writeMap.entrySet()) {
+                if (!IGNORED_PROPERTIES.contains(entry.getKey())) {
+                    String key = parent != null ? bindings.instantiateExpression(entry.getKey()) : entry.getKey();
+                    Object value = computeValue(target, key, entry.getValue());
+                    if (value == null) {
+                        //null value are not handled by modifiable value maps,
+                        //removing the property if it exists
+                        Resource propertyResource = resource.getChild(key);
+                        if (propertyResource != null) {
+                            logger.info("removing {}", propertyResource.getPath());
+                            if (!isDryRun()){
+                                Property property = propertyResource.adaptTo(Property.class);
+                                if (property != null) {
+                                    property.remove();
+                                }
+                            }
+                        }
+                    } else {
+                        logger.info("writing {}={}",target.getPath() + "@" + key, value);
+                        if (!isDryRun()){
+                            properties.put(key, value);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * write the configured tree at the target resource, creating each node if needed, copying values.
+     * @param conf
+     * @return
+     */
+    private void writeTree(Node conf, Resource target) throws RepositoryException {
+        copyProperties(resolver.getResource(conf.getPath()), target);
+        NodeIterator childrenConf = conf.getNodes();
+        if (childrenConf.hasNext()){
+            Node targetNode = target.adaptTo(Node.class);
+            while (childrenConf.hasNext()){
+                Node childConf = childrenConf.nextNode();
+                String name = childConf.getName();
+                logger.info("dubbing {} at {}", conf.getPath(), target.getPath());
+                if (!isDryRun()){
+                    Node childTarget = targetNode.hasNode(name) ? targetNode.getNode(name) : targetNode.addNode(name, childConf.getPrimaryNodeType().getName());
+                    writeTree(childConf, resolver.getResource(childTarget.getPath()));
+                }
+            }
+        }
+    }
+
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        try {
+            Resource resource = getInput();
+            if (resource != null) {
+                writeTree(confTree, resource);
+                return super.getOutput();
+            }
+        } catch (Exception e) {
+            logger.error("unable to write values, cutting pipe", e);
+        }
+        return EMPTY_ITERATOR;
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/XPathPipe.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/XPathPipe.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/XPathPipe.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/XPathPipe.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,44 @@
+/*
+ * 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 javax.jcr.query.Query;
+import java.util.Iterator;
+
+/**
+ * generates output based on an xpath query (no input is considered)
+ */
+public class XPathPipe extends BasePipe {
+
+    public static final String RESOURCE_TYPE = "slingPipes/xpath";
+
+    public XPathPipe(Plumber plumber, Resource resource) throws Exception {
+        super(plumber, resource);
+    }
+
+    @Override
+    public Iterator<Resource> getOutput() {
+        String query = getExpr();
+        if (StringUtils.isNotBlank(query)){
+            return resource.getResourceResolver().findResources(query, Query.XPATH);
+        }
+        return EMPTY_ITERATOR;
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/impl/PlumberImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/impl/PlumberImpl.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/impl/PlumberImpl.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/main/java/org/apache/sling/pipes/impl/PlumberImpl.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,150 @@
+/*
+ * 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.impl;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.distribution.DistributionRequest;
+import org.apache.sling.distribution.DistributionRequestType;
+import org.apache.sling.distribution.DistributionResponse;
+import org.apache.sling.distribution.Distributor;
+import org.apache.sling.distribution.SimpleDistributionRequest;
+import org.apache.sling.pipes.AuthorizablePipe;
+import org.apache.sling.pipes.BasePipe;
+import org.apache.sling.pipes.ContainerPipe;
+import org.apache.sling.pipes.FilterPipe;
+import org.apache.sling.pipes.PathPipe;
+import org.apache.sling.pipes.JsonPipe;
+import org.apache.sling.pipes.MovePipe;
+import org.apache.sling.pipes.MultiPropertyPipe;
+import org.apache.sling.pipes.ParentPipe;
+import org.apache.sling.pipes.Pipe;
+import org.apache.sling.pipes.Plumber;
+import org.apache.sling.pipes.ReferencePipe;
+import org.apache.sling.pipes.RemovePipe;
+import org.apache.sling.pipes.SlingQueryPipe;
+import org.apache.sling.pipes.WritePipe;
+import org.apache.sling.pipes.XPathPipe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Filter;
+
+/**
+ * implements plumber interface, and registers default pipes
+ */
+@Component
+@Service
+public class PlumberImpl implements Plumber {
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    Map<String, Class<? extends BasePipe>> registry;
+
+    @Reference (policy= ReferencePolicy.DYNAMIC, cardinality= ReferenceCardinality.OPTIONAL_UNARY)
+    protected volatile Distributor distributor = null;
+
+    @Activate
+    public void activate(){
+        registry = new HashMap<>();
+        registerPipe(BasePipe.RESOURCE_TYPE, BasePipe.class);
+        registerPipe(ContainerPipe.RESOURCE_TYPE, ContainerPipe.class);
+        registerPipe(SlingQueryPipe.RESOURCE_TYPE, SlingQueryPipe.class);
+        registerPipe(WritePipe.RESOURCE_TYPE, WritePipe.class);
+        registerPipe(JsonPipe.RESOURCE_TYPE, JsonPipe.class);
+        registerPipe(MultiPropertyPipe.RESOURCE_TYPE, MultiPropertyPipe.class);
+        registerPipe(AuthorizablePipe.RESOURCE_TYPE, AuthorizablePipe.class);
+        registerPipe(XPathPipe.RESOURCE_TYPE, XPathPipe.class);
+        registerPipe(ReferencePipe.RESOURCE_TYPE, ReferencePipe.class);
+        registerPipe(RemovePipe.RESOURCE_TYPE, RemovePipe.class);
+        registerPipe(ParentPipe.RESOURCE_TYPE, ParentPipe.class);
+        registerPipe(MovePipe.RESOURCE_TYPE, MovePipe.class);
+        registerPipe(PathPipe.RESOURCE_TYPE, PathPipe.class);
+        registerPipe(FilterPipe.RESOURCE_TYPE, FilterPipe.class);
+    }
+
+    @Override
+    public Pipe getPipe(Resource resource) {
+        if ((resource == null) || !registry.containsKey(resource.getResourceType())) {
+            log.error("Pipe configuration resource is either null, or its type is not registered");
+        } else {
+            try {
+                Class<? extends Pipe> pipeClass = registry.get(resource.getResourceType());
+                return pipeClass.getDeclaredConstructor(Plumber.class, Resource.class).newInstance(this, resource);
+            } catch (Exception e) {
+                log.error("Unable to properly instantiate the pipe configured in {}", resource.getPath(), e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Set<String> execute(ResourceResolver resolver, String path, Map additionalBindings, boolean save) throws Exception {
+        Resource pipeResource = resolver.getResource(path);
+        Pipe pipe = getPipe(pipeResource);
+        if (pipe == null) {
+            throw new Exception("unable to build pipe based on configuration at " + path);
+        }
+        return execute(resolver, pipe, additionalBindings, save);
+    }
+
+    @Override
+    public Set<String> execute(ResourceResolver resolver, Pipe pipe, Map additionalBindings, boolean save) throws Exception {
+        if (additionalBindings != null && pipe instanceof ContainerPipe){
+            pipe.getBindings().addBindings(additionalBindings);
+        }
+
+        log.info("[{}] execution starts, save ({})", pipe, save);
+        Set<String> set = new HashSet<>();
+        for (Iterator<Resource> it = pipe.getOutput(); it.hasNext();){
+            Resource resource = it.next();
+            if (resource != null) {
+                log.debug("[{}] retrieved {}", pipe.getName(), resource.getPath());
+                set.add(resource.getPath());
+            }
+        }
+        if  (pipe.modifiesContent() && save && resolver.hasChanges() && !pipe.isDryRun()){
+            log.info("[{}] saving changes...", pipe.getName());
+            resolver.commit();
+            if (distributor != null && StringUtils.isNotBlank(pipe.getDistributionAgent())) {
+                log.info("a distribution agent is configured, will try to distribute the changes");
+                DistributionRequest request = new SimpleDistributionRequest(DistributionRequestType.ADD, true, set.toArray(new String[set.size()]));
+                DistributionResponse response = distributor.distribute(pipe.getDistributionAgent(), resolver, request);
+                log.info("distribution response : {}", response);
+            }
+        }
+        log.info("[{}] done executing.", pipe.getName());
+        return set;
+    }
+
+    @Override
+    public void registerPipe(String type, Class<? extends BasePipe> pipeClass) {
+        registry.put(type, pipeClass);
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/AbstractPipeTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/AbstractPipeTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/AbstractPipeTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/AbstractPipeTest.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,55 @@
+/*
+ * 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.pipes.dummies.DummyNull;
+import org.apache.sling.pipes.dummies.DummySearch;
+import org.apache.sling.pipes.impl.PlumberImpl;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+
+/**
+ * this abstract class for pipes implements a plumber with all registered pipes, plus some test ones, and give some paths,
+ * it also provides a testing Sling Context, with some content.
+ */
+public class AbstractPipeTest {
+
+    protected static final String PATH_PIPE = "/etc/pipe";
+    protected static final String PATH_FRUITS = "/content/fruits";
+    protected static final String PATH_BANANA = PATH_FRUITS + "/banana";
+    protected static final String PATH_APPLE = PATH_FRUITS + "/apple";
+    protected static final String NN_SIMPLE = "simple";
+    protected static final String NN_COMPLEX = "complex";
+    protected static final String PN_INDEX = "/index";
+    Plumber plumber;
+
+    @Rule
+    public SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK);
+
+    @Before
+    public void setup(){
+        PlumberImpl plumberImpl = new PlumberImpl();
+        plumberImpl.activate();
+        plumberImpl.registerPipe("slingPipes/dummyNull", DummyNull.class);
+        plumberImpl.registerPipe("slingPipes/dummySearch", DummySearch.class);
+        plumber = plumberImpl;
+        context.load().json("/fruits.json", PATH_FRUITS);
+    }
+
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/ContainerPipeTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/ContainerPipeTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/ContainerPipeTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/ContainerPipeTest.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.junit.Before;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * testing container with dummy child pipes
+ */
+public class ContainerPipeTest extends AbstractPipeTest {
+
+    public static final String NN_DUMMYTREE = "dummyTree";
+    public static final String NN_OTHERTREE = "otherTree";
+    public static final String NN_ROTTENTREE = "rottenTree";
+    public static final String NN_ONEPIPE = "onePipeContainer";
+
+    @Before
+    public void setup() {
+        super.setup();
+        context.load().json("/container.json", PATH_PIPE);
+    }
+
+    @Test
+    public void testDummyTree() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_DUMMYTREE);
+        ContainerPipe pipe = (ContainerPipe)plumber.getPipe(resource);
+        assertNotNull("A container pipe should be built out from the given configuration", pipe);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertTrue("There should be some results", resourceIterator.hasNext());
+        Resource firstResource = resourceIterator.next();
+        assertNotNull("First resource should not be null", firstResource);
+        assertEquals("First resource should be instantiated path with apple & pea",
+                PATH_FRUITS + "/apple/isnota/pea/buttheyhavesamecolor",
+                firstResource.getPath());
+        assertTrue("There should still be another item", resourceIterator.hasNext());
+        Resource secondResource = resourceIterator.next();
+        assertNotNull("Second resource should not be null", secondResource);
+        assertEquals("Second resource should be instantiated path with apple & carrot",
+                PATH_FRUITS + "/apple/isnota/carrot/andtheircolorisdifferent",
+                secondResource.getPath());
+        assertTrue("There should still be another item", resourceIterator.hasNext());
+        Resource thirdResource = resourceIterator.next();
+        assertNotNull("Third resource should not be null", thirdResource);
+        assertEquals("Third resource should be instantiated path with banana & pea",
+                PATH_FRUITS + "/banana/isnota/pea/andtheircolorisdifferent",
+                thirdResource.getPath());
+        assertTrue("There should still be another item", resourceIterator.hasNext());
+        Resource fourthResource = resourceIterator.next();
+        assertNotNull("Fourth resource should not be null", fourthResource);
+        assertEquals("fourthResource resource should be instantiated path with banana & carrot",
+                PATH_FRUITS + "/banana/isnota/carrot/andtheircolorisdifferent",
+                fourthResource.getPath());
+        assertFalse("There should be no more items", resourceIterator.hasNext());
+    }
+
+    @Test
+    public void testOtherTree() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_OTHERTREE);
+        ContainerPipe pipe = (ContainerPipe)plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertTrue("There should be some results", resourceIterator.hasNext());
+        Resource firstResource = resourceIterator.next();
+        assertNotNull("First resource should not be null", firstResource);
+        assertEquals("First resource should be instantiated path with apple & pea",
+                PATH_FRUITS + "/apple/isnota/pea/buttheyhavesamecolor",
+                firstResource.getPath());
+        assertTrue("There should still be another item", resourceIterator.hasNext());
+        Resource secondResource = resourceIterator.next();
+        assertNotNull("Second resource should not be null", secondResource);
+        assertEquals("Second resource should be instantiated path with banana & pea",
+                PATH_FRUITS + "/banana/isnota/pea/andtheircolorisdifferent",
+                secondResource.getPath());
+        assertFalse("There should be no more items", resourceIterator.hasNext());
+    }
+
+    @Test
+    public void testRottenTree() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_ROTTENTREE);
+        ContainerPipe pipe = (ContainerPipe)plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertFalse("There shouldn't be any resource", resourceIterator.hasNext());
+    }
+
+    @Test
+    public void testOnePipe() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_ONEPIPE);
+        ContainerPipe pipe = (ContainerPipe)plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertTrue("There should be children", resourceIterator.hasNext());
+    }
+}
\ No newline at end of file

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/FilterPipeTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/FilterPipeTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/FilterPipeTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/FilterPipeTest.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.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.*;
+
+/**
+ * testing different kind of filters
+ */
+public class FilterPipeTest extends AbstractPipeTest {
+    public static final String NN_PROPERTIES = "properties";
+    public static final String NN_NOCHILDREN = "noChildrenPasses";
+    public static final String NN_NOCHILDREN_FAILS = "noChildrenVoid";
+    public static final String NN_TEST = "testPasses";
+    public static final String NN_TEST_FAILS = "testFails";
+    public void setup() {
+        super.setup();
+        context.load().json("/filter.json", PATH_PIPE);
+    }
+
+    @Test
+    public void testFilterProperties(){
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_PROPERTIES);
+        ContainerPipe pipe = (ContainerPipe)plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertTrue("output has one resource...", resourceIterator.hasNext());
+        ValueMap properties = resourceIterator.next().adaptTo(ValueMap.class);
+        assertFalse("...and only One", resourceIterator.hasNext());
+        assertEquals("output resource's color is green", "green", properties.get("color", String.class));
+        assertTrue("output resource's name is not void", StringUtils.isNotBlank(properties.get("name", String.class)));
+    }
+
+    @Test
+    public void testNoChildrenPasses(){
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_NOCHILDREN);
+        Pipe pipe = plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertTrue("output has one resource...", resourceIterator.hasNext());
+        resourceIterator.next().adaptTo(ValueMap.class);
+        assertFalse("...and only One", resourceIterator.hasNext());
+    }
+
+
+    @Test
+    public void testNoChildrenFails(){
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_NOCHILDREN_FAILS);
+        Pipe pipe = plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertFalse("output has no resource...", resourceIterator.hasNext());
+    }
+
+    @Test
+    public void testTestPasses() {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_TEST);
+        Pipe pipe = plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertTrue("output has one resource...", resourceIterator.hasNext());
+    }
+
+    @Test
+    public void testTestFails() {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_TEST_FAILS);
+        Pipe pipe = plumber.getPipe(resource);
+        Iterator<Resource> resourceIterator = pipe.getOutput();
+        assertFalse("output has no resource...", resourceIterator.hasNext());
+    }
+
+}
\ No newline at end of file

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/JsonPipeTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/JsonPipeTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/JsonPipeTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/JsonPipeTest.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,67 @@
+/*
+ * 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.junit.Before;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.*;
+
+/**
+ * testing json pipe with anonymous yahoo meteo API
+ */
+public class JsonPipeTest extends AbstractPipeTest {
+    public static final String CONF = "/content/json/conf/weather";
+    public static final String ARRAY = "/content/json/conf/array";
+
+    @Before
+    public void setup() {
+        super.setup();
+        context.load().json("/json.json", "/content/json");
+    }
+
+    @Test
+    public void testPipedJson() throws Exception{
+        Resource resource = context.resourceResolver().getResource(CONF);
+        Pipe pipe = plumber.getPipe(resource);
+        Iterator<Resource> outputs = pipe.getOutput();
+        outputs.next();
+        Resource result = outputs.next();
+        context.resourceResolver().commit();
+        ValueMap properties = result.adaptTo(ValueMap.class);
+        assertTrue("There should be a Paris property", properties.containsKey("Paris"));
+        assertTrue("There should be a Bucharest property", properties.containsKey("Bucharest"));
+    }
+
+    @Test
+    public void testPipedArray() throws Exception {
+        Resource resource = context.resourceResolver().getResource(ARRAY);
+        ContainerPipe pipe = (ContainerPipe)plumber.getPipe(resource);
+        Iterator<Resource> outputs = pipe.getOutput();
+        Resource first = outputs.next();
+        Resource second = outputs.next();
+        Resource third = outputs.next();
+        assertFalse("there should be only three elements", outputs.hasNext());
+        assertEquals("first resource should be one", "/content/json/array/one", first.getPath());
+        assertEquals("second resource should be two", "/content/json/array/two", second.getPath());
+        assertEquals("third resource should be three", "/content/json/array/three", third.getPath());
+    }
+}
\ No newline at end of file

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MovePipeTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MovePipeTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MovePipeTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MovePipeTest.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,70 @@
+/*
+ * 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.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import javax.jcr.Session;
+import java.util.Iterator;
+
+/**
+ * testing moving nodes & properties
+ */
+public class MovePipeTest extends AbstractPipeTest {
+
+    static final String MOVENODE_PIPE = "/moveNode";
+    static final String MOVEPROPERTY_PIPE = "/moveProperty";
+    static final String MOVED_NODE_PATH = "/granny";
+    static final String MOVED_PROPERTY_PATH = "/indexFruits";
+
+    @Before
+    public void setup() {
+        super.setup();
+        context.load().json("/move.json", PATH_PIPE);
+    }
+
+    @Ignore //move operation is not supported yet by MockSession
+    @Test
+    public void testMoveNode() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + MOVENODE_PIPE);
+        Pipe pipe = plumber.getPipe(resource);
+        Iterator<Resource> output = pipe.getOutput();
+        Assert.assertTrue(output.hasNext());
+        output.next();
+        Session session = context.resourceResolver().adaptTo(Session.class);
+        session.save();
+        Assert.assertTrue("new node path should exists", session.nodeExists(PATH_FRUITS + MOVED_NODE_PATH));
+    }
+
+    @Ignore //move operation is not supported yet by MockSession
+    @Test
+    public void testMoveProperty() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + MOVEPROPERTY_PIPE);
+        Pipe pipe = plumber.getPipe(resource);
+        Iterator<Resource> output = pipe.getOutput();
+        Assert.assertTrue(output.hasNext());
+        output.next();
+        Session session = context.resourceResolver().adaptTo(Session.class);
+        session.save();
+        Assert.assertTrue("new property path should exists", session.propertyExists(PATH_FRUITS + MOVED_NODE_PATH));
+        Assert.assertFalse("old property path should not", session.propertyExists(PATH_FRUITS + PN_INDEX));
+    }
+}
\ No newline at end of file

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MultiPropertyPipeTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MultiPropertyPipeTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MultiPropertyPipeTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/MultiPropertyPipeTest.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,69 @@
+/*
+ * 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.junit.Before;
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * testing normal case of MV Property Pipe
+ */
+public class MultiPropertyPipeTest extends AbstractPipeTest {
+
+    @Before
+    public void setup() {
+        super.setup();
+        context.load().json("/multiProperty.json", PATH_PIPE);
+    }
+
+    @Test
+    public void testMV() throws Exception{
+        Resource conf = context.resourceResolver().getResource(PATH_PIPE + "/working");
+        Pipe pipe = plumber.getPipe(conf);
+        Iterator<Resource> outputs = pipe.getOutput();
+        Resource resource = outputs.next();
+        assertNotNull(resource);
+        assertEquals("Resource path should be the input", PATH_FRUITS + PN_INDEX, resource.getPath());
+        String fruit = (String)pipe.getOutputBinding();
+        assertEquals("First resource binding should be apple", "apple", fruit);
+        resource = outputs.next();
+        assertNotNull(resource);
+        assertEquals("Resource path should be the input", PATH_FRUITS + PN_INDEX, resource.getPath());
+        fruit = (String)pipe.getOutputBinding();
+        assertEquals("Second resource binding should be banana", "banana", fruit);
+    }
+
+    @Test
+    public void testNonWorkingMV() throws Exception{
+        Resource conf = context.resourceResolver().getResource(PATH_PIPE + "/typo");
+        Pipe pipe = plumber.getPipe(conf);
+        assertFalse("There should not be next for a non existing resource", pipe.getOutput().hasNext());
+        conf = context.resourceResolver().getResource(PATH_PIPE + "/notProperty");
+        pipe = plumber.getPipe(conf);
+        assertFalse("There should not be next for a resource that is not a property", pipe.getOutput().hasNext());
+        conf = context.resourceResolver().getResource(PATH_PIPE + "/notMultiple");
+        pipe = plumber.getPipe(conf);
+        assertFalse("There should not be next for a property that is not multiple", pipe.getOutput().hasNext());
+    }
+}

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PipeBindingsTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PipeBindingsTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PipeBindingsTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PipeBindingsTest.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.junit.Before;
+import org.junit.Test;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ * testing binding's expressions instanciations
+ */
+public class PipeBindingsTest extends AbstractPipeTest {
+
+    private static final String NN_MOREBINDINGS = "moreBindings";
+
+    @Before
+    public void setup() {
+        super.setup();
+        context.load().json("/container.json", PATH_PIPE);
+    }
+
+    @Test
+    public void computeEcma5Expression() {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + ContainerPipeTest.NN_DUMMYTREE);
+        PipeBindings bindings = new PipeBindings(resource);
+        Map<String,String> expressions = new HashMap<>();
+        expressions.put("blah ${blah} blah", "'blah ' + blah + ' blah'");
+        expressions.put("${blah}", "blah");
+        expressions.put("${blah} blah", "blah + ' blah'");
+        expressions.put("blah ${blah}", "'blah ' + blah");
+        expressions.put("${blah}${blah}", "blah + '' + blah");
+        expressions.put("+[${blah}]", "'+[' + blah + ']'");
+        for (Map.Entry<String,String> test : expressions.entrySet()){
+            assertEquals(test.getKey() + " should be transformed in " + test.getValue(), test.getValue(), bindings.computeECMA5Expression(test.getKey()));
+        }
+    }
+
+    @Test
+    public void testInstantiateExpression() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + ContainerPipeTest.NN_DUMMYTREE);
+        PipeBindings bindings = new PipeBindings(resource);
+        Map<String, String> testMap = new HashMap<>();
+        testMap.put("a", "apricots");
+        testMap.put("b", "bananas");
+        bindings.getBindings().put("test", testMap);
+        String newExpression = bindings.instantiateExpression("${test.a} and ${test.b}");
+        assertEquals("expression should be correctly instantiated", "apricots and bananas", newExpression);
+    }
+
+    @Test
+    public void testInstantiateObject() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + ContainerPipeTest.NN_DUMMYTREE);
+        PipeBindings bindings = new PipeBindings(resource);
+        Map<String, String> testMap = new HashMap<>();
+        testMap.put("a", "apricots");
+        testMap.put("b", "bananas");
+        bindings.getBindings().put("test", testMap);
+        String newExpression = (String)bindings.instantiateObject("${test.a} and ${test.b}");
+        assertEquals("expression should be correctly instantiated", "apricots and bananas", newExpression);
+        Calendar cal = (Calendar)bindings.instantiateObject("${new Date(2012,04,12)}");
+        assertNotNull("calendar should be instantiated", cal);
+        assertEquals("year should be correct", 2012, cal.get(Calendar.YEAR));
+        assertEquals("month should be correct", 4, cal.get(Calendar.MONTH));
+        assertEquals("date should be correct", 11, cal.get(Calendar.DAY_OF_MONTH));
+    }
+
+    @Test
+    public void testAdditionalBindings() throws Exception {
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_MOREBINDINGS);
+        PipeBindings bindings = new PipeBindings(resource);
+        String expression = bindings.instantiateExpression("${three}");
+        assertEquals("computed expression should be taking additional bindings 'three' in account", "3", expression);
+    }
+
+    @Test
+    public void testAdditionalScript() throws Exception {
+        context.load().binaryFile("/testSum.js", "/content/test/testSum.js");
+        Resource resource = context.resourceResolver().getResource(PATH_PIPE + "/" + NN_MOREBINDINGS);
+        PipeBindings bindings = new PipeBindings(resource);
+        Number expression = (Number)bindings.instantiateObject("${testSumFunction(1,2)}");
+        assertEquals("computed expression have testSum script's functionavailable", 3, expression.intValue());
+    }
+}
\ No newline at end of file

Added: sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PlumberServletTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PlumberServletTest.java?rev=1718377&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PlumberServletTest.java (added)
+++ sling/trunk/contrib/extensions/sling-pipes/src/test/java/org/apache/sling/pipes/PlumberServletTest.java Mon Dec  7 14:19:52 2015
@@ -0,0 +1,188 @@
+/*
+ * 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.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+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.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * testing the servlet logic (parameters & output)
+ */
+public class PlumberServletTest  extends AbstractPipeTest {
+
+    String containersPath = PATH_PIPE + "/" + "containers";
+
+    String dummyTreePath = containersPath + "/" + ContainerPipeTest.NN_DUMMYTREE;
+
+    String writePath = PATH_PIPE + "/" + "write";
+
+    String pipedWritePath = writePath + "/" + WritePipeTest.NN_PIPED;
+
+    StringWriter stringResponse;
+
+    SlingHttpServletResponse response;
+
+    PlumberServlet servlet = new PlumberServlet();
+
+    @Before
+    public void setup() {
+        super.setup();
+        context.load().json("/plumber.json", PATH_PIPE);
+        context.load().json("/container.json", containersPath);
+        context.load().json("/write.json", writePath);
+        servlet.plumber = plumber;
+        stringResponse = new StringWriter();
+        try {
+            response = mockPlumberServletResponse(stringResponse);
+        } catch (Exception e){
+
+        }
+    }
+
+    private void assertDummyTree() throws JSONException {
+        String finalResponse = stringResponse.toString();
+        assertFalse("There should be a response", StringUtils.isBlank(finalResponse));
+        JSONArray array = new JSONArray(finalResponse);
+        assertEquals("response should be an array with 4 results", 4, array.length());
+    }
+
+    @Test
+    public void testDummyTreeThroughRT() throws Exception {
+        SlingHttpServletRequest request = mockPlumberServletRequest(context.resourceResolver(), dummyTreePath, null, null, null, null);
+        servlet.execute(request, response, false);
+        assertDummyTree();
+    }
+
+    @Test
+    public void testDummyTreeThroughPlumber() throws Exception {
+        SlingHttpServletRequest request = mockPlumberServletRequest(context.resourceResolver(), PATH_PIPE, dummyTreePath, null, null, null);
+        servlet.execute(request, response, false);
+        assertDummyTree();
+    }
+
+    @Test
+    public void testWriteExecute() throws ServletException {
+        SlingHttpServletRequest request = mockPlumberServletRequest(context.resourceResolver(), pipedWritePath, null, null, null, null);
+        servlet.execute(request, response, true);
+        String finalResponse = stringResponse.toString();
+        assertFalse("There should be a response", StringUtils.isBlank(finalResponse));
+    }
+
+    /**
+     * in this test we execute a pipe that modifies content, with a flag mocking the GET request:
+     * the execution should fail.
+     */
+    @Test
+    public void testGetOnWriteExecute() throws ServletException {
+        SlingHttpServletRequest request = mockPlumberServletRequest(context.resourceResolver(), pipedWritePath, null, null, null, null);
+        boolean hasFailed = true;
+        try {
+            servlet.execute(request, response, false);
+            hasFailed = false;
+        } catch (Exception e){
+
+        }
+        assertTrue("Execution should have failed", hasFailed);
+    }
+
+    @Test
+    public void testAdditionalBindingsAndWriter() throws Exception {
+        String testBinding = "testBinding";
+        String testBindingLength = testBinding + "Length";
+        String bindingValue = "testBindingValue";
+        String pathLengthParam = "pathLength";
+        JSONObject bindings = new JSONObject("{'" + testBinding + "':'" + bindingValue + "'}");
+        JSONObject respObject = new JSONObject("{'" + pathLengthParam + "':'${path.get(\"dummyGrandChild\").length}','" + testBindingLength + "':'${" + testBinding + ".length}'}");
+        SlingHttpServletRequest request =
+                mockPlumberServletRequest(context.resourceResolver(), dummyTreePath, null, bindings.toString(), respObject.toString(), null);
+        servlet.execute(request, response, false);
+        assertDummyTree();
+        JSONArray array = new JSONArray(stringResponse.toString());
+        for (int i = 0; i < array.length(); i++) {
+            JSONObject object = array.optJSONObject(i);
+            assertNotNull("there should be an object returned at each time", object);
+            String path = object.optString(PlumberServlet.PATH_KEY);
+            assertNotNull("the string path should be returned for each item, containing the path of the resource");
+            String pathLength = object.optString(pathLengthParam);
+            assertNotNull("there should be a pathLength param, as specified in the writer", pathLength);
+            assertEquals("Pathlength should be the string representation of the path length", path.length() + "", pathLength);
+            String testBindingLengthValue = object.optString(testBindingLength);
+            assertNotNull("testBindingLength should be there", testBindingLengthValue);
+            assertEquals("testBindingLength should be the string representation of the additional binding length",
+                    bindingValue.length() + "", testBindingLengthValue);
+        }
+    }
+
+    @Test
+    public void testDryRun() throws Exception {
+        SlingHttpServletRequest dryRunRequest =
+                mockPlumberServletRequest(context.resourceResolver(), pipedWritePath, null, null, null, "true");
+        servlet.execute(dryRunRequest, response, true);
+        Resource resource = context.resourceResolver().getResource("/content/fruits");
+        ValueMap properties = resource.adaptTo(ValueMap.class);
+        assertFalse("property fruits shouldn't have been written", properties.containsKey("fruits"));
+        SlingHttpServletRequest request =
+                mockPlumberServletRequest(context.resourceResolver(), pipedWritePath, null, null, null, "false");
+        servlet.execute(request, response, true);
+        WritePipeTest.assertPiped(resource);
+    }
+
+    public static SlingHttpServletRequest mockPlumberServletRequest(ResourceResolver resolver,
+                                                                    String path,
+                                                                    String pathParam,
+                                                                    String bindings,
+                                                                    String writer,
+                                                                    String dryRun){
+        SlingHttpServletRequest request = mock(SlingHttpServletRequest.class);
+        Resource resource = resolver.getResource(path);
+        when(request.getResourceResolver()).thenReturn(resolver);
+        when(request.getResource()).thenReturn(resource);
+        when(request.getParameter(PlumberServlet.PARAM_PATH)).thenReturn(pathParam);
+        when(request.getParameter(PlumberServlet.PARAM_BINDINGS)).thenReturn(bindings);
+        when(request.getParameter(PlumberServlet.PARAM_WRITER)).thenReturn(writer);
+        when(request.getParameter(BasePipe.DRYRUN_KEY)).thenReturn(dryRun);
+        return request;
+    }
+
+    public static SlingHttpServletResponse mockPlumberServletResponse(StringWriter writer) throws IOException {
+        SlingHttpServletResponse response = mock(SlingHttpServletResponse.class);
+        PrintWriter printWriter = new PrintWriter(writer);
+        when(response.getWriter()).thenReturn(printWriter);
+        return response;
+    }
+}