mirror of
https://github.com/apache/nifi.git
synced 2025-02-16 06:55:28 +00:00
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)
This commit is contained in:
parent
d39848f06e
commit
9cefc4a5a5
@ -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 {
|
public static class DirectoryExistsValidator implements Validator {
|
||||||
|
|
||||||
private final boolean allowEL;
|
private final boolean allowEL;
|
||||||
|
@ -48,6 +48,7 @@ import org.apache.nifi.annotation.documentation.Tags;
|
|||||||
import org.apache.nifi.annotation.lifecycle.OnScheduled;
|
import org.apache.nifi.annotation.lifecycle.OnScheduled;
|
||||||
import org.apache.nifi.annotation.lifecycle.OnUnscheduled;
|
import org.apache.nifi.annotation.lifecycle.OnUnscheduled;
|
||||||
import org.apache.nifi.components.PropertyDescriptor;
|
import org.apache.nifi.components.PropertyDescriptor;
|
||||||
|
import org.apache.nifi.components.Validator;
|
||||||
import org.apache.nifi.flowfile.FlowFile;
|
import org.apache.nifi.flowfile.FlowFile;
|
||||||
import org.apache.nifi.logging.ProcessorLog;
|
import org.apache.nifi.logging.ProcessorLog;
|
||||||
import org.apache.nifi.processor.AbstractProcessor;
|
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.exception.ProcessException;
|
||||||
import org.apache.nifi.processor.io.OutputStreamCallback;
|
import org.apache.nifi.processor.io.OutputStreamCallback;
|
||||||
import org.apache.nifi.processor.util.StandardValidators;
|
import org.apache.nifi.processor.util.StandardValidators;
|
||||||
|
import org.apache.nifi.processors.standard.util.ArgumentUtils;
|
||||||
|
|
||||||
@Tags({"command", "process", "source", "external", "invoke", "script"})
|
@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 "
|
@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)
|
.addValidator(StandardValidators.BOOLEAN_VALIDATOR)
|
||||||
.build();
|
.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()
|
public static final Relationship REL_SUCCESS = new Relationship.Builder()
|
||||||
.name("success")
|
.name("success")
|
||||||
.description("All created FlowFiles are routed to this relationship")
|
.description("All created FlowFiles are routed to this relationship")
|
||||||
@ -132,6 +146,7 @@ public class ExecuteProcess extends AbstractProcessor {
|
|||||||
properties.add(COMMAND_ARGUMENTS);
|
properties.add(COMMAND_ARGUMENTS);
|
||||||
properties.add(BATCH_DURATION);
|
properties.add(BATCH_DURATION);
|
||||||
properties.add(REDIRECT_ERROR_STREAM);
|
properties.add(REDIRECT_ERROR_STREAM);
|
||||||
|
properties.add(ARG_DELIMITER);
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,51 +160,7 @@ public class ExecuteProcess extends AbstractProcessor {
|
|||||||
.build();
|
.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
|
@OnScheduled
|
||||||
public void setupExecutor(final ProcessContext context) {
|
public void setupExecutor(final ProcessContext context) {
|
||||||
@ -293,7 +264,8 @@ public class ExecuteProcess extends AbstractProcessor {
|
|||||||
|
|
||||||
protected List<String> createCommandStrings(final ProcessContext context) {
|
protected List<String> createCommandStrings(final ProcessContext context) {
|
||||||
final String command = context.getProperty(COMMAND).getValue();
|
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);
|
final List<String> commandStrings = new ArrayList<>(args.size() + 1);
|
||||||
commandStrings.add(command);
|
commandStrings.add(command);
|
||||||
|
@ -56,6 +56,7 @@ import org.apache.nifi.processor.exception.ProcessException;
|
|||||||
import org.apache.nifi.processor.io.InputStreamCallback;
|
import org.apache.nifi.processor.io.InputStreamCallback;
|
||||||
import org.apache.nifi.processor.io.OutputStreamCallback;
|
import org.apache.nifi.processor.io.OutputStreamCallback;
|
||||||
import org.apache.nifi.processor.util.StandardValidators;
|
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.BufferedInputStream;
|
||||||
import org.apache.nifi.stream.io.BufferedOutputStream;
|
import org.apache.nifi.stream.io.BufferedOutputStream;
|
||||||
import org.apache.nifi.stream.io.StreamUtils;
|
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) {
|
public ValidationResult validate(String subject, String input, ValidationContext context) {
|
||||||
ValidationResult result = new ValidationResult.Builder()
|
ValidationResult result = new ValidationResult.Builder()
|
||||||
.subject(subject).valid(true).input(input).build();
|
.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) {
|
for (String arg : args) {
|
||||||
ValidationResult valResult = ATTRIBUTE_EXPRESSION_LANGUAGE_VALIDATOR.validate(subject, arg, context);
|
ValidationResult valResult = ATTRIBUTE_EXPRESSION_LANGUAGE_VALIDATOR.validate(subject, arg, context);
|
||||||
if (!valResult.isValid()) {
|
if (!valResult.isValid()) {
|
||||||
@ -191,6 +192,17 @@ public class ExecuteStreamCommand extends AbstractProcessor {
|
|||||||
.defaultValue("false")
|
.defaultValue("false")
|
||||||
.build();
|
.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;
|
private static final List<PropertyDescriptor> PROPERTIES;
|
||||||
|
|
||||||
@ -200,6 +212,7 @@ public class ExecuteStreamCommand extends AbstractProcessor {
|
|||||||
props.add(EXECUTION_COMMAND);
|
props.add(EXECUTION_COMMAND);
|
||||||
props.add(IGNORE_STDIN);
|
props.add(IGNORE_STDIN);
|
||||||
props.add(WORKING_DIR);
|
props.add(WORKING_DIR);
|
||||||
|
props.add(ARG_DELIMITER);
|
||||||
PROPERTIES = Collections.unmodifiableList(props);
|
PROPERTIES = Collections.unmodifiableList(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +256,7 @@ public class ExecuteStreamCommand extends AbstractProcessor {
|
|||||||
final String commandArguments = context.getProperty(EXECUTION_ARGUMENTS).getValue();
|
final String commandArguments = context.getProperty(EXECUTION_ARGUMENTS).getValue();
|
||||||
final boolean ignoreStdin = Boolean.parseBoolean(context.getProperty(IGNORE_STDIN).getValue());
|
final boolean ignoreStdin = Boolean.parseBoolean(context.getProperty(IGNORE_STDIN).getValue());
|
||||||
if (!StringUtils.isBlank(commandArguments)) {
|
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());
|
args.add(context.newPropertyValue(arg).evaluateAttributeExpressions(inputFlowFile).getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ import java.io.File;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.nifi.processor.ProcessContext;
|
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.MockFlowFile;
|
||||||
import org.apache.nifi.util.TestRunner;
|
import org.apache.nifi.util.TestRunner;
|
||||||
import org.apache.nifi.util.TestRunners;
|
import org.apache.nifi.util.TestRunners;
|
||||||
@ -34,28 +35,28 @@ public class TestExecuteProcess {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSplitArgs() {
|
public void testSplitArgs() {
|
||||||
final List<String> nullArgs = ExecuteProcess.splitArgs(null);
|
final List<String> nullArgs = ArgumentUtils.splitArgs(null, ' ');
|
||||||
assertNotNull(nullArgs);
|
assertNotNull(nullArgs);
|
||||||
assertTrue(nullArgs.isEmpty());
|
assertTrue(nullArgs.isEmpty());
|
||||||
|
|
||||||
final List<String> zeroArgs = ExecuteProcess.splitArgs(" ");
|
final List<String> zeroArgs = ArgumentUtils.splitArgs(" ", ' ');
|
||||||
assertNotNull(zeroArgs);
|
assertNotNull(zeroArgs);
|
||||||
assertTrue(zeroArgs.isEmpty());
|
assertTrue(zeroArgs.isEmpty());
|
||||||
|
|
||||||
final List<String> singleArg = ExecuteProcess.splitArgs(" hello ");
|
final List<String> singleArg = ArgumentUtils.splitArgs(" hello ", ' ');
|
||||||
assertEquals(1, singleArg.size());
|
assertEquals(1, singleArg.size());
|
||||||
assertEquals("hello", singleArg.get(0));
|
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(2, twoArg.size());
|
||||||
assertEquals("hello", twoArg.get(0));
|
assertEquals("hello", twoArg.get(0));
|
||||||
assertEquals("good-bye", twoArg.get(1));
|
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(1, singleQuotedArg.size());
|
||||||
assertEquals("hello", singleQuotedArg.get(0));
|
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(2, twoQuotedArg.size());
|
||||||
assertEquals("hello", twoQuotedArg.get(0));
|
assertEquals("hello", twoQuotedArg.get(0));
|
||||||
assertEquals("good bye", twoQuotedArg.get(1));
|
assertEquals("good bye", twoQuotedArg.get(1));
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.nifi.processors.standard;
|
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.MockFlowFile;
|
||||||
import org.apache.nifi.util.TestRunner;
|
import org.apache.nifi.util.TestRunner;
|
||||||
import org.apache.nifi.util.TestRunners;
|
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_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"));
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user