package org.ovirt.engine.core.config; import java.util.HashMap; import org.ovirt.engine.core.config.validation.ConfigActionType; /** * The <code>EngineConfigCLIParser</code> class represents a parser for the EngineConfig tool. It parses the given * arguments into meaningful keys and values. The parser assumes the '=' char can only be used in the format k=v, and * not as a char that is actually part of a key/value. */ public class EngineConfigCLIParser { private HashMap<String, String> argsMap = new HashMap<>(); private EngineConfigMap engineConfigMap = new EngineConfigMap(); public EngineConfigCLIParser() { } /** * Parses the given arguments, identifies the desired action, and the different keys and values. * * @param args * The arguments that need to be parsed. * @throws IllegalArgumentException * If there are no arguments, if a legal action was not identified, or if second argument has '=' char, * but action is not 'set'. */ public void parse(String[] args) { validateNonEmpty(args); parseAction(args); parseArguments(args); } private void validateNonEmpty(String[] args) { if (args.length == 0) { throw new IllegalArgumentException("Error: at least 1 argument needed for configuration utility to run."); } } /** * Parses the argument in the currentIndex of args, into a key and its value. Can also parse version, properties * file, config file. If the argument in the currentIndex does not have a value, assumes the value is in the * following argument. The argsMap helps to parse the arguments which are eventually set into the engineConfigMap. * * @return whether or not the next argument is to be skipped */ private boolean parseKeyValue(String[] args, int currentIndex) { boolean fShouldSkip = false; int delimiterIndex = args[currentIndex].indexOf("="); String key = getStringBeforeEqualChar(args[currentIndex], delimiterIndex); // includes '-' String value = getStringAfterEqualChar(args[currentIndex], delimiterIndex); if (OptionKey.OPTION_ONLY_RELOADABLE.getOptionalStrings().contains(key)) { setOnlyReloadableOption(key); } else if (!key.isEmpty()) { if (!value.isEmpty()) { argsMap.put(key, value); } else { if (args.length > currentIndex + 1) { // To make sure there is another argument argsMap.put(key, args[currentIndex + 1]); fShouldSkip = true; } } } return fShouldSkip; } private void setOnlyReloadableOption(String key) { argsMap.put(key, Boolean.toString(true)); } /** * Parses the second argument in case it does not start with a '-'. There are two valid scenarios for this. First, * when the argument has been given as the key in the 'get' action, in the format: "-g key". Second, in the 'set' * action. For set action, we require key=value, or parsing will fail. */ private void parseSecondArgWithoutDash(String arg, boolean passFileExists) { int delimiterIndex = arg.indexOf("="); if (isSetOrMergeAction() && delimiterIndex == -1 && !passFileExists) { throw new IllegalArgumentException("Argument for set/merge action must be in format of key=value."); } String key = getStringBeforeEqualChar(arg, delimiterIndex); String value = getStringAfterEqualChar(arg, delimiterIndex); if (!key.isEmpty()) { if (!value.isEmpty()) { parseSecondArgWithKeyValue(arg, key, value); } else if (isSetOrMergeAction() && getKey() == null) { engineConfigMap.setKey(key); engineConfigMap.setValue(value); } else if ((getConfigAction().equals(ConfigActionType.ACTION_GET) || getConfigAction().equals(ConfigActionType.ACTION_HELP)) && getKey() == null) { engineConfigMap.setKey(arg); // sets the key in 'get' action with format: "-g key" } } } /** * Parses second argument with a key and a value. Is only valid in the 'set' action. */ private void parseSecondArgWithKeyValue(String arg, String key, String value) { if (isSetOrMergeAction()) { engineConfigMap.setKey(key); engineConfigMap.setValue(value); } else { throw new IllegalArgumentException("Illegal second argument: " + arg + "."); } } private boolean isSetOrMergeAction() { return getConfigAction().equals(ConfigActionType.ACTION_SET) || getConfigAction().equals(ConfigActionType.ACTION_MERGE); } /** * Parses all arguments except for the first argument which is assumed to be the action. The argsMap member helps to * parse the arguments which are eventually set into the engineConfigMap. * * @param args * The arguments that needs to be parsed. */ private void parseArguments(String[] args) { boolean fShouldSkip = true; // So the first arg which is the action will be skipped boolean passFileExists = false; for (String arg : args) { if (arg.startsWith("--admin-pass-file")) { passFileExists = true; break; } } for (int currentIndex = 0; currentIndex < args.length; currentIndex++) { if (fShouldSkip) { fShouldSkip = false; continue; } if (args[currentIndex].startsWith("-")) { fShouldSkip = parseKeyValue(args, currentIndex); } else if (currentIndex == 1) { parseSecondArgWithoutDash(args[currentIndex], passFileExists); } } fillEngineConfigMap(); } /** * Parses the action from the given arguments. * * @throws IllegalArgumentException * If the first argument is not a legal action */ private void parseAction(String[] args) { validateArgStartsWithDash(args[0]); int delimiterIndex = args[0].indexOf("="); String action = getStringBeforeEqualChar(args[0], delimiterIndex); String key = getStringAfterEqualChar(args[0], delimiterIndex); if (!action.isEmpty()) { if (!key.isEmpty()) { handleActionWithKey(action, key); } else { handleActionWithoutKey(action); } } else { throw new IllegalArgumentException("Action verb must come first, and '" + args[0] + "' is not an action.\nPlease tell me what to do: list? get? set? get-all?"); } } /** * Returns the first part of the given arg, until the delimiterIndex, excluding. Did not use split() because of * problematic handling of empty parts. */ private String getStringAfterEqualChar(String arg, int delimiterIndex) { String value; if (delimiterIndex > 0) { value = arg.substring(delimiterIndex + 1); } else { value = ""; } return value; } /** * Returns the second part of the given arg, starting from the delimiterIndex, excluding. Did not use split() * because of problematic handling of empty parts. */ private String getStringBeforeEqualChar(String arg, int delimiterIndex) { String key; if (delimiterIndex > 0) { key = arg.substring(0, delimiterIndex); } else { key = arg; } return key; } /** * Handles an action without a key. */ private void handleActionWithoutKey(String action) { engineConfigMap.setConfigAction(ConfigActionType.getActionType(action)); if (getConfigAction() == null) { throw new IllegalArgumentException("Action verb must come first, and '" + action + "' is not an action.\nPlease tell me what to do: list? get? set? get-all?"); } } /** * Handles an action with a key. The only valid action with a key in the first argument is the 'get' action, in the * format: "--get=key". */ private void handleActionWithKey(String action, String key) { engineConfigMap.setConfigAction(ConfigActionType.getActionType(action)); if (action.equals("--get") || action.equals("--help")) { engineConfigMap.setKey(key); } else { throw new IllegalArgumentException("Action verb must come first, and '" + action + '=' + key + "' is not an action.\nPlease tell me what to do: list? get? set? get-all?"); } } /** * Makes sure the first argument starts with a '-', since all actions do. */ private void validateArgStartsWithDash(String arg) { if (!arg.startsWith("-")) { throw new IllegalArgumentException("First argument must be an action, and start with '-' or '--'"); } } private String parseOptionKey(OptionKey optionKey) { for (String configKeyName : optionKey.getOptionalStrings()) { if (argsMap.containsKey(configKeyName)) { return argsMap.get(configKeyName); } } return null; } private void fillEngineConfigMap() { engineConfigMap.setVersion(parseOptionKey(OptionKey.OPTION_VERSION)); engineConfigMap.setAlternateConfigFile(parseOptionKey(OptionKey.OPTION_CONFIG)); engineConfigMap.setAlternatePropertiesFile(parseOptionKey(OptionKey.OPTION_PROPERTIES)); engineConfigMap.setUser(parseOptionKey(OptionKey.OPTION_USER)); engineConfigMap.setAdminPassFile(parseOptionKey(OptionKey.OPTION_ADMINPASSFILE)); engineConfigMap.setOnlyReloadable(parseOptionKey(OptionKey.OPTION_ONLY_RELOADABLE)); engineConfigMap.setLogFile(parseOptionKey(OptionKey.OPTION_LOG_FILE)); engineConfigMap.setLogLevel(parseOptionKey(OptionKey.OPTION_LOG_LEVEL)); } public EngineConfigMap getEngineConfigMap() { return engineConfigMap; } public String getUser() { return engineConfigMap.getUser(); } public String getAdminPassFile() { return engineConfigMap.getAdminPassFile(); } public String getVersion() { return engineConfigMap.getVersion(); } public ConfigActionType getConfigAction() { return engineConfigMap.getConfigAction(); } public String getKey() { return engineConfigMap.getKey(); } public String getValue() { return engineConfigMap.getValue(); } public String getAlternateConfigFile() { return engineConfigMap.getAlternateConfigFile(); } public String getAlternatePropertiesFile() { return engineConfigMap.getAlternatePropertiesFile(); } public String engineConfigMapToString() { return engineConfigMap.toString(); } public boolean isOnlyReloadable() { return engineConfigMap.isOnlyReloadable(); } }