Add command line handler 23/120423/3
authoradheli.tavares <adheli.tavares@est.tech>
Fri, 9 Apr 2021 10:51:05 +0000 (11:51 +0100)
committeradheli.tavares <adheli.tavares@est.tech>
Tue, 13 Apr 2021 12:38:54 +0000 (13:38 +0100)
Added a Handler for command line classes to share common strucutures.

Issue-ID: POLICY-3128
Change-Id: I662911c467faf5c39b8db018bb1a564fba7587a6
Signed-off-by: adheli.tavares <adheli.tavares@est.tech>
utils/pom.xml
utils/src/main/java/org/onap/policy/common/utils/cmd/CommandLineArgumentsHandler.java [new file with mode: 0644]
utils/src/main/java/org/onap/policy/common/utils/cmd/CommandLineException.java [new file with mode: 0644]
utils/src/test/java/org/onap/policy/common/utils/cmd/TestCommandLineArguments.java [new file with mode: 0644]
utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java
utils/src/test/resources/cmdFiles/configuration.json [new file with mode: 0644]
utils/src/test/resources/cmdFiles/property.json [new file with mode: 0644]
utils/src/test/resources/version.txt [new file with mode: 0644]

index bd9df71..75173a2 100644 (file)
@@ -3,6 +3,7 @@
   ONAP Policy Engine - Common Modules
   ================================================================================
   Copyright (C) 2018-2021 AT&T Intellectual Property. All rights reserved.
+  Modifications Copyright (C) 2021 Nordix Foundation.
   ================================================================================
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
             <artifactId>assertj-core</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>commons-cli</groupId>
