diff --git a/nuxeo-features/nuxeo-platform-imaging/nuxeo-platform-imaging-api/src/main/java/org/nuxeo/ecm/platform/picture/api/adapters/DefaultPictureAdapter.java b/nuxeo-features/nuxeo-platform-imaging/nuxeo-platform-imaging-api/src/main/java/org/nuxeo/ecm/platform/picture/api/adapters/DefaultPictureAdapter.java index a760148fc601..e9d58da6126f 100644 --- a/nuxeo-features/nuxeo-platform-imaging/nuxeo-platform-imaging-api/src/main/java/org/nuxeo/ecm/platform/picture/api/adapters/DefaultPictureAdapter.java +++ b/nuxeo-features/nuxeo-platform-imaging/nuxeo-platform-imaging-api/src/main/java/org/nuxeo/ecm/platform/picture/api/adapters/DefaultPictureAdapter.java @@ -32,14 +32,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.nuxeo.common.utils.FileUtils; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.api.PropertyException; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolder; import org.nuxeo.ecm.core.api.model.Property; -import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService; import org.nuxeo.ecm.platform.picture.api.ImageInfo; import org.nuxeo.ecm.platform.picture.api.ImagingConvertConstants; import org.nuxeo.ecm.platform.picture.api.ImagingService; @@ -72,24 +70,14 @@ public boolean fillPictureViews(Blob blob, String filename, String title, } File file = blob.getFile(); - CommandLineExecutorService commandLineExecutorService = Framework.getLocalService(CommandLineExecutorService.class); - boolean validFilename = file != null && commandLineExecutorService.isValidParameter(file.getName()); - if (file == null || !validFilename) { - String extension; - if (file != null) { - extension = "." + FileUtils.getFileExtension(file.getName()); - } else { - extension = ".jpg"; - } - file = File.createTempFile("nuxeoImage", extension); + if (file == null) { + file = File.createTempFile("nuxeoImage", ".jpg"); Framework.trackFile(file, this); blob.transferTo(file); // use a persistent blob with our file - if (!validFilename) { - String digest = blob.getDigest(); - blob = Blobs.createBlob(file, blob.getMimeType(), blob.getEncoding(), blob.getFilename()); - blob.setDigest(digest); - } + String digest = blob.getDigest(); + blob = Blobs.createBlob(file, blob.getMimeType(), blob.getEncoding(), blob.getFilename()); + blob.setDigest(digest); } fileContent = blob; diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CmdParameters.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CmdParameters.java index e0e20dd534a2..fbd873a47414 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CmdParameters.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CmdParameters.java @@ -1,10 +1,10 @@ /* - * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors. + * (C) Copyright 2006-2015 Nuxeo SAS (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html + * http://www.gnu.org/licenses/lgpl-2.1.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -12,33 +12,28 @@ * Lesser General Public License for more details. * * Contributors: - * Nuxeo - initial API and implementation - * - * $Id$ - * + * Thierry Delprat + * Vincent Dutat + * Vladimir Pasquier + * Julien Carsique + * Florent Guillaume */ - package org.nuxeo.ecm.platform.commandline.executor.api; import java.io.File; import java.io.Serializable; import java.util.HashMap; +import java.util.List; import java.util.Map; /** - * Wraps command parameters (String or File). Quoted by default. Use {@link #addNamedParameter(String, String, boolean)} - * to avoid quotes. - * - * @author tiry - * @author Vincent Dutat + * Wraps command parameters (String or File path, or a list of values that will be expanded as separate parameters). */ public class CmdParameters implements Serializable { private static final long serialVersionUID = 1L; - protected final Map params = new HashMap<>(); - - private final HashMap cmdParameters = new HashMap<>(); + private final Map params = new HashMap<>(); /** * It is recommended to use the CmdParameters instance returned by @@ -50,63 +45,75 @@ public CmdParameters() { } public void addNamedParameter(String name, String value) { - params.put(name, value); - // Quote by default - CmdParameter cmdParameter = new CmdParameter(value, true); - cmdParameters.put(name, cmdParameter); + params.put(name, new ParameterValue(value)); } public void addNamedParameter(String name, File file) { addNamedParameter(name, file.getAbsolutePath()); } - public Map getParameters() { - return params; - } - /** - * @since 7.1 + * @since 7.10 */ - public void addNamedParameter(String name, String value, boolean quote) { - params.put(name, value); - CmdParameter cmdParameter = new CmdParameter(value, quote); - cmdParameters.put(name, cmdParameter); + public void addNamedParameter(String name, List values) { + params.put(name, new ParameterValue(values)); } /** - * @since 7.1 + * @since 7.10 */ - public void addNamedParameter(String name, File file, boolean quote) { - addNamedParameter(name, file.getAbsolutePath(), quote); + public String getParameter(String name) { + ParameterValue param = params.get(name); + return param == null ? null : param.getValue(); } /** * @since 7.1 */ - public HashMap getCmdParameters() { - return cmdParameters; + public Map getParameters() { + return params; } /** - * @since 7.1 + * A parameter value holding either a single String or a list of Strings. + * + * @since 7.10 */ - public class CmdParameter { + public static class ParameterValue { private final String value; - private final boolean quote; + private final List values; - public CmdParameter(String value, boolean quote) { + public ParameterValue(String value) { this.value = value; - this.quote = quote; + values = null; + } + + /** + * @since 7.10 + */ + public ParameterValue(List values) { + this.values = values; + value = null; } public String getValue() { return value; } - public boolean isQuote() { - return quote; + public List getValues() { + return values; + } + + /** + * Checks whether this is multi-valued. + * + * @since 7.10 + */ + public boolean isMulti() { + return values != null; } } + } diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CommandLineExecutorService.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CommandLineExecutorService.java index 6b0441fd7429..14403a5098a6 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CommandLineExecutorService.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/api/CommandLineExecutorService.java @@ -19,7 +19,6 @@ package org.nuxeo.ecm.platform.commandline.executor.api; import java.util.List; -import java.util.regex.Pattern; /** * Interface for the service that manages commandline execution. @@ -28,10 +27,6 @@ */ public interface CommandLineExecutorService { - Pattern VALID_PARAMETER_PATTERN = Pattern.compile("[\\p{L}_0-9-.%:=/\\\\ ]+"); - - Pattern VALID_PARAMETER_PATTERN_WIN = Pattern.compile("[\\p{L}_0-9-.%~:=/\\\\ ()]+"); - CommandAvailability getCommandAvailability(String commandName); ExecResult execCommand(String commandName, CmdParameters params) throws CommandNotAvailable; @@ -40,22 +35,6 @@ public interface CommandLineExecutorService { List getAvailableCommands(); - /** - * Returns true if the given {@code parameter} is valid to be used in a command. - * - * @since 5.7 - */ - boolean isValidParameter(String parameter); - - /** - * Checks if the given {@code parameter} is valid to be used in a command. - *

- * If not, throws an {@code IllegalArgumentException}. - * - * @since 5.7 - */ - void checkParameter(String parameter); - /** * @return a new {@link CmdParameters} pre-filled with commonly used parameters such as the tmp dir. * @since 7.4 diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineDescriptor.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineDescriptor.java index 8eb041b844c4..d9903d547750 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineDescriptor.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineDescriptor.java @@ -20,6 +20,8 @@ import java.io.Serializable; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; import org.apache.commons.lang3.SystemUtils; import org.nuxeo.common.xmap.annotation.XNode; @@ -123,4 +125,9 @@ public String getExecutor() { return CommandLineExecutorComponent.DEFAULT_EXECUTOR; } + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + } diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineExecutorComponent.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineExecutorComponent.java index f5cc9dd3c60b..d3fb5eeb2571 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineExecutorComponent.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/CommandLineExecutorComponent.java @@ -22,9 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Pattern; -import org.apache.commons.lang3.SystemUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -208,31 +206,6 @@ public List getAvailableCommands() { return cmds; } - @Override - public boolean isValidParameter(String parameter) { - Pattern VALID_PATTERN; - if (SystemUtils.IS_OS_WINDOWS) { - VALID_PATTERN = VALID_PARAMETER_PATTERN_WIN; - } else { - VALID_PATTERN = VALID_PARAMETER_PATTERN; - } - return VALID_PATTERN.matcher(parameter).matches(); - } - - @Override - public void checkParameter(String parameter) { - if (!isValidParameter(parameter)) { - Pattern VALID_PATTERN; - if (SystemUtils.IS_OS_WINDOWS) { - VALID_PATTERN = VALID_PARAMETER_PATTERN_WIN; - } else { - VALID_PATTERN = VALID_PARAMETER_PATTERN; - } - throw new IllegalArgumentException(String.format("'%s' contains illegal characters. It should match: %s", - parameter, VALID_PATTERN)); - } - } - // ****************************************** // for testing diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/cmdtesters/SystemPathExistTester.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/cmdtesters/SystemPathExistTester.java index 073ebed3a097..ce8eaad683ff 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/cmdtesters/SystemPathExistTester.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/cmdtesters/SystemPathExistTester.java @@ -32,12 +32,14 @@ */ public class SystemPathExistTester implements CommandTester { + @Override public CommandTestResult test(CommandLineDescriptor cmdDescriptor) { String cmd = cmdDescriptor.getCommand(); try { - Runtime.getRuntime().exec(cmd); + Runtime.getRuntime().exec(new String[] { cmd }); } catch (IOException e) { - return new CommandTestResult("command " + cmd + " not found in system path"); + return new CommandTestResult( + "command " + cmd + " not found in system path (descriptor " + cmdDescriptor + ")"); } return new CommandTestResult(); diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/executors/AbstractExecutor.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/executors/AbstractExecutor.java deleted file mode 100644 index 4ff52a62bf04..000000000000 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/executors/AbstractExecutor.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and contributors. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public License - * (LGPL) version 2.1 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl-2.1.html - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * Contributors: - * Nuxeo - initial API and implementation - * tdelprat, jcarsique - * - */ - -package org.nuxeo.ecm.platform.commandline.executor.service.executors; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.regex.Pattern; - -import org.apache.commons.lang3.SystemUtils; - -import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters; -import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters.CmdParameter; -import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService; -import org.nuxeo.ecm.platform.commandline.executor.service.CommandLineDescriptor; -import org.nuxeo.runtime.api.Framework; - -/** - * Base class for {@link Executor}. - * - * @author tiry - */ -public abstract class AbstractExecutor implements Executor { - - /** - * @deprecated since 5.7. See {@link CommandLineExecutorService#checkParameter(String)}. - */ - @Deprecated - public static final Pattern VALID_PARAMETER_PATTERN = Pattern.compile("[\\p{L}_0-9-.%:=/\\\\ ]+"); - - /** - * @deprecated Since 7.4. Use {@link SystemUtils#IS_OS_WINDOWS} - */ - @Deprecated - public static boolean isWindows() { - return SystemUtils.IS_OS_WINDOWS; - } - - /** - * Returns parameters as a String after having replaced parameterized values inside. - * - * @param cmdDesc CommandLineDescriptor containing parameters - * @param params parameterized values - * @return Parameters as a String - */ - public static String getParametersString(CommandLineDescriptor cmdDesc, CmdParameters params) { - String paramString = cmdDesc.getParametersString(); - paramString = replaceParams(params, paramString); - return paramString; - } - - /** - * Returns parameters as a String array after having replaced parameterized values inside. - * - * @param cmdDesc CommandLineDescriptor containing parameters - * @param params parameterized values - * @return Parameters as a String array - * @since 5.5 - */ - public static String[] getParametersArray(CommandLineDescriptor cmdDesc, CmdParameters params) { - List res = new ArrayList<>(); - String[] paramsArray = cmdDesc.getParametersString().split(" "); - for (String paramString : paramsArray) { - res.add(replaceParams(params, paramString)); - } - return res.toArray(new String[] {}); - } - - private static String replaceParams(CmdParameters params, String paramString) { - CommandLineExecutorService commandLineExecutorService = Framework.getLocalService(CommandLineExecutorService.class); - HashMap paramsValues = params.getCmdParameters(); - for (String pname : paramsValues.keySet()) { - String param = "#{" + pname + "}"; - if (paramString.contains(param)) { - CmdParameters.CmdParameter cmdParameter = paramsValues.get(pname); - String value = cmdParameter.getValue(); - commandLineExecutorService.checkParameter(value); - if (cmdParameter.isQuote()) { - paramString = paramString.replace("#{" + pname + "}", String.format("\"%s\"", value)); - } else { - paramString = paramString.replace("#{" + pname + "}", String.format("%s", value)); - } - } - } - return paramString; - } -} diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/executors/ShellExecutor.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/executors/ShellExecutor.java index 4337bfff8f73..6284e2bca23d 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/executors/ShellExecutor.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/main/java/org/nuxeo/ecm/platform/commandline/executor/service/executors/ShellExecutor.java @@ -12,38 +12,42 @@ * Lesser General Public License for more details. * * Contributors: - * Nuxeo - initial API and implementation - * tdelprat, jcarsique - * + * Thierry Delprat + * Julien Carsique + * Florent Guillaume */ - package org.nuxeo.ecm.platform.commandline.executor.service.executors; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -import org.apache.commons.lang.ArrayUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.SystemUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.commons.logging.impl.SimpleLog; - +import org.nuxeo.common.utils.ExceptionUtils; import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters; import org.nuxeo.ecm.platform.commandline.executor.api.ExecResult; +import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters.ParameterValue; import org.nuxeo.ecm.platform.commandline.executor.service.CommandLineDescriptor; import org.nuxeo.ecm.platform.commandline.executor.service.EnvironmentDescriptor; -import org.nuxeo.log4j.ThreadedStreamGobbler; /** * Default implementation of the {@link Executor} interface. Use simple shell exec. - * - * @author tiry */ -public class ShellExecutor extends AbstractExecutor { +public class ShellExecutor implements Executor { private static final Log log = LogFactory.getLog(ShellExecutor.class); @@ -53,60 +57,166 @@ public ExecResult exec(CommandLineDescriptor cmdDesc, CmdParameters params) { return exec(cmdDesc, params, new EnvironmentDescriptor()); } + protected static final AtomicInteger PIPE_COUNT = new AtomicInteger(); + + /** Used to split the contributed command, NOT the passed parameter values. */ + protected static final Pattern COMMAND_SPLIT = Pattern.compile("\"([^\"]*)\"|'([^']*)'|[^\\s]+"); + @Override public ExecResult exec(CommandLineDescriptor cmdDesc, CmdParameters params, EnvironmentDescriptor env) { - long t0 = System.currentTimeMillis(); - List output = Collections.synchronizedList(new ArrayList()); - - String[] cmd; + String commandLine = cmdDesc.getCommand() + " " + String.join(" ", cmdDesc.getParametersString()); try { - if (SystemUtils.IS_OS_WINDOWS) { - String[] paramsArray = getParametersArray(cmdDesc, params); - cmd = new String[] { "cmd", "/C", cmdDesc.getCommand() }; - cmd = (String[]) ArrayUtils.addAll(cmd, paramsArray); + if (log.isDebugEnabled()) { + log.debug("Running system command: " + commandLine); + } + long t0 = System.currentTimeMillis(); + ExecResult res = exec1(cmdDesc, params, env); + long t1 = System.currentTimeMillis(); + return new ExecResult(commandLine, res.getOutput(), t1 - t0, res.getReturnCode()); + } catch (IOException e) { + return new ExecResult(commandLine, e); + } + } + + protected ExecResult exec1(CommandLineDescriptor cmdDesc, CmdParameters params, EnvironmentDescriptor env) + throws IOException { + // split the configured parameters while keeping quoted parts intact + List list = new ArrayList<>(); + list.add(cmdDesc.getCommand()); + Matcher m = COMMAND_SPLIT.matcher(cmdDesc.getParametersString()); + while (m.find()) { + String word; + if (m.group(1) != null) { + word = m.group(1); // double-quoted + } else if (m.group(2) != null) { + word = m.group(2); // single-quoted } else { - String paramsString = getParametersString(cmdDesc, params); - cmd = new String[] { "/bin/sh", "-c", cmdDesc.getCommand() + " " + paramsString }; + word = m.group(); // word } - } catch (IllegalArgumentException e) { - return new ExecResult(cmdDesc.getCommand(), e); + List words = replaceParams(word, params); + list.addAll(words); } - String commandLine = StringUtils.join(cmd, " "); - Process p1; - try { - if (log.isDebugEnabled()) { - log.debug("Running system command: " + commandLine); + List processes = new LinkedList<>(); + List pipes = new LinkedList<>(); + List command = new LinkedList<>(); + Process process = null; + for (Iterator it = list.iterator(); it.hasNext();) { + String word = it.next(); + boolean build; + if (word.equals("|")) { + build = true; + } else { + command.add(word); + build = !it.hasNext(); } - ProcessBuilder processBuilder = new ProcessBuilder(cmd).directory(new File(env.getWorkingDirectory())); + if (!build) { + continue; + } + ProcessBuilder processBuilder = new ProcessBuilder(command); + command = new LinkedList<>(); // reset for next loop + processBuilder.directory(new File(env.getWorkingDirectory())); processBuilder.environment().putAll(env.getParameters()); - p1 = processBuilder.start(); - } catch (IOException e) { - return new ExecResult(commandLine, e); + processBuilder.redirectErrorStream(true); + Process newProcess = processBuilder.start(); + processes.add(newProcess); + if (process == null) { + // first process, nothing to input + IOUtils.closeQuietly(newProcess.getOutputStream()); + } else { + // pipe previous process output into new process input + // needs a thread doing the piping because Java has no way to connect two children processes directly + // except through a filesystem named pipe but that can't be created in a portable manner + Thread pipe = pipe(process.getInputStream(), newProcess.getOutputStream()); + pipes.add(pipe); + } + process = newProcess; } - ThreadedStreamGobbler out, err; - if (cmdDesc.getReadOutput()) { - out = new ThreadedStreamGobbler(p1.getInputStream(), output); - err = new ThreadedStreamGobbler(p1.getErrorStream(), output); - } else { - out = new ThreadedStreamGobbler(p1.getInputStream(), SimpleLog.LOG_LEVEL_DEBUG); - err = new ThreadedStreamGobbler(p1.getErrorStream(), SimpleLog.LOG_LEVEL_ERROR); + // get result from last process + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + List output = new ArrayList<>(); + while ((line = reader.readLine()) != null) { + output.add(line); } + reader.close(); - err.start(); - out.start(); + // wait for all processes, get first non-0 exit status + int returnCode = 0; + for (Process p : processes) { + try { + int exitCode = p.waitFor(); + if (returnCode == 0) { + returnCode = exitCode; + } + } catch (InterruptedException e) { + ExceptionUtils.checkInterrupt(e); + } + } - int exitCode = 0; - try { - exitCode = p1.waitFor(); - out.join(); - err.join(); - } catch (InterruptedException e) { - return new ExecResult(commandLine, e); + // wait for all pipes + for (Thread t : pipes) { + try { + t.join(); + } catch (InterruptedException e) { + ExceptionUtils.checkInterrupt(e); + } } - long t1 = System.currentTimeMillis(); - return new ExecResult(commandLine, output, t1 - t0, exitCode); + return new ExecResult(null, output, 0, returnCode); } + + /** + * Returns a started daemon thread piping bytes from the InputStream to the OutputStream. + *

+ * The streams are both closed when the copy is finished. + * + * @since 7.10 + */ + public static Thread pipe(InputStream in, OutputStream out) { + Runnable run = new Runnable() { + @Override + public void run() { + try { + IOUtils.copy(in, out); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(out); + } + } + }; + Thread thread = new Thread(run, "Nuxeo-pipe-" + PIPE_COUNT.incrementAndGet()); + thread.setDaemon(true); + thread.start(); + return thread; + } + + /** + * Expands parameter strings in a parameter word. + *

+ * This may return several words if the parameter value is marked as a list. + * + * @since 7.10 + */ + public static List replaceParams(String word, CmdParameters params) { + for (Entry es : params.getParameters().entrySet()) { + String name = es.getKey(); + ParameterValue paramVal = es.getValue(); + String param = "#{" + name + "}"; + if (paramVal.isMulti()) { + if (word.equals(param)) { + return paramVal.getValues(); + } + } else if (word.contains(param)) { + word = word.replace(param, paramVal.getValue()); + } + + } + return Collections.singletonList(word); + } + } diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestCommands.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestCommands.java index 281323c6a535..0fd14e0990ac 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestCommands.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestCommands.java @@ -18,20 +18,22 @@ package org.nuxeo.ecm.platform.commandline.executor.tests; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + import java.io.File; +import java.util.Arrays; import java.util.List; +import org.apache.commons.lang3.SystemUtils; import org.junit.Before; import org.junit.Test; - -import static org.junit.Assert.*; - import org.nuxeo.ecm.platform.commandline.executor.api.CmdParameters; import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService; import org.nuxeo.ecm.platform.commandline.executor.api.ExecResult; -import org.nuxeo.ecm.platform.commandline.executor.service.CommandLineDescriptor; -import org.nuxeo.ecm.platform.commandline.executor.service.CommandLineExecutorComponent; -import org.nuxeo.ecm.platform.commandline.executor.service.executors.AbstractExecutor; +import org.nuxeo.ecm.platform.commandline.executor.service.executors.ShellExecutor; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.test.NXRuntimeTestCase; @@ -51,47 +53,34 @@ public void setUp() throws Exception { } @Test - public void testCmdParamatersParsing() throws Exception { + public void testReplaceParams() throws Exception { CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class); - assertNotNull(cles); - - deployContrib("org.nuxeo.ecm.platform.commandline.executor", "OSGI-INF/commandline-aspell-test-contribs.xml"); - List cmds = cles.getRegistredCommands(); - assertNotNull(cmds); - assertEquals(1, cmds.size()); - assertTrue(cmds.contains("aspell")); - - CommandLineDescriptor cmdDesc = CommandLineExecutorComponent.getCommandDescriptor("aspell"); - - File textFile = File.createTempFile("testMe", "txt"); - String textFilePath = "/tmp/textMe.txt"; - CmdParameters params = cles.getDefaultCmdParameters(); - params.addNamedParameter("lang", "fr_FR"); - params.addNamedParameter("encoding", "utf-8"); - - // test String params - params.addNamedParameter("textFile", textFilePath); - String parsedParamString = AbstractExecutor.getParametersString(cmdDesc, params); - assertEquals("-a --lang=\"fr_FR\" --encoding=\"utf-8\" -H --rem-sgml-check=alt < \"/tmp/textMe.txt\"", - parsedParamString); - - // test with File param - params.addNamedParameter("textFile", textFile); - parsedParamString = AbstractExecutor.getParametersString(cmdDesc, params); - assertTrue(parsedParamString.startsWith("-a --lang=\"fr_FR\" --encoding=\"utf-8\" -H --rem-sgml-check=alt < ")); - assertTrue(parsedParamString.contains(System.getProperty("java.io.tmpdir"))); - - String[] res = AbstractExecutor.getParametersArray(cmdDesc, params); - assertEquals(7, res.length); - assertEquals("-a", res[0]); - assertEquals("--lang=\"fr_FR\"", res[1]); - assertEquals("--encoding=\"utf-8\"", res[2]); - assertEquals("-H", res[3]); - assertEquals("--rem-sgml-check=alt", res[4]); - assertEquals("<", res[5]); - assertTrue(res[6].startsWith("\"" + System.getProperty("java.io.tmpdir"))); - assertTrue(res[6].contains("testMe")); + + // test default param + List res = ShellExecutor.replaceParams("-tmp=#{java.io.tmpdir}", params); + assertEquals(Arrays.asList("-tmp=" + System.getProperty("java.io.tmpdir")), res); + + // test String param + params.addNamedParameter("foo", "/some/path"); + res = ShellExecutor.replaceParams("foo=#{foo}", params); + assertEquals(Arrays.asList("foo=/some/path"), res); + params.addNamedParameter("width", "320"); + params.addNamedParameter("height", "200"); + res = ShellExecutor.replaceParams("#{width}x#{height}", params); + assertEquals(Arrays.asList("320x200"), res); + + // test File param + File tmp = File.createTempFile("testCommands", "txt"); + tmp.delete(); + params.addNamedParameter("foo", tmp); + res = ShellExecutor.replaceParams("-file=#{foo}[0]", params); + assertEquals(Arrays.asList("-file=" + tmp.getAbsolutePath() + "[0]"), res); + + // test List param + params.addNamedParameter("tags", Arrays.asList("-foo", "-bar", "-baz")); + res = ShellExecutor.replaceParams("#{tags}", params); + assertEquals(Arrays.asList("-foo", "-bar", "-baz"), res); } @Test @@ -102,24 +91,27 @@ public void testCmdEnvironment() throws Exception { deployContrib("org.nuxeo.ecm.platform.commandline.executor", "OSGI-INF/commandline-env-test-contrib.xml"); List cmds = cles.getRegistredCommands(); assertNotNull(cmds); - assertTrue(cmds.contains("env")); assertTrue(cmds.contains("echo")); - ExecResult result = cles.execCommand("env", cles.getDefaultCmdParameters()); - assertTrue(result.isSuccessful()); - assertSame(0, result.getReturnCode()); - boolean found = false; - for (String line : result.getOutput()) { - if (line.contains("TMP")) { - found = true; - assertTrue(line.contains(System.getProperty("java.io.tmpdir"))); - } - } - assertTrue(found); - - result = cles.execCommand("echo", cles.getDefaultCmdParameters()); + ExecResult result = cles.execCommand("echo", cles.getDefaultCmdParameters()); assertTrue(result.isSuccessful()); assertSame(0, result.getReturnCode()); assertTrue(String.join("", result.getOutput()).contains(System.getProperty("java.io.tmpdir"))); } + + @Test + public void testCmdPipe() throws Exception { + CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class); + + deployContrib("org.nuxeo.ecm.platform.commandline.executor", "OSGI-INF/commandline-env-test-contrib.xml"); + + ExecResult result = cles.execCommand("pipe", cles.getDefaultCmdParameters()); + assertTrue(result.isSuccessful()); + assertEquals(0, result.getReturnCode()); + String line = String.join("", result.getOutput()); + // window's echo displays things exactly as is including quotes + String expected = SystemUtils.IS_OS_WINDOWS ? "\"a b\" \"c d\" e" : "a b c d e"; + assertEquals(expected, line); + } + } diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestService.java b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestService.java index dcc435c6f614..b26883747ac5 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestService.java +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/java/org/nuxeo/ecm/platform/commandline/executor/tests/TestService.java @@ -18,14 +18,16 @@ package org.nuxeo.ecm.platform.commandline.executor.tests; +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.junit.Assert.fail; + import java.util.List; -import org.apache.commons.lang3.SystemUtils; import org.junit.Before; import org.junit.Test; - -import static org.junit.Assert.*; - import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService; import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable; import org.nuxeo.runtime.api.Framework; @@ -112,20 +114,4 @@ public void testCmdExecption() { } } - @Test - public void testIllegalCharactersInParameters() { - CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class); - - assertTrue(cles.isValidParameter("only/valid:%chars.")); - assertTrue(cles.isValidParameter("Non-latin words such as \u0625\u0646\u062a\u0631\u0646\u062a are valid.")); - - if (SystemUtils.IS_OS_WINDOWS) { - assertTrue(cles.isValidParameter("(parentheses) are valid characters")); - } else { - assertFalse(cles.isValidParameter("(parentheses) are invalid characters")); - } - assertFalse(cles.isValidParameter("\"quotes\" are invalid characters")); - assertFalse(cles.isValidParameter("exclamation marks! are invalid characters")); - } - } diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-aspell-test-contribs.xml b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-aspell-test-contribs.xml similarity index 100% rename from nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-aspell-test-contribs.xml rename to nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-aspell-test-contribs.xml diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-dummy-test-contrib.xml b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-dummy-test-contrib.xml similarity index 100% rename from nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-dummy-test-contrib.xml rename to nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-dummy-test-contrib.xml diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-env-test-contrib.xml b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-env-test-contrib.xml similarity index 59% rename from nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-env-test-contrib.xml rename to nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-env-test-contrib.xml index 912bae3794c5..02a088679211 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-env-test-contrib.xml +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-env-test-contrib.xml @@ -2,15 +2,20 @@ - - env + + echo + #{java.io.tmpdir} + cmd + /C echo #{java.io.tmpdir} - + echo - #{java.io.tmpdir} + e | xargs echo "a b" 'c d' + cmd + /C echo bla | cmd /C echo "a b" "c d" e diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-imagemagic-test-contrib.xml b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-imagemagic-test-contrib.xml similarity index 100% rename from nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-imagemagic-test-contrib.xml rename to nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-imagemagic-test-contrib.xml diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-imagemagic-test-contrib2.xml b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-imagemagic-test-contrib2.xml similarity index 100% rename from nuxeo-services/nuxeo-platform-commandline-executor/src/main/resources/OSGI-INF/commandline-imagemagic-test-contrib2.xml rename to nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/OSGI-INF/commandline-imagemagic-test-contrib2.xml diff --git a/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/log4j.xml b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/log4j.xml index c0584909af1c..d8ea8b802194 100644 --- a/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/log4j.xml +++ b/nuxeo-services/nuxeo-platform-commandline-executor/src/test/resources/log4j.xml @@ -22,7 +22,7 @@ - + diff --git a/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/CommandLineConverter.java b/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/CommandLineConverter.java index e7c21f49c379..a3c9587bca48 100644 --- a/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/CommandLineConverter.java +++ b/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/CommandLineConverter.java @@ -119,7 +119,7 @@ protected Map getCmdStringParameters(BlobHolder blobHolder, Map< @Override protected BlobHolder buildResult(List cmdOutput, CmdParameters cmdParams) throws ConversionException { - String outputPath = cmdParams.getParameters().get(OUT_DIR_PATH_KEY); + String outputPath = cmdParams.getParameter(OUT_DIR_PATH_KEY); List blobs = new ArrayList<>(); File outputDir = new File(outputPath); if (outputDir.exists() && outputDir.isDirectory()) { diff --git a/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2HtmlConverter.java b/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2HtmlConverter.java index a98bd9dc4f35..ae44b78bafa7 100644 --- a/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2HtmlConverter.java +++ b/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2HtmlConverter.java @@ -44,7 +44,7 @@ public class PDF2HtmlConverter extends CommandLineBasedConverter { @Override protected BlobHolder buildResult(List cmdOutput, CmdParameters cmdParams) { - String outputPath = cmdParams.getParameters().get("outDirPath"); + String outputPath = cmdParams.getParameter("outDirPath"); File outputDir = new File(outputPath); File[] files = outputDir.listFiles(); List blobs = new ArrayList(); diff --git a/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2ImageConverter.java b/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2ImageConverter.java index 344d2e5b6b5f..f66882488214 100644 --- a/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2ImageConverter.java +++ b/nuxeo-services/nuxeo-platform-convert/src/main/java/org/nuxeo/ecm/platform/convert/plugins/PDF2ImageConverter.java @@ -44,7 +44,7 @@ public class PDF2ImageConverter extends CommandLineBasedConverter { @Override protected BlobHolder buildResult(List cmdOutput, CmdParameters cmdParams) { - String outputPath = cmdParams.getParameters().get("outDirPath"); + String outputPath = cmdParams.getParameter("outDirPath"); File outputDir = new File(outputPath); File[] files = outputDir.listFiles(); List blobs = new ArrayList();