Repository: nifi
Updated Branches:
  refs/heads/master e53ab391f -> 96764ed6a


NIFI-604: Custom Argument Delimiters ExecuteStreamCommand / ExecuteProcess

- Unified the way ExecuteStreamCommand and ExecuteProcess handle arguments
- Argument delimiters can now be specified. Their default being what they were 
using before (; and space)


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/9cefc4a5
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/9cefc4a5
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/9cefc4a5

Branch: refs/heads/master
Commit: 9cefc4a5a54adf63bf4eb67c4b8211f2a2b5dd14
Parents: d39848f
Author: ricky <ri...@cloudera.com>
Authored: Thu Aug 27 15:02:37 2015 -0400
Committer: ricky <ri...@cloudera.com>
Committed: Thu Aug 27 15:26:56 2015 -0400

----------------------------------------------------------------------
 .../nifi/processor/util/StandardValidators.java | 28 +++++++
 .../processors/standard/ExecuteProcess.java     | 62 +++++-----------
 .../standard/ExecuteStreamCommand.java          | 17 ++++-
 .../processors/standard/util/ArgumentUtils.java | 77 ++++++++++++++++++++
 .../processors/standard/TestExecuteProcess.java | 13 ++--
 .../standard/TestExecuteStreamCommand.java      | 22 ++++++
 6 files changed, 166 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/9cefc4a5/nifi-commons/nifi-processor-utilities/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
----------------------------------------------------------------------
diff --git 
a/nifi-commons/nifi-processor-utilities/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
 
b/nifi-commons/nifi-processor-utilities/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
index 37ba7d8..7777438 100644
--- 
a/nifi-commons/nifi-processor-utilities/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
+++ 
b/nifi-commons/nifi-processor-utilities/src/main/java/org/apache/nifi/processor/util/StandardValidators.java
@@ -602,6 +602,34 @@ public class StandardValidators {
         }
     }
 
