org.apache.sling.pipes.PipeBindings.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sling.pipes.PipeBindings.java

Source

/*
 * 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("\\$\\{(([^\\{^\\}]*(\\{[0-9,]+\\})?)*)\\}");

    /**
     * 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);
    }

    public void copyBindings(PipeBindings original) {
        getBindings().putAll(original.getBindings());
    }

    /**
     * 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) {
        log.debug("Adding binding {}={}", name, 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) {
        Matcher matcher = INJECTED_SCRIPT.matcher(expr);
        if (INJECTED_SCRIPT.matcher(expr).find()) {
            StringBuilder expression = new StringBuilder();
            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();
        }
        return null;
    }

    /**
     *
     * @param expr
     * @return
     * @throws ScriptException
     */
    protected Object evaluate(String expr) throws ScriptException {
        String computed = computeECMA5Expression(expr);
        if (computed != null) {
            //computed is null in case expr is a simple string
            return engine.eval(computed, scriptContext);
        }
        return expr;
    }

    /**
     * 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 != null && !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();
    }
}