This is an automated email from the ASF dual-hosted git repository.
npeltier pushed a commit to branch master
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-pipes.git
The following commit(s) were added to refs/heads/master by this push:
new 9aad072 SLING-9556 introduce command execution service
9aad072 is described below
commit 9aad072373b23ca7531482893376a6b920d95c08
Author: Nicolas Peltier <[email protected]>
AuthorDate: Wed Aug 5 17:54:08 2020 +0200
SLING-9556 introduce command execution service
- moved GogoCommand facilities to command executor service, that it points
to,
- make command executor service also serving servlet,
- make the servlet accepting command as parameter or file, considering only
'run' commands
---
.../org/apache/sling/pipes/CommandExecutor.java | 48 ++
.../java/org/apache/sling/pipes/PipeBindings.java | 2 +-
src/main/java/org/apache/sling/pipes/Plumber.java | 15 +
...{GogoCommands.java => CommandExecutorImpl.java} | 513 +++++++++++----------
.../apache/sling/pipes/internal/GogoCommands.java | 394 +---------------
.../sling/pipes/internal/PipeBuilderImpl.java | 21 +-
.../apache/sling/pipes/internal/PlumberImpl.java | 34 ++
.../sling/pipes/internal/PlumberServlet.java | 34 +-
...mandsTest.java => CommandExecutorImplTest.java} | 70 ++-
.../sling/pipes/internal/PipeBuilderImplTest.java | 68 +++
src/test/resources/testcommand.txt | 19 +
11 files changed, 550 insertions(+), 668 deletions(-)
diff --git a/src/main/java/org/apache/sling/pipes/CommandExecutor.java
b/src/main/java/org/apache/sling/pipes/CommandExecutor.java
new file mode 100644
index 0000000..e8135c4
--- /dev/null
+++ b/src/main/java/org/apache/sling/pipes/CommandExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * 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 java.lang.reflect.InvocationTargetException;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+public interface CommandExecutor {
+
+ /**
+ * internal execution command handler
+ * @param resolver resolver with which pipe will be executed
+ * @param path pipe path to execute
+ * @param options different options tokens
+ * @return Execution results
+ */
+ ExecutionResult execute(ResourceResolver resolver, String path, String...
options);
+
+ /**
+ * @param resolver resource resolver with which pipe will build the pipe
+ * @param commands list of commands for building the pipe
+ * @return PipeBuilder instance (that can be used to finalize the command)
+ * @throws InvocationTargetException can happen in case the mapping with
PB api went wrong
+ * @throws IllegalAccessException can happen in case the mapping with PB
api went wrong
+ */
+ PipeBuilder parse(ResourceResolver resolver, String... commands) throws
InvocationTargetException, IllegalAccessException;
+
+ /**
+ * @return help string
+ */
+ String help();
+}
diff --git a/src/main/java/org/apache/sling/pipes/PipeBindings.java
b/src/main/java/org/apache/sling/pipes/PipeBindings.java
index 16362a2..889e289 100644
--- a/src/main/java/org/apache/sling/pipes/PipeBindings.java
+++ b/src/main/java/org/apache/sling/pipes/PipeBindings.java
@@ -66,7 +66,7 @@ public class PipeBindings {
*/
public static final String NAME_BINDING = "name";
- private static final String INJECTED_SCRIPT_REGEXP =
"\\$\\{(([^\\{^\\}]*(\\{[0-9,]+\\})?)*)\\}";
+ public static final String INJECTED_SCRIPT_REGEXP =
"\\$\\{(([^\\{^\\}]*(\\{[0-9,]+\\})?)*)\\}";
private static final Pattern INJECTED_SCRIPT =
Pattern.compile(INJECTED_SCRIPT_REGEXP);
protected static final String IF_PREFIX = "$if";
protected static final Pattern CONDITIONAL_STRING = Pattern.compile("^\\"
+ IF_PREFIX + INJECTED_SCRIPT_REGEXP);
diff --git a/src/main/java/org/apache/sling/pipes/Plumber.java
b/src/main/java/org/apache/sling/pipes/Plumber.java
index 9a3091c..b3be41e 100644
--- a/src/main/java/org/apache/sling/pipes/Plumber.java
+++ b/src/main/java/org/apache/sling/pipes/Plumber.java
@@ -16,11 +16,17 @@
*/
package org.apache.sling.pipes;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.event.jobs.Job;
+import org.apache.sling.pipes.internal.JsonUtil;
import org.osgi.annotation.versioning.ProviderType;
+import java.io.IOException;
+import java.util.HashMap;
import java.util.Map;
/**
@@ -145,6 +151,15 @@ public interface Plumber {
boolean isRunning(Resource pipeResource);
/**
+ * Extract pipe bindings from the request
+ * @param request from where to extract bindings
+ * @param writeAllowed should we consider this execution is about to
modify content
+ * @return map of bindings
+ * @throws IOException in case something turns wrong with an input stream
+ */
+ Map<String, Object> getBindingsFromRequest(SlingHttpServletRequest
request, boolean writeAllowed) throws IOException;
+
+ /**
* @return service user that has been configured for executing pipes;
*/
Map<String, Object> getServiceUser();
diff --git a/src/main/java/org/apache/sling/pipes/internal/GogoCommands.java
b/src/main/java/org/apache/sling/pipes/internal/CommandExecutorImpl.java
similarity index 62%
copy from src/main/java/org/apache/sling/pipes/internal/GogoCommands.java
copy to src/main/java/org/apache/sling/pipes/internal/CommandExecutorImpl.java
index 54824ca..d34278a 100644
--- a/src/main/java/org/apache/sling/pipes/internal/GogoCommands.java
+++ b/src/main/java/org/apache/sling/pipes/internal/CommandExecutorImpl.java
@@ -14,120 +14,155 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.sling.pipes.internal;
-import org.apache.commons.io.IOUtils;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.PersistenceException;
+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.ResourceResolverFactory;
+import org.apache.sling.api.servlets.ServletResolverConstants;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.pipes.CommandExecutor;
import org.apache.sling.pipes.ExecutionResult;
import org.apache.sling.pipes.OutputWriter;
import org.apache.sling.pipes.Pipe;
import org.apache.sling.pipes.PipeBuilder;
import org.apache.sling.pipes.PipeExecutor;
import org.apache.sling.pipes.Plumber;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.apache.sling.pipes.PipeBindings.INJECTED_SCRIPT_REGEXP;
import static org.apache.sling.pipes.internal.CommandUtil.writeToMap;
-@Component(immediate = true,
- service = GogoCommands.class,
- property = {
- "osgi.command.scope=pipe",
- "osgi.command.function=build",
- "osgi.command.function=run",
- "osgi.command.function=execute",
- "osgi.command.function=help"
- })
-public class GogoCommands {
- final Logger log = LoggerFactory.getLogger(GogoCommands.class);
-
- protected static final String SEPARATOR = "/";
- protected static final String PARAMS = "@";
- protected static final String INPUT = "-";
- protected static final String KEY_VALUE_SEP = "=";
- protected static final String KEY_NAME = "name";
- protected static final String KEY_PATH = "path";
- protected static final String KEY_EXPR = "expr";
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+@Component(service = {Servlet.class, CommandExecutor.class}, property= {
+ ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES + "=" +
CommandExecutorImpl.RESOURCE_TYPE,
+ ServletResolverConstants.SLING_SERVLET_METHODS + "=POST",
+ ServletResolverConstants.SLING_SERVLET_EXTENSIONS + "=txt"
+})
+public class CommandExecutorImpl extends SlingAllMethodsServlet implements
CommandExecutor {
+ final Logger log = LoggerFactory.getLogger(CommandExecutorImpl.class);
+ public static final String RESOURCE_TYPE = "slingPipes/exec";
+ static final String REQ_PARAM_FILE = "pipe_cmdfile";
+ static final String REQ_PARAM_CMD = "pipe_cmd";
+ static final String WHITE_SPACE_SEPARATOR = "\\s";
+ static final String COMMENT_PREFIX = "#";
+ static final String SEPARATOR = "/";
+ static final String PARAMS = "@";
+ static final String KEY_VALUE_SEP = "=";
+ static final String FIRST_TOKEN = "first";
+ static final String SECOND_TOKEN = "second";
+ static final String CONFIGURATION_TOKEN = "(?<" + FIRST_TOKEN +
">[\\w/\\:]+)\\s*" + KEY_VALUE_SEP
+ + "(?<" + SECOND_TOKEN + ">[(\\w*)|" + INJECTED_SCRIPT_REGEXP + "]+)";
+ static final Pattern CONFIGURATION_PATTERN =
Pattern.compile(CONFIGURATION_TOKEN);
+ static final String KEY_NAME = "name";
+ static final String KEY_PATH = "path";
+ static final String KEY_EXPR = "expr";
+
+ private static final String HELP_START =
+ "\n a <pipe token> is <pipe> <expr|conf>? (<options>)?" +
+ "\n <options> are (@ <option>)* form with <option> being either" +
+ "\n\t'name pipeName' (used in bindings), " +
+ "\n\t'expr pipeExpression' (when not directly as <args>)" +
+ "\n\t'path pipePath' (when not directly as <args>)" +
+ "\n\t'with key=value ...'" +
+ "\n\t'outputs key=value ...'" +
+ "\n and <pipe> is one of the following :\n";
+ Map<String, Method> methodMap;
+ Map<String, PipeExecutor> executorMap;
- @Reference
- ResourceResolverFactory factory;
+ String help;
@Reference
Plumber plumber;
- Map<String, Method> methodMap;
-
- Map<String, PipeExecutor> executorMap;
-
-
- PrintStream print() {
- return System.out;
+ @Activate
+ @Modified
+ public void activate(){
+ methodMap = null;
+ executorMap = null;
+ help = null;
}
- /**
- * run command handler
- * @param cmds string tokens coming with run command
- * @throws Exception in case anything went wrong
- */
- public void run(String... cmds) throws LoginException,
InvocationTargetException, IllegalAccessException {
- try (ResourceResolver resolver =
factory.getServiceResourceResolver(plumber.getServiceUser())) {
- PipeBuilder builder = parse(resolver, cmds);
- print().println(builder.run());
- }
+ boolean isCommandCandidate(String line) {
+ return StringUtils.isNotBlank(line) &&
!line.startsWith(COMMENT_PREFIX);
}
- /**
- * build command handler
- * @param cmds string tokens coming with build command
- * @throws Exception in case anything went wrong
- */
- public void build(String... cmds) throws LoginException,
InvocationTargetException, IllegalAccessException, PersistenceException {
- try (ResourceResolver resolver =
factory.getServiceResourceResolver(plumber.getServiceUser())) {
- PipeBuilder builder = parse(resolver, cmds);
- print().println(builder.build().getResource().getPath());
+ List<String> getCommandList(SlingHttpServletRequest request) throws
IOException {
+ List<String> cmds = new ArrayList<>();
+ if (request.getParameterMap().containsKey(REQ_PARAM_CMD)) {
+ cmds.add(request.getParameter(REQ_PARAM_CMD));
+ } else if (request.getParameterMap().containsKey(REQ_PARAM_FILE)) {
+ InputStream is =
request.getRequestParameter(REQ_PARAM_FILE).getInputStream();
+ BufferedReader reader = new BufferedReader(new
InputStreamReader(is, StandardCharsets.UTF_8));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (isCommandCandidate(line)) {
+ cmds.add(line);
+ }
+ }
}
+ return cmds;
}
- /**
- * execute command handler
- * @param path pipe path
- * @param options string tokens coming with run command
- * @throws Exception in case anything went wrong
- */
- public void execute(String path, String... options) throws IOException,
LoginException {
- String computedPath = INPUT.equals(path) ?
IOUtils.toString(System.in).trim() : path;
- try (ResourceResolver resolver =
factory.getServiceResourceResolver(plumber.getServiceUser())) {
- print().println(executeInternal(resolver, computedPath, options));
+ @Override
+ protected void doPost(@NotNull SlingHttpServletRequest request, @NotNull
SlingHttpServletResponse response) throws ServletException, IOException {
+ String currentCommand = null;
+ PrintWriter writer = response.getWriter();
+ try {
+ ResourceResolver resolver = request.getResourceResolver();
+ Map<String, Object> bindings =
plumber.getBindingsFromRequest(request, true);
+ List<String> cmds = getCommandList(request);
+ if (cmds.isEmpty()) {
+ writer.println("No command to execute!");
+ }
+ for (String command : cmds) {
+ if (StringUtils.isNotBlank(command)) {
+ currentCommand = command;
+ PipeBuilder pipeBuilder = parse(resolver,
command.split(WHITE_SPACE_SEPARATOR));
+ writer.println(pipeBuilder.run(bindings));
+ }
+ }
+ response.setStatus(SC_OK);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ writer.println("Error executing " + currentCommand);
+ e.printStackTrace(writer);
+ response.sendError(SC_INTERNAL_SERVER_ERROR);
+ writer.println(help());
}
}
- /**
- * internal execution command handler
- * @param resolver resolver with which pipe will be executed
- * @param path pipe path to execute, {@code INPUT} for getting last
token's output as path for things like build some / pipe | execute -
- * @param optionTokens different options tokens
- * @return Execution results
- * @throws Exception exception in case something goes wrong
- */
- protected ExecutionResult executeInternal(ResourceResolver resolver,
String path, String... optionTokens) {
+ @Override
+ public ExecutionResult execute(ResourceResolver resolver, String path,
String... optionTokens) {
Resource resource = resolver.getResource(path);
if (resource == null){
throw new IllegalArgumentException(String.format("%s resource does
not exist", path));
@@ -146,36 +181,8 @@ public class GogoCommands {
return plumber.execute(resolver, path, bMap, writer, true);
}
- /**
- * help command handler
- */
- public void help(){
- print().format("\nSling Pipes Help\nAvailable commands are \n\n-
execute <path> <options>(execute a pipe already built at a given path), if path
is '-' then previous pipe token is used," +
- "\n- build (build pipe as configured in
arguments)" +
- "\n- run (run pipe as configured in
arguments)" +
- "\n- help (print(). this help)" +
- "\n\nfor pipe configured in argument, do
'pipe:<run|build|runAsync> <pipe token> (/ <pipe token> )*\n" +
- "\n a <pipe token> is <pipe> <expr|conf>?
(<options>)?" +
- "\n <options> are (@ <option>)* form with
<option> being either" +
- "\n\t'name pipeName' (used in bindings), " +
- "\n\t'expr pipeExpression' (when not directly
as <args>)" +
- "\n\t'path pipePath' (when not directly as
<args>)" +
- "\n\t'with key=value ...'" +
- "\n\t'outputs key=value ...'" +
- "\n and <pipe> is one of the following :\n");
- for (Map.Entry<String, PipeExecutor> entry :
getExecutorMap().entrySet()){
- print().format("\t%s\t\t:\t%s%n", entry.getKey(),
entry.getValue().description() );
- }
- }
-
- /**
- * @param resolver resource resolver with which pipe will build the pipe
- * @param cmds list of commands for building the pipe
- * @return PipeBuilder instance (that can be used to finalize the command)
- * @throws InvocationTargetException can happen in case the mapping with
PB api went wrong
- * @throws IllegalAccessException can happen in case the mapping with PB
api went wrong
- */
- protected PipeBuilder parse(ResourceResolver resolver, String...cmds)
throws InvocationTargetException, IllegalAccessException {
+ @Override
+ public PipeBuilder parse(ResourceResolver resolver, String...cmds) throws
InvocationTargetException, IllegalAccessException {
PipeBuilder builder = plumber.newPipe(resolver);
for (Token token : parseTokens(cmds)){
Method method = getMethodMap().get(token.pipeKey);
@@ -199,114 +206,6 @@ public class GogoCommands {
}
/**
- * builds utility maps
- */
- protected void computeMaps(){
- executorMap = new HashMap<>();
- methodMap = new HashMap<>();
- for (Method method : PipeBuilder.class.getDeclaredMethods()) {
- PipeExecutor executor = method.getAnnotation(PipeExecutor.class);
- if (executor != null) {
- methodMap.put(executor.command(), method);
- executorMap.put(executor.command(), executor);
- }
- }
- }
-
- /**
- * @return map of command to PB api method
- */
- protected Map<String, Method> getMethodMap() {
- if (methodMap == null) {
- computeMaps();
- }
- return methodMap;
- }
-
- /**
- * @return map of command to Annotation information around the PB api
- */
- protected Map<String, PipeExecutor> getExecutorMap() {
- if (executorMap == null) {
- computeMaps();
- }
- return executorMap;
- }
-
- /**
- * @param method corresponding PB api
- * @return true if the api does expect an expression (meaning a string)
- */
- protected boolean isExpressionExpected(Method method) {
- return method.getParameterCount() == 1 &&
method.getParameterTypes()[0].equals(String.class);
- }
-
- /**
- * @param method corresponding PB api
- * @return true if the api does expect a configuration (meaning a list of
key value pairs)
- */
- protected boolean isConfExpected(Method method) {
- return method.getParameterCount() == 1 &&
method.getParameterTypes()[0].equals(Object[].class);
- }
-
- /**
- * @param method corresponding PB api
- * @return true if the api does not expect parameters
- */
- protected boolean isWithoutExpectedParameter(Method method){
- return method.getParameterCount() == 0;
- }
-
- /**
- * @param o list of key value strings key1:value1,key2:value2,...
- * @return String [] key1,value1,key2,value2,... corresponding to the pipe
builder API
- */
- private String[] keyValuesToArray(List<String> o) {
- List<String> args = new ArrayList<>();
- for (String pair : o){
- args.addAll(Arrays.asList(pair.split(KEY_VALUE_SEP)));
- }
- return args.toArray(new String[args.size()]);
- }
-
-
- /**
- * @param commands full list of command tokens
- * @return Token list corresponding to the string ones
- */
- protected List<Token> parseTokens(String... commands) {
- List<Token> returnValue = new ArrayList<>();
- Token currentToken = new Token();
- returnValue.add(currentToken);
- List<String> currentList = new ArrayList<>();
- for (String token : commands){
- if (currentToken.pipeKey == null){
- currentToken.pipeKey = token;
- } else {
- switch (token){
- case GogoCommands.SEPARATOR:
- finishToken(currentToken, currentList);
- currentList = new ArrayList<>();
- currentToken = new Token();
- returnValue.add(currentToken);
- break;
- case GogoCommands.PARAMS:
- if (currentToken.args == null){
- currentToken.args = currentList;
- currentList = new ArrayList<>();
- }
- currentList.add(PARAMS);
- break;
- default:
- currentList.add(token);
- }
- }
- }
- finishToken(currentToken, currentList);
- return returnValue;
- }
-
- /**
* ends up processing of current token
* @param currentToken token being processed
* @param currentList list of argument that have been collected so far
@@ -322,24 +221,6 @@ public class GogoCommands {
}
/**
- * Pipe token, used to hold information of a "sub pipe" configuration
- */
- protected class Token {
- String pipeKey;
- List<String> args;
- Options options;
-
- @Override
- public String toString() {
- return "Token{" +
- "pipeKey='" + pipeKey + '\'' +
- ", args=" + args +
- ", options=" + options +
- '}';
- }
- }
-
- /**
* @param tokens array of tokens
* @return options from array
*/
@@ -361,7 +242,6 @@ public class GogoCommands {
protected class Options {
String name;
String path;
-
String expr;
String[] with;
OutputWriter writer;
@@ -369,16 +249,14 @@ public class GogoCommands {
@Override
public String toString() {
return "Options{" +
- "name='" + name + '\'' +
- ", path='" + path + '\'' +
- ", expr='" + expr + '\'' +
- ", with=" + Arrays.toString(with) +
- ", writer=" + writer +
- '}';
+ "name='" + name + '\'' +
+ ", path='" + path + '\'' +
+ ", expr='" + expr + '\'' +
+ ", with=" + Arrays.toString(with) +
+ ", writer=" + writer +
+ '}';
}
-
-
void setOutputs(List<String> values) {
this.writer = new JsonWriter();
String[] list = keyValuesToArray(values);
@@ -469,4 +347,149 @@ public class GogoCommands {
}
}
}
+
+ /**
+ * Pipe token, used to hold information of a "sub pipe" configuration
+ */
+ protected class Token {
+ String pipeKey;
+ List<String> args;
+ CommandExecutorImpl.Options options;
+
+ @Override
+ public String toString() {
+ return "Token{" +
+ "pipeKey='" + pipeKey + '\'' +
+ ", args=" + args +
+ ", options=" + options +
+ '}';
+ }
+ }
+
+ /**
+ * @param commands full list of command tokens
+ * @return Token list corresponding to the string ones
+ */
+ protected List<CommandExecutorImpl.Token> parseTokens(String... commands) {
+ List<CommandExecutorImpl.Token> returnValue = new ArrayList<>();
+ CommandExecutorImpl.Token currentToken = new
CommandExecutorImpl.Token();
+ returnValue.add(currentToken);
+ List<String> currentList = new ArrayList<>();
+ for (String token : commands){
+ if (currentToken.pipeKey == null){
+ currentToken.pipeKey = token;
+ } else {
+ switch (token){
+ case CommandExecutorImpl.SEPARATOR:
+ finishToken(currentToken, currentList);
+ currentList = new ArrayList<>();
+ currentToken = new CommandExecutorImpl.Token();
+ returnValue.add(currentToken);
+ break;
+ case CommandExecutorImpl.PARAMS:
+ if (currentToken.args == null){
+ currentToken.args = currentList;
+ currentList = new ArrayList<>();
+ }
+ currentList.add(PARAMS);
+ break;
+ default:
+ currentList.add(token);
+ }
+ }
+ }
+ finishToken(currentToken, currentList);
+ return returnValue;
+ }
+
+ /**
+ * builds utility maps
+ */
+ protected void computeMaps(){
+ executorMap = new HashMap<>();
+ methodMap = new HashMap<>();
+ for (Method method : PipeBuilder.class.getDeclaredMethods()) {
+ PipeExecutor executor = method.getAnnotation(PipeExecutor.class);
+ if (executor != null) {
+ methodMap.put(executor.command(), method);
+ executorMap.put(executor.command(), executor);
+ }
+ }
+ }
+
+ /**
+ * @return map of command to PB api method
+ */
+ protected Map<String, Method> getMethodMap() {
+ if (methodMap == null) {
+ computeMaps();
+ }
+ return methodMap;
+ }
+
+ /**
+ * @return map of command to Annotation information around the PB api
+ */
+ protected Map<String, PipeExecutor> getExecutorMap() {
+ if (executorMap == null) {
+ computeMaps();
+ }
+ return executorMap;
+ }
+
+ /**
+ * @param method corresponding PB api
+ * @return true if the api does expect an expression (meaning a string)
+ */
+ protected boolean isExpressionExpected(Method method) {
+ return method.getParameterCount() == 1 &&
method.getParameterTypes()[0].equals(String.class);
+ }
+
+ /**
+ * @param method corresponding PB api
+ * @return true if the api does expect a configuration (meaning a list of
key value pairs)
+ */
+ protected boolean isConfExpected(Method method) {
+ return method.getParameterCount() == 1 &&
method.getParameterTypes()[0].equals(Object[].class);
+ }
+
+ /**
+ * @param method corresponding PB api
+ * @return true if the api does not expect parameters
+ */
+ protected boolean isWithoutExpectedParameter(Method method){
+ return method.getParameterCount() == 0;
+ }
+
+ /**
+ * @param o list of key value strings key1=value1,key2=value2,...
+ * @return String [] key1,value1,key2,value2,... corresponding to the pipe
builder API
+ */
+ String[] keyValuesToArray(List<String> o) {
+ List<String> args = new ArrayList<>();
+ for (String pair : o){
+ Matcher matcher = CONFIGURATION_PATTERN.matcher(pair.trim());
+ if (matcher.matches()) {
+ args.add(matcher.group(FIRST_TOKEN));
+ args.add(matcher.group(SECOND_TOKEN));
+ }
+ }
+ return args.toArray(new String[args.size()]);
+ }
+
+ /**
+ * help command handler
+ */
+ public String help(){
+ if (StringUtils.isBlank(help)) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(HELP_START);
+ for (Map.Entry<String, PipeExecutor> entry :
getExecutorMap().entrySet()) {
+ builder.append(String.format("\t%s\t\t:\t%s%n",
entry.getKey(), entry.getValue().description()));
+ }
+ help = builder.toString();
+ }
+ return help;
+ }
+
}
diff --git a/src/main/java/org/apache/sling/pipes/internal/GogoCommands.java
b/src/main/java/org/apache/sling/pipes/internal/GogoCommands.java
index 54824ca..d137edf 100644
--- a/src/main/java/org/apache/sling/pipes/internal/GogoCommands.java
+++ b/src/main/java/org/apache/sling/pipes/internal/GogoCommands.java
@@ -17,34 +17,20 @@
package org.apache.sling.pipes.internal;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.pipes.ExecutionResult;
-import org.apache.sling.pipes.OutputWriter;
-import org.apache.sling.pipes.Pipe;
+
+import org.apache.sling.pipes.CommandExecutor;
import org.apache.sling.pipes.PipeBuilder;
-import org.apache.sling.pipes.PipeExecutor;
import org.apache.sling.pipes.Plumber;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.sling.pipes.internal.CommandUtil.writeToMap;
@Component(immediate = true,
service = GogoCommands.class,
@@ -56,27 +42,23 @@ import static
org.apache.sling.pipes.internal.CommandUtil.writeToMap;
"osgi.command.function=help"
})
public class GogoCommands {
- final Logger log = LoggerFactory.getLogger(GogoCommands.class);
- protected static final String SEPARATOR = "/";
- protected static final String PARAMS = "@";
- protected static final String INPUT = "-";
- protected static final String KEY_VALUE_SEP = "=";
- protected static final String KEY_NAME = "name";
- protected static final String KEY_PATH = "path";
- protected static final String KEY_EXPR = "expr";
+ static final String INPUT = "-";
+ static final String COMMAND_HELP_START = "\nSling Pipes Help\nAvailable
commands are \n\n- execute <path> <options>(execute a pipe already built at a
given path), if path is '-' then previous pipe token is used," +
+ "\n- build (build pipe as configured in arguments)" +
+ "\n- run (run pipe as configured in arguments)" +
+ "\n- help (print(). this help)" +
+ "\n\nfor pipe configured in argument, do 'pipe:<run|build|runAsync>
<pipe token> (/ <pipe token> )*\n";
@Reference
ResourceResolverFactory factory;
@Reference
- Plumber plumber;
-
- Map<String, Method> methodMap;
+ CommandExecutor commandExecutor;
- Map<String, PipeExecutor> executorMap;
-
+ @Reference
+ Plumber plumber;
PrintStream print() {
return System.out;
@@ -89,7 +71,7 @@ public class GogoCommands {
*/
public void run(String... cmds) throws LoginException,
InvocationTargetException, IllegalAccessException {
try (ResourceResolver resolver =
factory.getServiceResourceResolver(plumber.getServiceUser())) {
- PipeBuilder builder = parse(resolver, cmds);
+ PipeBuilder builder = commandExecutor.parse(resolver, cmds);
print().println(builder.run());
}
}
@@ -101,372 +83,28 @@ public class GogoCommands {
*/
public void build(String... cmds) throws LoginException,
InvocationTargetException, IllegalAccessException, PersistenceException {
try (ResourceResolver resolver =
factory.getServiceResourceResolver(plumber.getServiceUser())) {
- PipeBuilder builder = parse(resolver, cmds);
+ PipeBuilder builder = commandExecutor.parse(resolver, cmds);
print().println(builder.build().getResource().getPath());
}
}
/**
* execute command handler
- * @param path pipe path
+ * @param path pipe path, {@code INPUT} for getting last token's output as
path for things like build some / pipe | execute -
* @param options string tokens coming with run command
* @throws Exception in case anything went wrong
*/
public void execute(String path, String... options) throws IOException,
LoginException {
String computedPath = INPUT.equals(path) ?
IOUtils.toString(System.in).trim() : path;
try (ResourceResolver resolver =
factory.getServiceResourceResolver(plumber.getServiceUser())) {
- print().println(executeInternal(resolver, computedPath, options));
+ print().println(commandExecutor.execute(resolver, computedPath,
options));
}
}
/**
- * internal execution command handler
- * @param resolver resolver with which pipe will be executed
- * @param path pipe path to execute, {@code INPUT} for getting last
token's output as path for things like build some / pipe | execute -
- * @param optionTokens different options tokens
- * @return Execution results
- * @throws Exception exception in case something goes wrong
- */
- protected ExecutionResult executeInternal(ResourceResolver resolver,
String path, String... optionTokens) {
- Resource resource = resolver.getResource(path);
- if (resource == null){
- throw new IllegalArgumentException(String.format("%s resource does
not exist", path));
- }
- Options options = getOptions(optionTokens);
- Map<String, Object> bMap = null;
- if (options.with != null) {
- bMap = new HashMap<>();
- writeToMap(bMap, options.with);
- }
- OutputWriter writer = new NopWriter();
- if (options.writer != null){
- writer = options.writer;
- }
- writer.starts();
- return plumber.execute(resolver, path, bMap, writer, true);
- }
-
- /**
* help command handler
*/
public void help(){
- print().format("\nSling Pipes Help\nAvailable commands are \n\n-
execute <path> <options>(execute a pipe already built at a given path), if path
is '-' then previous pipe token is used," +
- "\n- build (build pipe as configured in
arguments)" +
- "\n- run (run pipe as configured in
arguments)" +
- "\n- help (print(). this help)" +
- "\n\nfor pipe configured in argument, do
'pipe:<run|build|runAsync> <pipe token> (/ <pipe token> )*\n" +
- "\n a <pipe token> is <pipe> <expr|conf>?
(<options>)?" +
- "\n <options> are (@ <option>)* form with
<option> being either" +
- "\n\t'name pipeName' (used in bindings), " +
- "\n\t'expr pipeExpression' (when not directly
as <args>)" +
- "\n\t'path pipePath' (when not directly as
<args>)" +
- "\n\t'with key=value ...'" +
- "\n\t'outputs key=value ...'" +
- "\n and <pipe> is one of the following :\n");
- for (Map.Entry<String, PipeExecutor> entry :
getExecutorMap().entrySet()){
- print().format("\t%s\t\t:\t%s%n", entry.getKey(),
entry.getValue().description() );
- }
- }
-
- /**
- * @param resolver resource resolver with which pipe will build the pipe
- * @param cmds list of commands for building the pipe
- * @return PipeBuilder instance (that can be used to finalize the command)
- * @throws InvocationTargetException can happen in case the mapping with
PB api went wrong
- * @throws IllegalAccessException can happen in case the mapping with PB
api went wrong
- */
- protected PipeBuilder parse(ResourceResolver resolver, String...cmds)
throws InvocationTargetException, IllegalAccessException {
- PipeBuilder builder = plumber.newPipe(resolver);
- for (Token token : parseTokens(cmds)){
- Method method = getMethodMap().get(token.pipeKey);
- if (method == null){
- throw new IllegalArgumentException(token.pipeKey + " is not a
valid pipe");
- }
- if (isExpressionExpected(method)){
- method.invoke(builder, token.args.get(0));
- } else if (isConfExpected(method)){
- method.invoke(builder, (Object)keyValuesToArray(token.args));
- } else if (isWithoutExpectedParameter(method)){
- method.invoke(builder);
- }
-
- if (token.options != null){
- token.options.writeToBuilder(builder);
- }
- }
- return builder;
-
- }
-
- /**
- * builds utility maps
- */
- protected void computeMaps(){
- executorMap = new HashMap<>();
- methodMap = new HashMap<>();
- for (Method method : PipeBuilder.class.getDeclaredMethods()) {
- PipeExecutor executor = method.getAnnotation(PipeExecutor.class);
- if (executor != null) {
- methodMap.put(executor.command(), method);
- executorMap.put(executor.command(), executor);
- }
- }
- }
-
- /**
- * @return map of command to PB api method
- */
- protected Map<String, Method> getMethodMap() {
- if (methodMap == null) {
- computeMaps();
- }
- return methodMap;
- }
-
- /**
- * @return map of command to Annotation information around the PB api
- */
- protected Map<String, PipeExecutor> getExecutorMap() {
- if (executorMap == null) {
- computeMaps();
- }
- return executorMap;
- }
-
- /**
- * @param method corresponding PB api
- * @return true if the api does expect an expression (meaning a string)
- */
- protected boolean isExpressionExpected(Method method) {
- return method.getParameterCount() == 1 &&
method.getParameterTypes()[0].equals(String.class);
- }
-
- /**
- * @param method corresponding PB api
- * @return true if the api does expect a configuration (meaning a list of
key value pairs)
- */
- protected boolean isConfExpected(Method method) {
- return method.getParameterCount() == 1 &&
method.getParameterTypes()[0].equals(Object[].class);
- }
-
- /**
- * @param method corresponding PB api
- * @return true if the api does not expect parameters
- */
- protected boolean isWithoutExpectedParameter(Method method){
- return method.getParameterCount() == 0;
- }
-
- /**
- * @param o list of key value strings key1:value1,key2:value2,...
- * @return String [] key1,value1,key2,value2,... corresponding to the pipe
builder API
- */
- private String[] keyValuesToArray(List<String> o) {
- List<String> args = new ArrayList<>();
- for (String pair : o){
- args.addAll(Arrays.asList(pair.split(KEY_VALUE_SEP)));
- }
- return args.toArray(new String[args.size()]);
- }
-
-
- /**
- * @param commands full list of command tokens
- * @return Token list corresponding to the string ones
- */
- protected List<Token> parseTokens(String... commands) {
- List<Token> returnValue = new ArrayList<>();
- Token currentToken = new Token();
- returnValue.add(currentToken);
- List<String> currentList = new ArrayList<>();
- for (String token : commands){
- if (currentToken.pipeKey == null){
- currentToken.pipeKey = token;
- } else {
- switch (token){
- case GogoCommands.SEPARATOR:
- finishToken(currentToken, currentList);
- currentList = new ArrayList<>();
- currentToken = new Token();
- returnValue.add(currentToken);
- break;
- case GogoCommands.PARAMS:
- if (currentToken.args == null){
- currentToken.args = currentList;
- currentList = new ArrayList<>();
- }
- currentList.add(PARAMS);
- break;
- default:
- currentList.add(token);
- }
- }
- }
- finishToken(currentToken, currentList);
- return returnValue;
- }
-
- /**
- * ends up processing of current token
- * @param currentToken token being processed
- * @param currentList list of argument that have been collected so far
- */
- protected void finishToken(Token currentToken, List<String> currentList){
- if (currentToken.args != null){
- //it means we have already parse args here, so we need to set
current list as options
- currentToken.options = getOptions(currentList);
- } else {
- currentToken.args = currentList;
- }
- log.debug("current token : {}", currentToken);
- }
-
- /**
- * Pipe token, used to hold information of a "sub pipe" configuration
- */
- protected class Token {
- String pipeKey;
- List<String> args;
- Options options;
-
- @Override
- public String toString() {
- return "Token{" +
- "pipeKey='" + pipeKey + '\'' +
- ", args=" + args +
- ", options=" + options +
- '}';
- }
- }
-
- /**
- * @param tokens array of tokens
- * @return options from array
- */
- protected Options getOptions(String[] tokens){
- return getOptions(Arrays.asList(tokens));
- }
-
- /**
- * @param tokens list of tokens
- * @return options from token list
- */
- protected Options getOptions(List<String> tokens){
- return new Options(tokens);
- }
-
- /**
- * Options for a pipe execution
- */
- protected class Options {
- String name;
- String path;
-
- String expr;
- String[] with;
- OutputWriter writer;
-
- @Override
- public String toString() {
- return "Options{" +
- "name='" + name + '\'' +
- ", path='" + path + '\'' +
- ", expr='" + expr + '\'' +
- ", with=" + Arrays.toString(with) +
- ", writer=" + writer +
- '}';
- }
-
-
-
- void setOutputs(List<String> values) {
- this.writer = new JsonWriter();
- String[] list = keyValuesToArray(values);
- Map<String, Object> outputs = new HashMap<>();
- CommandUtil.writeToMap(outputs, list);
- this.writer.setCustomOutputs(outputs);
- }
-
- /**
- * Constructor
- * @param options string list from where options will be built
- */
- protected Options(List<String> options){
- Map<String, Object> optionMap = new HashMap<>();
- String currentKey = null;
- List<String> currentList = null;
-
-
- for (String optionToken : options) {
- if (PARAMS.equals(optionToken)){
- finishOption(currentKey, currentList, optionMap);
- currentList = new ArrayList<>();
- currentKey = null;
- } else if (currentKey == null){
- currentKey = optionToken;
- } else {
- currentList.add(optionToken);
- }
- }
- finishOption(currentKey, currentList, optionMap);
- for (Map.Entry<String, Object> entry : optionMap.entrySet()){
- switch (entry.getKey()) {
- case Pipe.PN_NAME :
- this.name = (String)entry.getValue();
- break;
- case Pipe.PN_PATH :
- this.path = (String)entry.getValue();
- break;
- case Pipe.PN_EXPR :
- this.expr = (String)entry.getValue();
- break;
- case "with" :
- this.with =
keyValuesToArray((List<String>)entry.getValue());
- break;
- case "outputs" :
- setOutputs((List<String>)entry.getValue());
- break;
- default:
- throw new IllegalArgumentException(String.format("%s
is an unknown option", entry.getKey()));
- }
- }
-
- }
-
- /**
- * wrap up current option
- * @param currentKey option key
- * @param currentList list being processed
- * @param optionMap option map
- */
- protected void finishOption(String currentKey, List<String>
currentList, Map<String, Object> optionMap){
- if (currentList != null){
- if (currentKey.equals(KEY_NAME) || currentKey.equals(KEY_EXPR)
|| currentKey.equals(KEY_PATH)) {
- optionMap.put(currentKey, currentList.get(0));
- } else {
- optionMap.put(currentKey, currentList);
- }
- }
- }
-
- /**
- * write options to current builder
- * @param builder current builder
- * @throws IllegalAccessException
- */
- void writeToBuilder(PipeBuilder builder) throws IllegalAccessException
{
- if (StringUtils.isNotBlank(name)){
- builder.name(name);
- }
- if (StringUtils.isNotBlank(path)){
- builder.path(path);
- }
- if (StringUtils.isNotBlank(expr)){
- builder.expr(expr);
- }
- if (with != null){
- builder.with(with);
- }
- }
+ print().println(COMMAND_HELP_START + commandExecutor.help());
}
}
diff --git a/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
b/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
index e0e89b4..852d629 100644
--- a/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
+++ b/src/main/java/org/apache/sling/pipes/internal/PipeBuilderImpl.java
@@ -43,14 +43,17 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import static org.apache.commons.lang3.StringUtils.EMPTY;
import static
org.apache.sling.jcr.resource.JcrResourceConstants.NT_SLING_FOLDER;
import static
org.apache.sling.jcr.resource.JcrResourceConstants.NT_SLING_ORDERED_FOLDER;
import static
org.apache.sling.jcr.resource.JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY;
+import static org.apache.sling.pipes.BasePipe.SLASH;
import static org.apache.sling.pipes.internal.CommandUtil.checkArguments;
import static org.apache.sling.pipes.internal.CommandUtil.writeToMap;
import static org.apache.sling.pipes.internal.ManifoldPipe.PN_NUM_THREADS;
@@ -320,8 +323,22 @@ public class PipeBuilderImpl implements PipeBuilder {
* @throws PersistenceException in case configuration resource couldn't be
persisted
* @return resource created
*/
- protected Resource createResource(ResourceResolver resolver, String path,
String type, Map<String, Object> data) throws PersistenceException {
- return ResourceUtil.getOrCreateResource(resolver, path, data, type,
false);
+ Resource createResource(ResourceResolver resolver, String path, String
type, Map<String, Object> data) throws PersistenceException {
+ if (! data.keySet().stream().anyMatch(k -> k.contains(SLASH))) {
+ return ResourceUtil.getOrCreateResource(resolver, path, data,
type, false);
+ }
+ String returnPath = EMPTY;
+ for (Map.Entry<String, Object> entry : data.entrySet()) {
+ if (entry.getKey().contains(SLASH)) {
+ String deepPath = String.join(SLASH, path,
StringUtils.substringBeforeLast(entry.getKey(), SLASH));
+ createResource(resolver, deepPath, type,
Collections.singletonMap(
+ StringUtils.substringAfterLast(entry.getKey(), SLASH),
entry.getValue()));
+ if (returnPath.isEmpty() || returnPath.length() >
deepPath.length()) {
+ returnPath = deepPath;
+ }
+ }
+ }
+ return resolver.getResource(returnPath);
}
@Override
diff --git a/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
b/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
index 034bed4..27dcc3a 100644
--- a/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
+++ b/src/main/java/org/apache/sling/pipes/internal/PlumberImpl.java
@@ -19,6 +19,8 @@ package org.apache.sling.pipes.internal;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
@@ -33,6 +35,7 @@ import
org.apache.sling.distribution.SimpleDistributionRequest;
import org.apache.sling.event.jobs.Job;
import org.apache.sling.event.jobs.JobManager;
import org.apache.sling.event.jobs.consumer.JobConsumer;
+import org.apache.sling.pipes.AbstractInputStreamPipe;
import org.apache.sling.pipes.BasePipe;
import org.apache.sling.pipes.ExecutionResult;
import org.apache.sling.pipes.OutputWriter;
@@ -58,6 +61,8 @@ import javax.jcr.RepositoryException;
import javax.jcr.query.Query;
import javax.management.MBeanServer;
import javax.management.ObjectName;
+
+import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -94,6 +99,10 @@ public class PlumberImpl implements Plumber, JobConsumer,
PlumberMXBean {
protected static final String MBEAN_NAME_FORMAT =
"org.apache.sling.pipes:name=%s";
+ protected static final String PARAM_BINDINGS = "bindings";
+
+ protected static final String PARAM_FILE = "pipes_inputFile";
+
@ObjectClassDefinition(name="Apache Sling Pipes : Plumber configuration")
public @interface Configuration {
@AttributeDefinition(description="Number of iterations after which
plumber should saves a pipe execution")
@@ -211,6 +220,31 @@ public class PlumberImpl implements Plumber, JobConsumer,
PlumberMXBean {
}
@Override
+ public Map<String, Object> getBindingsFromRequest(SlingHttpServletRequest
request, boolean writeAllowed) throws IOException
+ {
+ Map<String, Object> bindings = new HashMap<>();
+ String dryRun = request.getParameter(BasePipe.DRYRUN_KEY);
+ if (StringUtils.isNotBlank(dryRun) &&
!dryRun.equals(Boolean.FALSE.toString())) {
+ bindings.put(BasePipe.DRYRUN_KEY, true);
+ }
+ String paramBindings = request.getParameter(PARAM_BINDINGS);
+ if (StringUtils.isNotBlank(paramBindings)){
+ try {
+
bindings.putAll(JsonUtil.unbox(JsonUtil.parseObject(paramBindings)));
+ } catch (Exception e){
+ log.error("Unable to retrieve bindings information", e);
+ }
+ }
+ RequestParameter fileParameter =
request.getRequestParameter(PARAM_FILE);
+ if (fileParameter != null){
+ bindings.put(AbstractInputStreamPipe.BINDING_IS,
fileParameter.getInputStream());
+ }
+
+ bindings.put(BasePipe.READ_ONLY, !writeAllowed);
+ return bindings;
+ }
+
+ @Override
public Job executeAsync(ResourceResolver resolver, String path,
Map<String, Object> bindings) {
if (allowedUsers.contains(resolver.getUserID())) {
return executeAsync(path, bindings);
diff --git a/src/main/java/org/apache/sling/pipes/internal/PlumberServlet.java
b/src/main/java/org/apache/sling/pipes/internal/PlumberServlet.java
index d174058..4ebbaa6 100644
--- a/src/main/java/org/apache/sling/pipes/internal/PlumberServlet.java
+++ b/src/main/java/org/apache/sling/pipes/internal/PlumberServlet.java
@@ -68,8 +68,6 @@ public class PlumberServlet extends SlingAllMethodsServlet {
protected static final String PARAM_ASYNC = "async";
- protected static final String PARAM_FILE = "pipes_inputFile";
-
@Reference
Plumber plumber;
@@ -100,7 +98,7 @@ public class PlumberServlet extends SlingAllMethodsServlet {
if (StringUtils.isBlank(path)) {
throw new IllegalArgumentException("path should be provided");
}
- Map<String, Object> bindings = getBindingsFromRequest(request,
writeAllowed);
+ Map<String, Object> bindings =
plumber.getBindingsFromRequest(request, writeAllowed);
String asyncParam = request.getParameter(PARAM_ASYNC);
if (StringUtils.isNotBlank(asyncParam) &&
asyncParam.equals(Boolean.TRUE.toString())){
Job job = plumber.executeAsync(request.getResourceResolver(),
path, bindings);
@@ -120,36 +118,6 @@ public class PlumberServlet extends SlingAllMethodsServlet
{
}
/**
- * Converts request into pipe bindings
- * @param request from where to extract bindings
- * @param writeAllowed should we consider this execution is about to
modify content
- * @return map of bindings
- * @throws IOException in case something turns wrong with an input stream
- */
- protected Map<String, Object>
getBindingsFromRequest(SlingHttpServletRequest request, boolean writeAllowed)
throws IOException{
- Map<String, Object> bindings = new HashMap<>();
- String dryRun = request.getParameter(BasePipe.DRYRUN_KEY);
- if (StringUtils.isNotBlank(dryRun) &&
!dryRun.equals(Boolean.FALSE.toString())) {
- bindings.put(BasePipe.DRYRUN_KEY, true);
- }
- String paramBindings = request.getParameter(PARAM_BINDINGS);
- if (StringUtils.isNotBlank(paramBindings)){
- try {
-
bindings.putAll(JsonUtil.unbox(JsonUtil.parseObject(paramBindings)));
- } catch (Exception e){
- log.error("Unable to retrieve bindings information", e);
- }
- }
- RequestParameter fileParameter =
request.getRequestParameter(PARAM_FILE);
- if (fileParameter != null){
- bindings.put(AbstractInputStreamPipe.BINDING_IS,
fileParameter.getInputStream());
- }
-
- bindings.put(BasePipe.READ_ONLY, !writeAllowed);
- return bindings;
- }
-
- /**
* Retrieve an output writer depending on the request
* @param request original request against which writers will be tested
* @param response response writers will point to
diff --git
a/src/test/java/org/apache/sling/pipes/internal/GogoCommandsTest.java
b/src/test/java/org/apache/sling/pipes/internal/CommandExecutorImplTest.java
similarity index 62%
rename from src/test/java/org/apache/sling/pipes/internal/GogoCommandsTest.java
rename to
src/test/java/org/apache/sling/pipes/internal/CommandExecutorImplTest.java
index 6bf25b6..4464978 100644
--- a/src/test/java/org/apache/sling/pipes/internal/GogoCommandsTest.java
+++ b/src/test/java/org/apache/sling/pipes/internal/CommandExecutorImplTest.java
@@ -16,41 +16,51 @@
*/
package org.apache.sling.pipes.internal;
+import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.pipes.AbstractPipeTest;
import org.apache.sling.pipes.ExecutionResult;
import org.apache.sling.pipes.PipeBuilder;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import
org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
+import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-public class GogoCommandsTest extends AbstractPipeTest {
+import javax.servlet.ServletException;
- GogoCommands commands;
+public class CommandExecutorImplTest extends AbstractPipeTest {
+
+ CommandExecutorImpl commands;
@Before
public void setup() throws PersistenceException {
super.setup();
- commands = new GogoCommands();
+ context = new SlingContext(ResourceResolverType.JCR_OAK);
+ context.load().json("/initial-content/content/fruits.json",
PATH_FRUITS);
+ commands = new CommandExecutorImpl();
commands.plumber = plumber;
}
@Test
public void testParseTokens(){
- List<GogoCommands.Token> tokens = commands.parseTokens("some",
"isolated", "items");
+ List<CommandExecutorImpl.Token> tokens = commands.parseTokens("some",
"isolated", "items");
assertEquals("there should be 1 token", 1, tokens.size());
- GogoCommands.Token token = tokens.get(0);
+ CommandExecutorImpl.Token token = tokens.get(0);
assertEquals("pipe key should be 'some'","some", token.pipeKey);
assertEquals("pipe args should be isolated, items",
Arrays.asList("isolated","items"), token.args);
String tokenString = "first arg / second firstarg secondarg @ name
second / third blah";
@@ -61,6 +71,15 @@ public class GogoCommandsTest extends AbstractPipeTest {
}
@Test
+ public void testKeyValueToArray() {
+ assertArrayEquals(new String[]{"one","two","three","four"},
commands.keyValuesToArray(Arrays.asList("one=two","three=four")));
+ assertArrayEquals(new String[]{"one","two","three","${four}"},
commands.keyValuesToArray(Arrays.asList("one=two","three=${four}")));
+ assertArrayEquals(new String[]{"one","two","three","${four == 'blah' ?
'five' : 'six'}"},
+ commands.keyValuesToArray(Arrays.asList("one=two","three=${four ==
'blah' ? 'five' : 'six'}")));
+ assertArrayEquals(new String[]{"jcr:content/singer","${'ringo' == one
? false : true}"},
commands.keyValuesToArray(Arrays.asList("jcr:content/singer=${'ringo' == one ?
false : true}")));
+ }
+
+ @Test
public void testSimpleExpression() throws Exception {
PipeBuilder builder =
commands.parse(context.resourceResolver(),"echo","/content/fruits");
assertTrue("there should be a resource",
builder.build().getOutput().hasNext());
@@ -79,7 +98,7 @@ public class GogoCommandsTest extends AbstractPipeTest {
public void testOptions() {
String expected = "works";
String optionString = "@ name works @ path works @ expr works @ with
one=works two=works @ outputs one=works two=works";
- GogoCommands.Options options =
commands.getOptions(optionString.split("\\s"));
+ CommandExecutorImpl.Options options =
commands.getOptions(optionString.split("\\s"));
assertEquals("check name", expected, options.name);
assertEquals("check expr", expected, options.expr);
assertEquals("check path", expected, options.path);
@@ -97,7 +116,7 @@ public class GogoCommandsTest extends AbstractPipeTest {
public void testOptionsListsWithOneItem() {
String expected = "works";
String optionString = "@ with one=works @ outputs one=works";
- GogoCommands.Options options =
commands.getOptions(optionString.split("\\s"));
+ CommandExecutorImpl.Options options =
commands.getOptions(optionString.split("\\s"));
Map bindings = new HashMap();
CommandUtil.writeToMap(bindings, options.with);
assertEquals("check with first", expected, bindings.get("one"));
@@ -130,6 +149,39 @@ public class GogoCommandsTest extends AbstractPipeTest {
public void testExecuteWithWriter() throws Exception {
PipeBuilder builder =
plumber.newPipe(context.resourceResolver()).echo("/content/${node}").$("nt:base");
String path = builder.build().getResource().getPath();
- ExecutionResult result =
commands.executeInternal(context.resourceResolver(), path, "@ outputs
title=jcr:title desc=jcr:description @ with node=fruits");
+ ExecutionResult result = commands.execute(context.resourceResolver(),
path, "@ outputs title=jcr:title desc=jcr:description @ with node=fruits");
+ }
+
+ String testServlet(Map<String,Object> params) throws ServletException,
IOException {
+ MockSlingHttpServletRequest request = context.request();
+ MockSlingHttpServletResponse response = context.response();
+ request.setParameterMap(params);
+ request.setMethod("POST");
+ commands.doPost(request, response);
+ if (response.getStatus() != 200) {
+ System.out.println(response.getOutputAsString());
+ }
+ assertEquals(200, response.getStatus());
+ return response.getOutputAsString();
+ }
+
+ @Test
+ public void testSimpleCommandServlet() throws IOException,
ServletException {
+ Map<String, Object> params = new HashMap<>();
+ params.put(CommandExecutorImpl.REQ_PARAM_CMD, "echo /content / mkdir
foo / write type=bar");
+ String response = testServlet(params);
+ assertEquals("{\"items\":[\"/content/foo\"],\"size\":1}\n", response);
+ }
+
+ @Test
+ public void testFileCommandServlet() throws IOException, ServletException {
+ Map<String, Object> params = new HashMap<>();
+ params.put(CommandExecutorImpl.REQ_PARAM_FILE,
IOUtils.toString(getClass().getResourceAsStream("/testcommand"
+ + ".txt"), "UTF-8"));
+ String response = testServlet(params);
+
assertEquals("{\"items\":[\"/content/beatles/john\",\"/content/beatles/paul\","
+ + "\"/content/beatles/georges\",\"/content/beatles/ringo\"],"
+ + "\"size\":4}\n"
+ +
"{\"items\":[\"/content/beatles/ringo/jcr:content\"],\"size\":1}\n", response);
}
}
\ No newline at end of file
diff --git
a/src/test/java/org/apache/sling/pipes/internal/PipeBuilderImplTest.java
b/src/test/java/org/apache/sling/pipes/internal/PipeBuilderImplTest.java
new file mode 100644
index 0000000..bd6475a
--- /dev/null
+++ b/src/test/java/org/apache/sling/pipes/internal/PipeBuilderImplTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sling.pipes.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.pipes.AbstractPipeTest;
+import org.junit.Test;
+
+public class PipeBuilderImplTest extends AbstractPipeTest {
+ boolean fetchBooleanResource(String path) {
+ Resource res = context.resourceResolver().getResource(path);
+ if (res != null) {
+ Boolean bool = res.adaptTo(Boolean.class);
+ if (bool != null) {
+ return bool;
+ }
+ }
+ return false;
+ }
+ @Test
+ public void testsimpleCreateResource() throws PersistenceException {
+ PipeBuilderImpl impl = new PipeBuilderImpl(context.resourceResolver(),
plumber);
+ Map<String, Object> map = new HashMap<>();
+ String rootPath = "/content/test";
+ map.put("simple", true);
+ Resource resource = impl.createResource(context.resourceResolver(),
rootPath, "nt:unstructured", map);
+ context.resourceResolver().commit();
+ assertTrue(fetchBooleanResource(rootPath + "/simple"));
+ }
+
+ @Test
+ public void testcreateResource() throws PersistenceException {
+ PipeBuilderImpl impl = new PipeBuilderImpl(context.resourceResolver(),
plumber);
+ Map<String, Object> map = new HashMap<>();
+ String rootPath = "/content/test";
+ map.put("one/levelDepth", true);
+ map.put("one/anotherLevel/depth", true);
+ Resource resource = impl.createResource(context.resourceResolver(),
rootPath, "nt:unstructured", map);
+ assertNotNull(resource);
+ assertEquals("returned resource should be the shallower resource where
we wrote", rootPath + "/one", resource.getPath());
+ context.resourceResolver().commit();
+ assertTrue(fetchBooleanResource(rootPath + "/one/levelDepth"));
+ assertTrue(fetchBooleanResource(rootPath + "/one/anotherLevel/depth"));
+ }
+}
diff --git a/src/test/resources/testcommand.txt
b/src/test/resources/testcommand.txt
new file mode 100644
index 0000000..940c815
--- /dev/null
+++ b/src/test/resources/testcommand.txt
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+mkdir /content / json ['john','paul','georges','ringo'] @ name beatle / mkdir
beatles/${beatle} / write jcr:content/isBeatle=true
jcr:content/firstName=${beatle} jcr:content/singer=${'ringo'==beatle?false:true}
+
+echo /content / xpath /jcr:root//element(*,nt:base)[@singer='false']
\ No newline at end of file