+    public static class StringLengthValidator implements Validator {
+        private final int minimum;
+        private final int maximum;
+
+        public StringLengthValidator(int minimum, int maximum) {
+            this.minimum = minimum;
+            this.maximum = maximum;
+        }
+
+        @Override
+        public ValidationResult validate(final String subject, final String 
value, final ValidationContext context) {
+            if (value.length() < minimum || value.length() > maximum) {
+                return new ValidationResult.Builder()
+                  .subject(subject)
+                  .valid(false)
+                  .input(value)
+                  .explanation(String.format("String length invalid [min: %d, 
max: %d]", minimum, maximum))
+                  .build();
+            } else {
+                return new ValidationResult.Builder()
+                  .valid(true)
+                  .input(value)
+                  .subject(subject)
+                  .build();
+            }
+        }
+    }
+
     public static class DirectoryExistsValidator implements Validator {
 
         private final boolean allowEL;

http://git-wip-us.apache.org/repos/asf/nifi/blob/9cefc4a5/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java
index f6085e7..c8a67a0 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteProcess.java
@@ -48,6 +48,7 @@ import org.apache.nifi.annotation.documentation.Tags;
 import org.apache.nifi.annotation.lifecycle.OnScheduled;
 import org.apache.nifi.annotation.lifecycle.OnUnscheduled;
 import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.Validator;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.logging.ProcessorLog;
 import org.apache.nifi.processor.AbstractProcessor;
@@ -57,6 +58,7 @@ import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.io.OutputStreamCallback;
 import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.standard.util.ArgumentUtils;
 
 @Tags({"command", "process", "source", "external", "invoke", "script"})
 @CapabilityDescription("Runs an operating system command specified by the user 
and writes the output of that command to a FlowFile. If the command is expected 
"
@@ -110,6 +112,18 @@ public class ExecuteProcess extends AbstractProcessor {
             .addValidator(StandardValidators.BOOLEAN_VALIDATOR)
             .build();
 
+    private static final Validator characterValidator = new 
StandardValidators.StringLengthValidator(1, 1);
+
+    static final PropertyDescriptor ARG_DELIMITER = new 
PropertyDescriptor.Builder()
+      .name("Argument Delimiter")
+      .description("Delimiter to use to separate arguments for a command 
[default: space]. Must be a single character.")
+      .addValidator(Validator.VALID)
+      .addValidator(characterValidator)
+      .required(true)
+      .defaultValue(" ")
+      .build();
+
+
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
     .name("success")
     .description("All created FlowFiles are routed to this relationship")
@@ -132,6 +146,7 @@ public class ExecuteProcess extends AbstractProcessor {
         properties.add(COMMAND_ARGUMENTS);
         properties.add(BATCH_DURATION);
         properties.add(REDIRECT_ERROR_STREAM);
+        properties.add(ARG_DELIMITER);
         return properties;
     }
 
@@ -145,51 +160,7 @@ public class ExecuteProcess extends AbstractProcessor {
         .build();
     }
 
-    static List<String> splitArgs(final String input) {
-        if (input == null) {
-            return Collections.emptyList();
-        }
-
-        final List<String> args = new ArrayList<>();
-
-        final String trimmed = input.trim();
-        boolean inQuotes = false;
-
-        final StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < trimmed.length(); i++) {
-            final char c = trimmed.charAt(i);
-            switch (c) {
-                case ' ':
-                case '\t':
-                case '\r':
-                case '\n': {
-                    if (inQuotes) {
-                        sb.append(c);
-                    } else {
-                        final String arg = sb.toString().trim();
-                        if (!arg.isEmpty()) {
-                            args.add(arg);
-                        }
-                        sb.setLength(0);
-                    }
-                    break;
-                }
-                case '"':
-                    inQuotes = !inQuotes;
-                    break;
-                default:
-                    sb.append(c);
-                    break;
-            }
-        }
-
-        final String finalArg = sb.toString().trim();
-        if (!finalArg.isEmpty()) {
-            args.add(finalArg);
-        }
 
-        return args;
-    }
 
     @OnScheduled
     public void setupExecutor(final ProcessContext context) {
@@ -293,7 +264,8 @@ public class ExecuteProcess extends AbstractProcessor {
 
     protected List<String> createCommandStrings(final ProcessContext context) {
         final String command = context.getProperty(COMMAND).getValue();
-        final List<String> args = 
splitArgs(context.getProperty(COMMAND_ARGUMENTS).getValue());
+        final List<String> args = 
ArgumentUtils.splitArgs(context.getProperty(COMMAND_ARGUMENTS).getValue(),
+          context.getProperty(ARG_DELIMITER).getValue().charAt(0));
 
         final List<String> commandStrings = new ArrayList<>(args.size() + 1);
         commandStrings.add(command);

http://git-wip-us.apache.org/repos/asf/nifi/blob/9cefc4a5/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java
index 676bd07..633ce61 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ExecuteStreamCommand.java
@@ -56,6 +56,7 @@ import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.io.InputStreamCallback;
 import org.apache.nifi.processor.io.OutputStreamCallback;
 import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.standard.util.ArgumentUtils;
 import org.apache.nifi.stream.io.BufferedInputStream;
 import org.apache.nifi.stream.io.BufferedOutputStream;
 import org.apache.nifi.stream.io.StreamUtils;
@@ -163,7 +164,7 @@ public class ExecuteStreamCommand extends AbstractProcessor 
{
                 public ValidationResult validate(String subject, String input, 
ValidationContext context) {
                     ValidationResult result = new ValidationResult.Builder()
                     .subject(subject).valid(true).input(input).build();
-                    String[] args = input.split(";");
+                    List<String> args = ArgumentUtils.splitArgs(input, 
context.getProperty(ARG_DELIMITER).getValue().charAt(0));
                     for (String arg : args) {
                         ValidationResult valResult = 
ATTRIBUTE_EXPRESSION_LANGUAGE_VALIDATOR.validate(subject, arg, context);
                         if (!valResult.isValid()) {
@@ -191,6 +192,17 @@ public class ExecuteStreamCommand extends 
AbstractProcessor {
             .defaultValue("false")
             .build();
 
+    private static final Validator characterValidator = new 
StandardValidators.StringLengthValidator(1, 1);
+
+    static final PropertyDescriptor ARG_DELIMITER = new 
PropertyDescriptor.Builder()
+            .name("Argument Delimiter")
+            .description("Delimiter to use to separate arguments for a command 
[default: ;]. Must be a single character")
+            .addValidator(Validator.VALID)
+            .addValidator(characterValidator)
+            .required(true)
+            .defaultValue(";")
+            .build();
+
 
     private static final List<PropertyDescriptor> PROPERTIES;
 
@@ -200,6 +212,7 @@ public class ExecuteStreamCommand extends AbstractProcessor 
{
         props.add(EXECUTION_COMMAND);
         props.add(IGNORE_STDIN);
         props.add(WORKING_DIR);
+        props.add(ARG_DELIMITER);
         PROPERTIES = Collections.unmodifiableList(props);
     }
 
@@ -243,7 +256,7 @@ public class ExecuteStreamCommand extends AbstractProcessor 
{
         final String commandArguments = 
context.getProperty(EXECUTION_ARGUMENTS).getValue();
         final boolean ignoreStdin = 
Boolean.parseBoolean(context.getProperty(IGNORE_STDIN).getValue());
         if (!StringUtils.isBlank(commandArguments)) {
-            for (String arg : commandArguments.split(";")) {
+            for (String arg : ArgumentUtils.splitArgs(commandArguments, 
context.getProperty(ARG_DELIMITER).getValue().charAt(0))) {
                 
args.add(context.newPropertyValue(arg).evaluateAttributeExpressions(inputFlowFile).getValue());
             }
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/9cefc4a5/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/ArgumentUtils.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/ArgumentUtils.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/ArgumentUtils.java
new file mode 100644
index 0000000..dff6ac7
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/util/ArgumentUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.nifi.processors.standard.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ArgumentUtils {
+    private final static char QUOTE = '"';
+    private final static List<Character> DELIMITING_CHARACTERS = new 
ArrayList<>(3);
+
+    static {
+        DELIMITING_CHARACTERS.add('\t');
+        DELIMITING_CHARACTERS.add('\r');
+        DELIMITING_CHARACTERS.add('\n');
+    }
+
+    public static List<String> splitArgs(final String input, final char 
definedDelimiter) {
+        if (input == null) {
+            return Collections.emptyList();
+        }
+
+        final List<String> args = new ArrayList<>();
+
+        final String trimmed = input.trim();
+        boolean inQuotes = false;
+        final StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < trimmed.length(); i++) {
+            final char c = trimmed.charAt(i);
+
+            if (DELIMITING_CHARACTERS.contains(c) || c == definedDelimiter) {
+                if (inQuotes) {
+                    sb.append(c);
+                } else {
+                    final String arg = sb.toString().trim();
+                    if (!arg.isEmpty()) {
+                        args.add(arg);
+                    }
+                    sb.setLength(0);
+                }
+                continue;
+            }
+
+            if (c == QUOTE) {
+                inQuotes = !inQuotes;
+                continue;
+            }
+
+            sb.append(c);
+        }
+
+        final String finalArg = sb.toString().trim();
+
+        if (!finalArg.isEmpty()) {
+            args.add(finalArg);
+        }
+
+        return args;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/9cefc4a5/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteProcess.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteProcess.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteProcess.java
index ff98dfa..22feef9 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteProcess.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteProcess.java
@@ -24,6 +24,7 @@ import java.io.File;
 import java.util.List;
 
 import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processors.standard.util.ArgumentUtils;
 import org.apache.nifi.util.MockFlowFile;
 import org.apache.nifi.util.TestRunner;
 import org.apache.nifi.util.TestRunners;
@@ -34,28 +35,28 @@ public class TestExecuteProcess {
 
     @Test
     public void testSplitArgs() {
-        final List<String> nullArgs = ExecuteProcess.splitArgs(null);
+        final List<String> nullArgs = ArgumentUtils.splitArgs(null, ' ');
         assertNotNull(nullArgs);
         assertTrue(nullArgs.isEmpty());
 
-        final List<String> zeroArgs = ExecuteProcess.splitArgs("  ");
+        final List<String> zeroArgs = ArgumentUtils.splitArgs("  ", ' ');
         assertNotNull(zeroArgs);
         assertTrue(zeroArgs.isEmpty());
 
-        final List<String> singleArg = ExecuteProcess.splitArgs("    hello   
");
+        final List<String> singleArg = ArgumentUtils.splitArgs("    hello   ", 
' ');
         assertEquals(1, singleArg.size());
         assertEquals("hello", singleArg.get(0));
 
-        final List<String> twoArg = ExecuteProcess.splitArgs("   hello    
good-bye   ");
+        final List<String> twoArg = ArgumentUtils.splitArgs("   hello    
good-bye   ", ' ');
         assertEquals(2, twoArg.size());
         assertEquals("hello", twoArg.get(0));
         assertEquals("good-bye", twoArg.get(1));
 
-        final List<String> singleQuotedArg = ExecuteProcess.splitArgs("  
\"hello\" ");
+        final List<String> singleQuotedArg = ArgumentUtils.splitArgs("  
\"hello\" ", ' ');
         assertEquals(1, singleQuotedArg.size());
         assertEquals("hello", singleQuotedArg.get(0));
 
-        final List<String> twoQuotedArg = ExecuteProcess.splitArgs("   hello 
\"good   bye\"");
+        final List<String> twoQuotedArg = ArgumentUtils.splitArgs("   hello 
\"good   bye\"", ' ');
         assertEquals(2, twoQuotedArg.size());
         assertEquals("hello", twoQuotedArg.get(0));
         assertEquals("good   bye", twoQuotedArg.get(1));

http://git-wip-us.apache.org/repos/asf/nifi/blob/9cefc4a5/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteStreamCommand.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteStreamCommand.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteStreamCommand.java
index 1cdc4a1..f01f404 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteStreamCommand.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestExecuteStreamCommand.java
@@ -16,6 +16,8 @@
  */
 package org.apache.nifi.processors.standard;
 
+import org.apache.nifi.processor.Processor;
+import org.apache.nifi.processors.standard.util.ArgumentUtils;
 import org.apache.nifi.util.MockFlowFile;
 import org.apache.nifi.util.TestRunner;
 import org.apache.nifi.util.TestRunners;
@@ -229,4 +231,24 @@ public class TestExecuteStreamCommand {
         assertTrue("NIFI_TEST_1 environment variable is missing", 
dynamicEnvironmentVariables.contains("NIFI_TEST_1=testvalue1"));
         assertTrue("NIFI_TEST_2 environment variable is missing", 
dynamicEnvironmentVariables.contains("NIFI_TEST_2=testvalue2"));
     }
+
+    @Test
+    public void testQuotedArguments() throws Exception {
+        List<String> args = ArgumentUtils.splitArgs("echo -n \"arg1 arg2 
arg3\"", ' ');
+        assertEquals(3, args.size());
+        args = ArgumentUtils.splitArgs("echo;-n;\"arg1 arg2 arg3\"", ';');
+        assertEquals(3, args.size());
+    }
+
+    @Test
+    public void testInvalidDelimiter() throws Exception {
+        final TestRunner controller = 
TestRunners.newTestRunner(ExecuteStreamCommand.class);
+        controller.setProperty(ExecuteStreamCommand.EXECUTION_COMMAND, "echo");
+        controller.assertValid();
+        controller.setProperty(ExecuteStreamCommand.ARG_DELIMITER, "foo");
+        controller.assertNotValid();
+        controller.setProperty(ExecuteStreamCommand.ARG_DELIMITER, "f");
+        controller.assertValid();
+    }
+
 }

Reply via email to