+            <artifactId>commons-cli</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/utils/src/main/java/org/onap/policy/common/utils/cmd/CommandLineArgumentsHandler.java b/utils/src/main/java/org/onap/policy/common/utils/cmd/CommandLineArgumentsHandler.java
new file mode 100644 (file)
index 0000000..37a9047
--- /dev/null
@@ -0,0 +1,273 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.cmd;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.lang3.StringUtils;
+import org.onap.policy.common.utils.resources.ResourceUtils;
+
+/**
+ * Class for command line common processing.
+ *
+ * @author Adheli Tavares (adheli.tavares@est.tech)
+ *
+ */
+public class CommandLineArgumentsHandler {
+    private static final String FILE_MESSAGE_PREAMBLE = " file \"";
+    private static final int HELP_LINE_LENGTH = 120;
+
+    private final Options options;
+
+    private final String helpClassName;
+    private final String component;
+
+    @Getter
+    @Setter
+    private String configurationFilePath = null;
+
+    @Getter
+    @Setter
+    private String propertyFilePath = null;
+
+    @Getter
+    private CommandLine commandLine = null;
+
+    /**
+     * Construct the options with default values for the CLI editor.
+     */
+    protected CommandLineArgumentsHandler(String helpClassName, String component) {
+        this.helpClassName = helpClassName;
+        this.component = component;
+        //@formatter:off
+        options = new Options();
+        options.addOption(Option.builder("h")
+                .longOpt("help")
+                .desc("outputs the usage of this command")
+                .required(false)
+                .type(Boolean.class)
+                .build());
+        options.addOption(Option.builder("v")
+                .longOpt("version")
+                .desc("outputs the version of " + this.component)
+                .required(false)
+                .type(Boolean.class)
+                .build());
+        options.addOption(Option.builder("c")
+                .longOpt("config-file")
+                .desc(String.format("the full path to the configuration file to use, "
+                        + "the configuration file must be a Json file containing the %s parameters", this.component))
+                .hasArg().argName("CONFIG_FILE")
+                .required(false)
+                .type(String.class)
+                .build());
+        //@formatter:on
+    }
+
+    /**
+     * Construct the options for the CLI editor with extra options.
+     */
+    public CommandLineArgumentsHandler(String helpClassName, String component, Option... customOptions) {
+        this(helpClassName, component);
+        if (customOptions != null && customOptions.length > 0) {
+            for (Option option : customOptions) {
+                options.addOption(option);
+            }
+        }
+    }
+
+    /**
+     * Construct the options with brand new options for the CLI editor.
+     */
+    public CommandLineArgumentsHandler(String helpClassName, String component, Options options) {
+        this.options = options;
+        this.helpClassName = helpClassName;
+        this.component = component;
+    }
+
+    /**
+     * Parse the command line options.
+     *
+     * @param args The command line arguments
+     * @return a string with a message for help and version, or null if there is no message
+     * @throws CommandLineException on command argument errors
+     */
+    public String parse(final String[] args) throws CommandLineException {
+        // Clear all our arguments
+        setConfigurationFilePath(null);
+        setPropertyFilePath(null);
+
+        try {
+            commandLine = new DefaultParser().parse(options, args);
+        } catch (final ParseException | NullPointerException e) {
+            throw new CommandLineException("invalid command line arguments specified", e);
+        }
+
+        // Arguments left over after Commons CLI does its stuff
+        final String[] remainingArgs = removeEmptyValues(commandLine.getArgs());
+
+        if (remainingArgs.length > 0) {
+            throw new CommandLineException("too many command line arguments specified: " + Arrays.toString(args));
+        }
+
+        if (commandLine.hasOption('h')) {
+            return help();
+        }
+
+        if (commandLine.hasOption('v')) {
+            return version();
+        }
+
+        if (commandLine.hasOption('c')) {
+            setConfigurationFilePath(commandLine.getOptionValue('c'));
+        }
+
+        if (commandLine.hasOption('p')) {
+            setPropertyFilePath(commandLine.getOptionValue('p'));
+        }
+
+        return null;
+    }
+
+    /**
+     * Validate the command line options.
+     *
+     * @throws CommandLineException on command argument validation errors
+     */
+    public void validate() throws CommandLineException {
+        validateReadableFile(this.component + " configuration", configurationFilePath);
+    }
+
+    /**
+     * Print version information for policy distribution.
+     *
+     * @return the version string
+     */
+    public String version() {
+        return ResourceUtils.getResourceAsString("version.txt");
+    }
+
+    /**
+     * Print help information for policy distribution.
+     *
+     * @return the help string
+     */
+    public String help() {
+        final HelpFormatter helpFormatter = new HelpFormatter();
+        final StringWriter stringWriter = new StringWriter();
+        final PrintWriter printWriter = new PrintWriter(stringWriter);
+        final String cmdLineSyntax = this.helpClassName + " [options...]";
+
+        helpFormatter.printHelp(printWriter, HELP_LINE_LENGTH, cmdLineSyntax, "options", options, 0, 0, "");
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Gets the full expanded configuration file path.
+     *
+     * @return the configuration file path
+     */
+    public String getFullConfigurationFilePath() {
+        return ResourceUtils.getFilePath4Resource(getConfigurationFilePath());
+    }
+
+    /**
+     * Check set configuration file path.
+     *
+     * @return true, if check set configuration file path
+     */
+    public boolean checkSetConfigurationFilePath() {
+        return StringUtils.isNotBlank(getConfigurationFilePath());
+    }
+
+    /**
+     * Gets the full expanded property file path.
+     *
+     * @return the property file path
+     */
+    public String getFullPropertyFilePath() {
+        return ResourceUtils.getFilePath4Resource(getPropertyFilePath());
+    }
+
+    /**
+     * Check set property file path.
+     *
+     * @return true, if check set property file path
+     */
+    public boolean checkSetPropertyFilePath() {
+        return StringUtils.isNotBlank(getPropertyFilePath());
+    }
+
+    /**
+     * Validate readable file.
+     *
+     * @param fileTag the file tag
+     * @param fileName the file name
+     * @throws CommandLineException on the file name passed as a parameter
+     */
+    protected void validateReadableFile(final String fileTag, final String fileName) throws CommandLineException {
+        if (StringUtils.isBlank(fileName)) {
+            throw new CommandLineException(fileTag + " file was not specified as an argument");
+        }
+
+        // The file name refers to a resource on the local file system
+        final URL fileUrl = ResourceUtils.getUrl4Resource(fileName);
+        if (fileUrl == null) {
+            throw new CommandLineException(fileTag + FILE_MESSAGE_PREAMBLE + fileName + "\" does not exist");
+        }
+
+        try {
+            Path path = Path.of(fileUrl.toURI());
+            if (!Files.isRegularFile(path)) {
+                throw new CommandLineException(fileTag + FILE_MESSAGE_PREAMBLE + fileName + "\" is not a normal file");
+            }
+            if (!Files.isReadable(path)) {
+                throw new CommandLineException(fileTag + FILE_MESSAGE_PREAMBLE + fileName + "\" is unreadable");
+            }
+        } catch (URISyntaxException e) {
+            throw new CommandLineException("Error parsing " + fileUrl.toString(), e);
+        }
+
+    }
+
+    /**
+     * Checks if args has any null or empty value after parsing.
+     *
+     * @param args remaining args from CLI parse.
+     */
+    private String[] removeEmptyValues(String[] args) {
+        return Arrays.stream(args).filter(StringUtils::isNotBlank).toArray(String[]::new);
+    }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/cmd/CommandLineException.java b/utils/src/main/java/org/onap/policy/common/utils/cmd/CommandLineException.java
new file mode 100644 (file)
index 0000000..95870f7
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.cmd;
+
+/**
+ * Exception used for CommandLineArguments class.
+ *
+ * @author Adheli Tavares (adheli.tavares@est.tech)
+ *
+ */
+public class CommandLineException extends Exception {
+
+    /**
+     * Generated serialVersionUID.
+     */
+    private static final long serialVersionUID = -1200607308084606425L;
+
+    /**
+     * Instantiates a new exception with a message.
+     *
+     * @param message the message
+     */
+    public CommandLineException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Instantiates a new exception with a message and a caused by exception.
+     *
+     * @param message the message
+     * @param exp the exception that caused this exception to be thrown
+     */
+    public CommandLineException(final String message, final Exception exp) {
+        super(message, exp);
+    }
+}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/cmd/TestCommandLineArguments.java b/utils/src/test/java/org/onap/policy/common/utils/cmd/TestCommandLineArguments.java
new file mode 100644 (file)
index 0000000..1501e76
--- /dev/null
@@ -0,0 +1,181 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.cmd;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.junit.Test;
+
+public class TestCommandLineArguments {
+    private static final String FAKE_HELP_CLASS = "org.onap.policy.HelpClass";
+    private static final String FAKE_COMPONENT = "fake policy cpm";
+    private static final String TEST_CONFIG_FILE = "cmdFiles/configuration.json";
+    private static final String TEST_PROPERTY_FILE = "cmdFiles/property.json";
+    private static final String ERR_MSG_INVALID_ARGS = "invalid command line arguments specified";
+    private static final String ERR_MSG_POLICY_CONFIG_FILE =
+            "fake policy cpm configuration file was not specified as an argument";
+
+    CommandLineArgumentsHandler testCmd = new CommandLineArgumentsHandler(FAKE_HELP_CLASS, FAKE_COMPONENT);
+
+    @Test
+    public void testVersion() throws CommandLineException {
+        String[] version = {"-v"};
+        assertThat(testCmd.parse(version)).startsWith("ONAP Version test.");
+    }
+
+    @Test
+    public void testHelp() throws CommandLineException {
+        String[] help = {"-h"};
+        assertThat(testCmd.parse(help)).startsWith("usage: org.onap.policy.HelpClass [options...]");
+    }
+
+    @Test
+    public void testParse() throws CommandLineException {
+        String[] args = {"-c", TEST_CONFIG_FILE};
+        testCmd.parse(args);
+
+        assertTrue(testCmd.checkSetConfigurationFilePath());
+        assertThat(testCmd.getFullConfigurationFilePath()).contains(TEST_CONFIG_FILE);
+    }
+
+    @Test
+    public void testParse_ShouldThrowExceptionWithInvalidArguments() {
+        String[] invalidArgs = {"-a"};
+        assertThatThrownBy(() -> testCmd.parse(invalidArgs)).hasMessage(ERR_MSG_INVALID_ARGS)
+                .hasRootCauseMessage("Unrecognized option: -a");
+    }
+
+    @Test
+    public void testParse_ShouldThrowExceptionWithExtraArguments() {
+        String[] remainingArgs = {"-c", TEST_CONFIG_FILE, "extraArgs"};
+        String expectedErrorMsg =
+                "too many command line arguments specified: [-c, cmdFiles/configuration.json, extraArgs]";
+        assertThatThrownBy(() -> testCmd.parse(remainingArgs)).hasMessage(expectedErrorMsg);
+    }
+
+    @Test
+    public void testParse_ShouldThrowExceptionWhenFileNameNull() {
+        String[] nullArgs = {"-c", null};
+        assertThatThrownBy(() -> testCmd.parse(nullArgs)).hasMessage(ERR_MSG_INVALID_ARGS).hasRootCauseMessage(null);
+    }
+
+    @Test
+    public void testValidate() throws CommandLineException {
+        String[] validConfigArgs = {"-c", TEST_CONFIG_FILE};
+        testCmd.parse(validConfigArgs);
+        assertThatCode(() -> testCmd.validate()).doesNotThrowAnyException();
+    }
+
+    @Test
+    public void testValidate_ShouldThrowExceptionWhenConfigFileNotPresent() throws CommandLineException {
+        String[] versionArgs = {"-v"};
+        testCmd.parse(versionArgs);
+        assertValidate(versionArgs, ERR_MSG_POLICY_CONFIG_FILE);
+    }
+
+    @Test
+    public void testValidate_ShouldThrowExceptionWhenFileNameEmpty() {
+        String[] argsOnlyKeyNoValue = {"-c", ""};
+        assertValidate(argsOnlyKeyNoValue, ERR_MSG_POLICY_CONFIG_FILE);
+        assertFalse(testCmd.checkSetConfigurationFilePath());
+    }
+
+    @Test
+    public void testValidate_ShouldThrowExceptionWhenFileNameEmptySpace() {
+        String[] argsOnlyKeyNoValue = {"-c", " "};
+        assertValidate(argsOnlyKeyNoValue, ERR_MSG_POLICY_CONFIG_FILE);
+        assertFalse(testCmd.checkSetConfigurationFilePath());
+    }
+
+    @Test
+    public void testValidate_ShouldThrowExceptionWhenFileNameDoesNotExist() {
+        String[] fileNameNotExistentArgs = {"-c", "someFileName.json"};
+        assertValidate(fileNameNotExistentArgs,
+                "fake policy cpm configuration file \"someFileName.json\" does not exist");
+    }
+
+    @Test
+    public void testValidate_ShouldThrowExceptionWhenFileNameIsNotFile() {
+        String[] folderAsFileNameArgs = {"-c", "src/test/resources"};
+        assertValidate(folderAsFileNameArgs,
+                "fake policy cpm configuration file \"src/test/resources\" is not a normal file");
+    }
+
+    @Test
+    public void testAddExtraOptions() throws CommandLineException {
+        Option extra = Option.builder("p").longOpt("property-file")
+                .desc("the full path to the topic property file to use, the property file contains the "
+                        + FAKE_COMPONENT + " properties")
+                .hasArg().argName("PROP_FILE").required(false).type(String.class).build();
+
+        CommandLineArgumentsHandler testCmdExtraOpt =
+                new CommandLineArgumentsHandler(FAKE_HELP_CLASS, FAKE_COMPONENT, extra);
+
+        String[] args = {"-p", TEST_PROPERTY_FILE};
+        testCmdExtraOpt.parse(args);
+
+        assertTrue(testCmdExtraOpt.checkSetPropertyFilePath());
+        assertThat(testCmdExtraOpt.getFullPropertyFilePath()).contains(TEST_PROPERTY_FILE);
+
+        String[] argsNoProperty = {"-p", ""};
+        testCmdExtraOpt.parse(argsNoProperty);
+
+        assertFalse(testCmdExtraOpt.checkSetPropertyFilePath());
+    }
+
+    @Test
+    public void testNewOptions() throws CommandLineException {
+        Options newOptions = new Options();
+        newOptions.addOption(
+                Option.builder("a").longOpt("fake-option").desc("the fake property to check command line parse")
+                        .hasArg().argName("FAKE_OPT").required(false).type(String.class).build());
+
+        CommandLineArgumentsHandler testCmdExtraOpt =
+                new CommandLineArgumentsHandler(FAKE_HELP_CLASS, FAKE_COMPONENT, newOptions);
+
+        String[] args = {"-a", "aaaa"};
+        testCmdExtraOpt.parse(args);
+
+        assertTrue(testCmdExtraOpt.getCommandLine().hasOption("a"));
+
+        // should raise exception as -c is not present on options;
+        // default options should've been replaced by constructor parameter.
+        String[] argsError = {"-c", "aaaa.json"};
+        assertThatThrownBy(() -> testCmdExtraOpt.parse(argsError)).hasMessage(ERR_MSG_INVALID_ARGS)
+                .hasRootCauseMessage("Unrecognized option: -c");
+    }
+
+    private void assertValidate(String[] testArgs, String expectedErrorMsg) {
+        try {
+            testCmd.parse(testArgs);
+        } catch (CommandLineException e) {
+            fail(e.getMessage());
+        }
+        assertThatThrownBy(() -> testCmd.validate()).hasMessage(expectedErrorMsg);
+    }
+}
index 2e12218..6a2fe1a 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2018 Ericsson. All rights reserved.
  *  Modifications Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
- *  Modifications Copyright (C) 2020 Nordix Foundation.
+ *  Modifications Copyright (C) 2020-2021 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -264,7 +264,7 @@ public class ResourceUtilsTest {
 
         theString = ResourceUtils.getResourceAsString("");
 
-        assertEquals("logback-test.xml\nMETA-INF\norg\ntestdir\n", theString);
+        assertEquals("cmdFiles\nlogback-test.xml\nMETA-INF\norg\ntestdir\nversion.txt\n", theString);
 
     }
 
diff --git a/utils/src/test/resources/cmdFiles/configuration.json b/utils/src/test/resources/cmdFiles/configuration.json
new file mode 100644 (file)
index 0000000..64cd100
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "propertyName" : "test",
+    "propertyType" : "string"
+}
diff --git a/utils/src/test/resources/cmdFiles/property.json b/utils/src/test/resources/cmdFiles/property.json
new file mode 100644 (file)
index 0000000..be63ece
--- /dev/null
@@ -0,0 +1,3 @@
+{
+    "configName" : "test"
+}
diff --git a/utils/src/test/resources/version.txt b/utils/src/test/resources/version.txt
new file mode 100644 (file)
index 0000000..9970c7b
--- /dev/null
@@ -0,0 +1 @@
+ONAP Version test.