http://git-wip-us.apache.org/repos/asf/sqoop/blob/8362c73c/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigDisplayer.java ---------------------------------------------------------------------- diff --git a/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigDisplayer.java b/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigDisplayer.java new file mode 100644 index 0000000..dea271a --- /dev/null +++ b/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigDisplayer.java @@ -0,0 +1,258 @@ +/** + * 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.sqoop.shell.utils; + +import static org.apache.sqoop.shell.ShellEnvironment.print; +import static org.apache.sqoop.shell.ShellEnvironment.println; +import static org.apache.sqoop.shell.ShellEnvironment.resourceString; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; + +import org.apache.commons.lang.StringUtils; +import org.apache.sqoop.common.Direction; +import org.apache.sqoop.model.MAccountableEntity; +import org.apache.sqoop.model.MBooleanInput; +import org.apache.sqoop.model.MConfig; +import org.apache.sqoop.model.MConnector; +import org.apache.sqoop.model.MDriverConfig; +import org.apache.sqoop.model.MEnumInput; +import org.apache.sqoop.model.MInput; +import org.apache.sqoop.model.MInputType; +import org.apache.sqoop.model.MIntegerInput; +import org.apache.sqoop.model.MJob; +import org.apache.sqoop.model.MLink; +import org.apache.sqoop.model.MMapInput; +import org.apache.sqoop.model.MStringInput; +import org.apache.sqoop.shell.core.Constants; +import org.apache.sqoop.validation.Message; +import org.apache.sqoop.validation.Status; + +/** + * Convenience static methods for displaying config related information + */ +public final class ConfigDisplayer { + + public static void displayDriverConfigDetails(MDriverConfig driverConfig, ResourceBundle bundle) { + displayConfig(driverConfig.getConfigs(), + resourceString(Constants.RES_CONFIG_DISPLAYER_JOB), bundle); + } + + public static void displayConnectorConfigDetails(MConnector connector, ResourceBundle bundle) { + displayConfig( + connector.getLinkConfig().getConfigs(), + resourceString(Constants.RES_CONFIG_DISPLAYER_LINK), + bundle); + + displayConfig( + connector.getConfig(Direction.FROM).getConfigs(), + Direction.FROM.toString() + " " + resourceString(Constants.RES_CONFIG_DISPLAYER_JOB), + bundle); + + displayConfig( + connector.getConfig(Direction.TO).getConfigs(), + Direction.TO.toString() + " " + resourceString(Constants.RES_CONFIG_DISPLAYER_JOB), + bundle); + } + + private static void displayConfig(List<MConfig> configs, + String type, + ResourceBundle bundle) { + Iterator<MConfig> iterator = configs.iterator(); + int findx = 1; + while (iterator.hasNext()) { + print(" "); + print(type); + print(" %s ", resourceString(Constants.RES_CONFIG_DISPLAYER_CONFIG)); + print(findx++); + println(":"); + + MConfig config = iterator.next(); + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_NAME)); + println(config.getName()); + + // Label + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_LABEL)); + println(bundle.getString(config.getLabelKey())); + + // Help text + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_HELP)); + println(bundle.getString(config.getHelpKey())); + + List<MInput<?>> inputs = config.getInputs(); + Iterator<MInput<?>> iiter = inputs.iterator(); + int iindx = 1; + while (iiter.hasNext()) { + print(" %s ", resourceString(Constants.RES_CONFIG_DISPLAYER_INPUT)); + print(iindx++); + println(":"); + + MInput<?> input = iiter.next(); + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_NAME)); + println(input.getName()); + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_LABEL)); + println(bundle.getString(input.getLabelKey())); + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_HELP)); + println(bundle.getString(input.getHelpKey())); + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_TYPE)); + println(input.getType()); + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_SENSITIVE)); + println(input.isSensitive()); + if (input.getType() == MInputType.STRING) { + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_SIZE)); + println(((MStringInput)input).getMaxLength()); + } else if(input.getType() == MInputType.ENUM) { + print(" %s: ", resourceString(Constants.RES_CONFIG_DISPLAYER_POSSIBLE_VALUES)); + println(StringUtils.join(((MEnumInput)input).getValues(), ",")); + } + } + } + } + + public static void displayConfig(List<MConfig> configs, ResourceBundle bundle) { + for(MConfig config : configs) { + displayConfig(config, bundle); + } + } + + /** + * Method prints the warning message of ACCEPTABLE status + * @param entity - link or job instance + */ + public static void displayConfigWarning(MAccountableEntity entity) { + List<MConfig> configList = new ArrayList<MConfig>(); + boolean showMessage = true; + if (entity instanceof MLink) { + MLink link = (MLink) entity; + configList.addAll(link.getConnectorLinkConfig().getConfigs()); + } else if(entity instanceof MJob) { + MJob job = (MJob) entity; + configList.addAll(job.getJobConfig(Direction.FROM).getConfigs()); + configList.addAll(job.getDriverConfig().getConfigs()); + configList.addAll(job.getJobConfig(Direction.TO).getConfigs()); + } + for(MConfig config : configList) { + if(config.getValidationStatus() == Status.ACCEPTABLE) { + if(showMessage) { + print("\n@|yellow %s|@\n", resourceString(Constants.RES_CONFIG_DISPLAYER_FORM_WARNING)); + showMessage = false; + } + for(Message message : config.getValidationMessages()) { + ConfigFiller.warningMessage(message.getMessage()); + } + } + } + } + + private static void displayConfig(MConfig config, ResourceBundle bundle) { + print(" "); + println(bundle.getString(config.getLabelKey())); + + for (MInput<?> input : config.getInputs()) { + print(" "); + print(bundle.getString(input.getLabelKey())); + print(": "); + if(!input.isEmpty()) { + if (input.isSensitive()) { + print("(%s)", resourceString(Constants.RES_CONFIG_DISPLAYER_INPUT_SENSITIVE)); + } else { + // Based on the input type, let's perconfig specific load + switch (input.getType()) { + case STRING: + displayInputString((MStringInput) input); + break; + case INTEGER: + displayInputInteger((MIntegerInput) input); + break; + case BOOLEAN: + displayInputBoolean((MBooleanInput) input); + break; + case MAP: + displayInputMap((MMapInput) input); + break; + case ENUM: + displayInputEnum((MEnumInput) input); + break; + default: + print("\n%s " + input.getType(), resourceString(Constants.RES_CONFIG_DISPLAYER_UNSUPPORTED_DATATYPE)); + return; + } + } + } + println(""); + } + } + + /** + * Display content of String input. + * + * @param input String input + */ + private static void displayInputString(MStringInput input) { + print(input.getValue()); + } + + /** + * Display content of Integer input. + * + * @param input Integer input + */ + private static void displayInputInteger(MIntegerInput input) { + print(input.getValue()); + } + + /** + * Display content of Boolean input. + * + * @param input Boolean input + */ + private static void displayInputBoolean(MBooleanInput input) { + print(input.getValue()); + } + + /** + * Display content of Map input + * + * @param input Map input + */ + private static void displayInputMap(MMapInput input) { + for(Map.Entry<String, String> entry : input.getValue().entrySet()) { + println(); + print(" "); + print(entry.getKey()); + print(" = "); + print(entry.getValue()); + } + } + + /** + * Display content of Enum input + * + * @param input Enum input + */ + private static void displayInputEnum(MEnumInput input) { + print(input.getValue()); + } + + private ConfigDisplayer() { + // Do not instantiate + } +}
http://git-wip-us.apache.org/repos/asf/sqoop/blob/8362c73c/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigFiller.java ---------------------------------------------------------------------- diff --git a/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigFiller.java b/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigFiller.java new file mode 100644 index 0000000..c61d33b --- /dev/null +++ b/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigFiller.java @@ -0,0 +1,911 @@ +/** + * 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.sqoop.shell.utils; + +import jline.ConsoleReader; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.lang.StringUtils; +import org.apache.sqoop.common.Direction; +import org.apache.sqoop.model.MBooleanInput; +import org.apache.sqoop.model.MLink; +import org.apache.sqoop.model.MEnumInput; +import org.apache.sqoop.model.MConfig; +import org.apache.sqoop.model.MInput; +import org.apache.sqoop.model.MIntegerInput; +import org.apache.sqoop.model.MMapInput; +import org.apache.sqoop.model.MJob; +import org.apache.sqoop.model.MNamedElement; +import org.apache.sqoop.model.MStringInput; +import org.apache.sqoop.model.MValidatedElement; +import org.apache.sqoop.validation.Message; +import org.apache.sqoop.validation.Status; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ResourceBundle; + +import static org.apache.sqoop.shell.ShellEnvironment.*; + +/** + * Convenient methods for retrieving user input and CLI options. + */ +public final class ConfigFiller { + + /** + * Internal input that will be reused for loading names for link and + * job objects. + */ + private static MStringInput nameInput = new MStringInput("object-name", false, (short)25); + + /** + * Fill job object based on CLI options. + * + * @param line Associated console reader object + * @param job Job that user is suppose to fill in + * @return True if we filled all inputs, false if user has stopped processing + * @throws IOException + */ + public static boolean fillJob(CommandLine line, + MJob job) + throws IOException { + + job.setName(line.getOptionValue("name")); + return fillJobConfig(line, + job.getJobConfig(Direction.FROM).getConfigs(), + job.getJobConfig(Direction.TO).getConfigs(), + job.getDriverConfig().getConfigs()); + } + + /** + * Fill link object based on CLI options. + * + * @param line Associated command line options + * @param link Link that user is suppose to fill in + * @return True if we filled all inputs, false if user has stopped processing + * @throws IOException + */ + public static boolean fillLink(CommandLine line, MLink link) throws IOException { + + link.setName(line.getOptionValue("name")); + return fillLinkConfig(line, link.getConnectorLinkConfig().getConfigs()); + } + + /** + * Load CLI options for link configs + * + * @param line CLI options container + * @param linkConfig from config to read or edit + * @return + * @throws IOException + */ + public static boolean fillLinkConfig(CommandLine line, + List<MConfig> linkConfig) + throws IOException { + return fillConfigs("link", linkConfig, line); + } + + /** + * Load CLI options for job configs + * + * @param line CLI options container + * @param fromConfig from config to read or edit + * @param toConfig to config to read or edit + * @param driverConfig driver config to read or edit + * @return + * @throws IOException + */ + public static boolean fillJobConfig(CommandLine line, + List<MConfig> fromConfig, + List<MConfig> toConfig, + List<MConfig> driverConfig) + throws IOException { + return fillConfigs("from", fromConfig, line) + && fillConfigs("to", toConfig, line) + && fillConfigs("driver", driverConfig, line); + } + + /** + * Load all CLI options for a list of configs. + * + * @param prefix placed at the beginning of the CLI option key + * @param configs Forms to read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + static boolean fillConfigs(String prefix, List<MConfig> configs, CommandLine line) + throws IOException { + for (MConfig config : configs) { + if (!fillConfig(prefix, config, line)) { + return false; + } + } + return true; + } + + /** + * Load all CLI options for a particular config. + * + * @param prefix placed at the beginning of the CLI option key + * @param config Config to read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + @SuppressWarnings("rawtypes") + static boolean fillConfig(String prefix, MConfig config, CommandLine line) throws IOException { + for (MInput input : config.getInputs()) { + if (!fillInput(prefix, input, line)) { + return false; + } + } + return true; + } + + /** + * Load CLI option. + * Chooses the appropriate 'fill' method to use based on input type. + * + * Keys for CLI options are automatically created from the 'prefix' argument + * and 'input' argument: <prefix>-<config name>-<input name> + * + * @param prefix placed at the beginning of the CLI option key + * @param input Input that we should read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + @SuppressWarnings("rawtypes") + public static boolean fillInput(String prefix, MInput input, CommandLine line) throws IOException { + // Based on the input type, let's perconfig specific load + switch (input.getType()) { + case STRING: + return fillInputString(prefix, (MStringInput) input, line); + case INTEGER: + return fillInputInteger(prefix, (MIntegerInput) input, line); + case BOOLEAN: + return fillInputBoolean(prefix, (MBooleanInput) input, line); + case MAP: + return fillInputMap(prefix, (MMapInput) input, line); + case ENUM: + return fillInputEnum(prefix, (MEnumInput) input, line); + default: + println("Unsupported data type " + input.getType()); + return true; + } + } + + /** + * Load CLI option for enum type. + * + * Currently only supports numeric values. + * + * @param prefix placed at the beginning of the CLI option key + * @param input Input that we should read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + private static boolean fillInputEnum(String prefix, + MEnumInput input, + CommandLine line) + throws IOException { + String opt = ConfigOptions.getOptionKey(prefix, input); + if (line.hasOption(opt)) { + String value = line.getOptionValue(opt); + int index = java.util.Arrays.asList(input.getValues()).indexOf(value); + + if(index < 0) { + errorMessage(input, String.format("Invalid option %s. Please use one of %s.", value, StringUtils.join(input.getValues(), ", "))); + return false; + } + + input.setValue(value); + } else { + input.setEmpty(); + } + return true; + } + + /** + * Load CLI options for map type. + * + * Parses Key-Value pairs that take the config "<key>=<value>&<key>=<value>&...". + * + * @param prefix placed at the beginning of the CLI option key + * @param input Input that we should read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + private static boolean fillInputMap(String prefix, + MMapInput input, + CommandLine line) + throws IOException { + String opt = ConfigOptions.getOptionKey(prefix, input); + if (line.hasOption(opt)) { + String value = line.getOptionValue(opt); + Map<String, String> values = new HashMap<String, String>(); + String[] entries = value.split("&"); + for (String entry : entries) { + if (entry.contains("=")) { + String[] keyValue = entry.split("="); + values.put(keyValue[0], keyValue[1]); + } else { + errorMessage(input, "Don't know what to do with " + entry); + return false; + } + } + input.setValue(values); + } else { + input.setEmpty(); + } + return true; + } + + /** + * Load integer input from CLI option. + * + * @param prefix placed at the beginning of the CLI option key + * @param input Input that we should read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + private static boolean fillInputInteger(String prefix, + MIntegerInput input, + CommandLine line) + throws IOException { + String opt = ConfigOptions.getOptionKey(prefix, input); + if (line.hasOption(opt)) { + try { + input.setValue(Integer.valueOf(line.getOptionValue(ConfigOptions.getOptionKey(prefix, input)))); + } catch (NumberFormatException ex) { + errorMessage(input, "Input is not valid integer number"); + return false; + } + } else { + input.setEmpty(); + } + return true; + } + + /** + * Load string input from CLI option. + * + * @param prefix placed at the beginning of the CLI option key + * @param input Input that we should read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + public static boolean fillInputString(String prefix, + MStringInput input, + CommandLine line) + throws IOException { + String opt = ConfigOptions.getOptionKey(prefix, input); + if (line.hasOption(opt)) { + String value = line.getOptionValue(ConfigOptions.getOptionKey(prefix, input)); + if(value.length() > input.getMaxLength()) { + errorMessage(input, "Size of input exceeds allowance for this input" + + " field. Maximal allowed size is " + input.getMaxLength()); + } + input.setValue(value); + } else { + input.setEmpty(); + } + return true; + } + + /** + * Load boolean input from CLI option. + * + * @param prefix placed at the beginning of the CLI option key + * @param input Input that we should read or edit + * @param line CLI options container + * @return + * @throws IOException + */ + public static boolean fillInputBoolean(String prefix, + MBooleanInput input, + CommandLine line) + throws IOException { + String opt = ConfigOptions.getOptionKey(prefix, input); + if (line.hasOption(opt)) { + input.setValue(Boolean.valueOf(line.getOptionValue(ConfigOptions.getOptionKey(prefix, input)))); + } else { + input.setEmpty(); + } + return true; + } + + /** + * Fill link object based on user input. + * + * @param reader Associated console reader object + * @param link Link that user is suppose to fill in + * @param linkConfigBundle Connector resource bundle + * @return True if we filled all inputs, false if user has stopped processing + * @throws IOException + */ + public static boolean fillLinkWithBundle(ConsoleReader reader, MLink link, ResourceBundle linkConfigBundle) + throws IOException { + + link.setName(getName(reader, link.getName())); + return fillLinkConfigWithBundle(reader, link.getConnectorLinkConfig().getConfigs(), linkConfigBundle); + } + + /** + * Fill job object based on user input. + * + * @param reader Associated console reader object + * @param job Job that user is suppose to fill in + * @param fromConfigBundle Connector resource bundle + * @param driverConfigBundle Driver config resource bundle + * @return True if we filled all inputs, false if user has stopped processing + * @throws IOException + */ + public static boolean fillJobWithBundle(ConsoleReader reader, + MJob job, + ResourceBundle fromConfigBundle, + ResourceBundle toConfigBundle, + ResourceBundle driverConfigBundle) + throws IOException { + + job.setName(getName(reader, job.getName())); + + return fillJobConfigWithBundle(reader, + job.getJobConfig(Direction.FROM).getConfigs(), + fromConfigBundle, + job.getJobConfig(Direction.TO).getConfigs(), + toConfigBundle, + job.getDriverConfig().getConfigs(), + driverConfigBundle); + } + public static boolean fillLinkConfigWithBundle(ConsoleReader reader, + List<MConfig> linkConfig, + ResourceBundle linkConfigBundle) throws IOException { + + + if(!fillConfigsWithBundle(linkConfig, reader, linkConfigBundle)) { + return false; + } + return true; + } + + public static boolean fillJobConfigWithBundle(ConsoleReader reader, + List<MConfig> fromConfig, + ResourceBundle fromConfigBundle, + List<MConfig> toConfig, + ResourceBundle toConfigBundle, + List<MConfig> driverConfig, + ResourceBundle driverConfigBundle) throws IOException { + + + // Job From config + if(!fillConfigsWithBundle(fromConfig, reader, fromConfigBundle)) { + return false; + } + // Job To config + if(!fillConfigsWithBundle(toConfig, reader, toConfigBundle)) { + return false; + } + // Job Driver config + if(!fillConfigsWithBundle(driverConfig, reader, driverConfigBundle)) { + return false; + } + + return true; + } + + public static boolean fillConfigsWithBundle(List<MConfig> configs, ConsoleReader reader, + ResourceBundle configBundle) throws IOException { + for (MConfig config : configs) { + if (!fillConfigWithBundle(config, reader, configBundle)) { + return false; + } + } + + return true; + } + + @SuppressWarnings("rawtypes") + public static boolean fillConfigWithBundle(MConfig config, ConsoleReader reader, ResourceBundle bundle) + throws IOException { + println(""); + println(bundle.getString(config.getLabelKey())); + + // Print out config validation + printValidationMessage(config, false); + println(""); + + for (MInput input : config.getInputs()) { + if(!fillInputWithBundle(input, reader, bundle)) { + return false; + } + } + + return true; + } + + @SuppressWarnings("rawtypes") + static boolean fillInputWithBundle(MInput input, ConsoleReader reader, ResourceBundle bundle) + throws IOException { + // Print out validation + printValidationMessage(input, false); + + // Based on the input type, let's perconfig specific load + switch (input.getType()) { + case STRING: + return fillInputStringWithBundle((MStringInput) input, reader, bundle); + case INTEGER: + return fillInputInteger((MIntegerInput) input, reader, bundle); + case BOOLEAN: + return fillInputBooleanWithBundle((MBooleanInput) input, reader, bundle); + case MAP: + return fillInputMapWithBundle((MMapInput) input, reader, bundle); + case ENUM: + return fillInputEnumWithBundle((MEnumInput) input, reader, bundle); + default: + println("Unsupported data type " + input.getType()); + return true; + } + } + + /** + * Load user input for enum type. + * + * Print out numbered list of all available options and let user choose one + * item from that. + * + * @param input Input that we should read or edit + * @param reader Associated console reader + * @param bundle Resource bundle + * @return True if user with to continue with loading addtional inputs + * @throws IOException + */ + private static boolean fillInputEnumWithBundle(MEnumInput input, + ConsoleReader reader, + ResourceBundle bundle) + throws IOException { + // Prompt in enum case + println(bundle.getString(input.getLabelKey()) + ": "); + + // Indexes + int i = -1; + int lastChoice = -1; + + // Print out all values as a numbered list + for(String value : input.getValues()) { + i++; + + println(" " + i + " : " + value); + + // Only show last choice if not sensitive + if(!input.isEmpty() && value.equals(input.getValue()) && !input.isSensitive()) { + lastChoice = i; + } + } + + // Prompt + reader.printString("Choose: "); + + // Fill previously filled index when available + if(lastChoice != -1) { + reader.putString(Integer.toString(lastChoice)); + } + + reader.flushConsole(); + String userTyped; + if(input.isSensitive()) { + userTyped = reader.readLine('*'); + } else { + userTyped = reader.readLine(); + } + + if (userTyped == null) { + return false; + } else if (userTyped.isEmpty()) { + input.setEmpty(); + } else { + Integer index; + try { + index = Integer.valueOf(userTyped); + + if(index < 0 || index >= input.getValues().length) { + errorMessage("Invalid index"); + return fillInputEnumWithBundle(input, reader, bundle); + } + + input.setValue(input.getValues()[index]); + } catch (NumberFormatException ex) { + errorMessage("Input is not valid integer number"); + return fillInputEnumWithBundle(input, reader, bundle); + } + } + + return true; + } + + /** + * Load user input for map type. + * + * This implementation will load one map entry at the time. Current flows is + * as follows: if user did not enter anything (empty input) finish loading + * and return from function. If user specified input with equal sign (=), + * lets add new key value pair. Otherwise consider entire input as a key name + * and try to remove it from the map. + * + * Please note that following code do not supports equal sign in property + * name. It's however perfectly fine to have equal sign in value. + * + * @param input Input that we should read or edit + * @param reader Associated console reader + * @param bundle Resource bundle + * @return True if user wish to continue with loading additional inputs + * @throws IOException + */ + private static boolean fillInputMapWithBundle(MMapInput input, + ConsoleReader reader, + ResourceBundle bundle) + throws IOException { + // Special prompt in Map case + println(bundle.getString(input.getLabelKey()) + ": "); + + // Internal loading map + Map<String, String> values = input.getValue(); + if(values == null) { + values = new HashMap<String, String>(); + } + + String userTyped; + + while(true) { + // Print all current items in each iteration + // However do not printout if this input contains sensitive information. + println("There are currently " + values.size() + " values in the map:"); + if (!input.isSensitive()) { + for(Map.Entry<String, String> entry : values.entrySet()) { + println(entry.getKey() + " = " + entry.getValue()); + } + } + + // Special prompt for Map entry + reader.printString("entry# "); + reader.flushConsole(); + + if(input.isSensitive()) { + userTyped = reader.readLine('*'); + } else { + userTyped = reader.readLine(); + } + + if(userTyped == null) { + // Finish loading and return back to Sqoop shell + return false; + } else if(userTyped.isEmpty()) { + // User has finished loading data to Map input, either set input empty + // if there are no entries or propagate entries to the input + if(values.size() == 0) { + input.setEmpty(); + } else { + input.setValue(values); + } + return true; + } else { + // User has specified regular input, let's check if it contains equals + // sign. Save new entry (or update existing one) if it does. Otherwise + // try to remove entry that user specified. + if(userTyped.contains("=")) { + String []keyValue = userTyped.split("=", 2); + values.put(handleUserInput(keyValue[0]), handleUserInput(keyValue[1])); + } else { + String key = handleUserInput(userTyped); + if(values.containsKey(key)) { + values.remove(key); + } else { + errorMessage("Don't know what to do with " + userTyped); + } + } + } + + } + } + + /** + * Handle special cases in user input. + * + * Preserve null and empty values, remove whitespace characters before and + * after loaded string and de-quote the string if it's quoted (to preserve + * spaces for example). + * + * @param input String loaded from user + * @return Unquoted transconfiged string + */ + private static String handleUserInput(String input) { + // Preserve null and empty values + if(input == null) { + return null; + } + if(input.isEmpty()) { + return input; + } + + // Removes empty characters at the begging and end of loaded string + input = input.trim(); + + int lastIndex = input.length() - 1; + char first = input.charAt(0); + char last = input.charAt(lastIndex); + + // Remove quoting if present + if(first == '\'' && last == '\'') { + input = input.substring(1, lastIndex); + } else if(first == '"' && last == '"') { + input = input.substring(1, lastIndex); + } + + // Return final string + return input; + } + + private static boolean fillInputInteger(MIntegerInput input, + ConsoleReader reader, + ResourceBundle bundle) + throws IOException { + generatePrompt(reader, bundle, input); + + // Fill already filled data when available + // However do not printout if this input contains sensitive information. + if(!input.isEmpty() && !input.isSensitive()) { + reader.putString(input.getValue().toString()); + } + + // Get the data + String userTyped; + if(input.isSensitive()) { + userTyped = reader.readLine('*'); + } else { + userTyped = reader.readLine(); + } + + if (userTyped == null) { + return false; + } else if (userTyped.isEmpty()) { + input.setEmpty(); + } else { + Integer value; + try { + value = Integer.valueOf(userTyped); + input.setValue(value); + } catch (NumberFormatException ex) { + errorMessage("Input is not valid integer number"); + return fillInputInteger(input, reader, bundle); + } + + input.setValue(Integer.valueOf(userTyped)); + } + + return true; + } + + /** + * Load string input from the user. + * + * @param input Input that we should load in + * @param reader Associated console reader + * @param bundle Resource bundle for this input + * @return + * @throws IOException + */ + static boolean fillInputStringWithBundle(MStringInput input, + ConsoleReader reader, + ResourceBundle bundle) + throws IOException { + generatePrompt(reader, bundle, input); + + // Fill already filled data when available + // However do not printout if this input contains sensitive information. + if(!input.isEmpty() && !input.isSensitive()) { + reader.putString(input.getValue()); + } + + // Get the data + String userTyped; + if(input.isSensitive()) { + userTyped = reader.readLine('*'); + } else { + userTyped = reader.readLine(); + } + + if (userTyped == null) { + // Propagate end of loading process + return false; + } else if (userTyped.isEmpty()) { + // Empty input in case that nothing was given + input.setEmpty(); + } else { + // Set value that user has entered + input.setValue(userTyped); + + // Check that it did not exceeds maximal allowance for given input + if(userTyped.length() > input.getMaxLength()) { + errorMessage("Size of input exceeds allowance for this input" + + " field. Maximal allowed size is " + input.getMaxLength()); + return fillInputStringWithBundle(input, reader, bundle); + } + } + + return true; + } + + /** + * Load boolean input from the user. + * + * @param input Input that we should load in + * @param reader Associated console reader + * @param bundle Resource bundle for this input + * @return + * @throws IOException + */ + static boolean fillInputBooleanWithBundle(MBooleanInput input, + ConsoleReader reader, + ResourceBundle bundle) + throws IOException { + generatePrompt(reader, bundle, input); + + // Fill already filled data when available + // However do not printout if this input contains sensitive information. + if(!input.isEmpty() && !input.isSensitive()) { + reader.putString(input.getValue().toString()); + } + + // Get the data + String userTyped; + if(input.isSensitive()) { + userTyped = reader.readLine('*'); + } else { + userTyped = reader.readLine(); + } + + if (userTyped == null) { + // Propagate end of loading process + return false; + } else if (userTyped.isEmpty()) { + // Empty input in case that nothing was given + input.setEmpty(); + } else { + // Set value that user has entered + input.setValue(Boolean.valueOf(userTyped)); + } + + return true; + } + + @SuppressWarnings("rawtypes") + static void generatePrompt(ConsoleReader reader, ResourceBundle bundle, MInput input) + throws IOException { + reader.printString(bundle.getString(input.getLabelKey()) + ": "); + reader.flushConsole(); + } + + static String getName(ConsoleReader reader, String name) throws IOException { + if (name == null) { + nameInput.setEmpty(); + } else { + nameInput.setValue(name); + } + + fillInputStringWithBundle(nameInput, reader, getResourceBundle()); + + return nameInput.getValue(); + } + + /** + * Print validation message in cases that it's not in state "FINE" + * + * @param element Validated element + */ + static void printValidationMessage(MValidatedElement element, boolean includeInputPrefix) { + if(element.getValidationStatus() == Status.getDefault()) { + return; + } + + for(Message message : element.getValidationMessages()) + switch (message.getStatus()) { + case UNACCEPTABLE: + if (includeInputPrefix) { + errorMessage(element, message.getMessage()); + } else { + errorMessage(message.getMessage()); + } + break; + case ACCEPTABLE: + if (includeInputPrefix) { + warningMessage(element, message.getMessage()); + } else { + warningMessage(message.getMessage()); + } + break; + default: + // Simply ignore all other states for the moment + break; + } + } + + static void errorMessage(String message) { + println("Error message: @|red " + message + " |@"); + } + + static void errorMessage(MNamedElement input, String message) { + print(input.getName()); + print(": "); + errorMessage(message); + } + + static void warningMessage(String message) { + println("Warning message: @|yellow " + message + " |@"); + } + + static void warningMessage(MNamedElement input, String message) { + print(input.getName()); + print(": "); + warningMessage(message); + } + + public static void errorIntroduction() { + println("\n @|red There are issues with entered data, please revise your input:|@"); + } + + // link object has the connector link config + public static void printLinkValidationMessages(MLink link) { + for (MConfig config : link.getConnectorLinkConfig().getConfigs()) { + for (MInput<?> input : config.getInputs()) { + printValidationMessage(input, true); + } + } + } + + // job has the from/to and the driver config + public static void printJobValidationMessages(MJob job) { + for (MConfig config : job.getJobConfig(Direction.FROM).getConfigs()) { + for (MInput<?> input : config.getInputs()) { + printValidationMessage(input, true); + } + } + + for (MConfig config : job.getJobConfig(Direction.TO).getConfigs()) { + for (MInput<?> input : config.getInputs()) { + printValidationMessage(input, true); + } + } + + for (MConfig config : job.getDriverConfig().getConfigs()) { + for (MInput<?> input : config.getInputs()) { + printValidationMessage(input, true); + } + } + } + + private ConfigFiller() { + // Do not instantiate + } +} http://git-wip-us.apache.org/repos/asf/sqoop/blob/8362c73c/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigOptions.java ---------------------------------------------------------------------- diff --git a/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigOptions.java b/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigOptions.java new file mode 100644 index 0000000..97b6e3b --- /dev/null +++ b/shell/src/main/java/org/apache/sqoop/shell/utils/ConfigOptions.java @@ -0,0 +1,117 @@ +/** + * 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.sqoop.shell.utils; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.sqoop.common.SqoopException; +import org.apache.sqoop.model.MConfig; +import org.apache.sqoop.model.MInput; +import org.apache.sqoop.model.MInputType; +import org.apache.sqoop.shell.core.ShellError; + +/** + * Utilities for automatically creating org.apache.commons.cli.Option objects. + */ +public class ConfigOptions { + /** + * This method is used to automatically generate keys + * for a particular input. + * + * @param prefix Prefix to prepend to CLI option keys + * @param input + * @return + */ + @SuppressWarnings("rawtypes") + public static String getOptionKey(String prefix, MInput input) { + return prefix + "-" + input.getName().replace('.', '-'); + } + + /** + * This method is used to automatically generate CLI options + * for a list of configs. + * + * @param prefix Prefix to prepend to CLI option keys + * @param configs Forms to get options for + * @return + */ + public static List<Option> getConfigsOptions(String prefix, List<MConfig> configs) { + List<Option> options = new LinkedList<Option>(); + for (MConfig config : configs) { + List<Option> configOptions = getConfigOptions(prefix, config); + options.addAll(configOptions); + } + return options; + } + + /** + * This method is used to automatically generate CLI options + * for a particular config. + * + * @param prefix Prefix to prepend to CLI option keys + * @param config Config to get options for + * @return List<Option> + */ + @SuppressWarnings({ "rawtypes", "static-access" }) + public static List<Option> getConfigOptions(String prefix, MConfig config) { + List<Option> options = new LinkedList<Option>(); + for (MInput input : config.getInputs()) { + if (input.getType().equals(MInputType.BOOLEAN)) { + options.add(OptionBuilder + .withLongOpt(getOptionKey(prefix, input)) + .create()); + } else { + options.add(OptionBuilder + .withLongOpt(getOptionKey(prefix, input)) + .hasArg() + .create()); + } + } + return options; + } + + /** + * Parses command line options. + * + * @param options parse arglist against these. + * @param start beginning index in arglist. + * @param arglist arguments to parse. + * @param stopAtNonOption stop parsing when nonoption found in arglist. + * @return CommandLine object + */ + public static CommandLine parseOptions(Options options, int start, List<String> arglist, boolean stopAtNonOption) { + String[] args = arglist.subList(start, arglist.size()).toArray(new String[arglist.size() - start]); + + CommandLineParser parser = new GnuParser(); + CommandLine line; + try { + line = parser.parse(options, args, stopAtNonOption); + } catch (ParseException e) { + throw new SqoopException(ShellError.SHELL_0003, e.getMessage(), e); + } + return line; + } +} http://git-wip-us.apache.org/repos/asf/sqoop/blob/8362c73c/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicConfigOptions.java ---------------------------------------------------------------------- diff --git a/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicConfigOptions.java b/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicConfigOptions.java new file mode 100644 index 0000000..d12bd2f --- /dev/null +++ b/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicConfigOptions.java @@ -0,0 +1,35 @@ +/** + * 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.sqoop.shell.utils; + +import org.apache.commons.cli.Options; + +/** + * Automatically create options for different components. + */ +@SuppressWarnings("serial") +public abstract class DynamicConfigOptions<M> extends Options { + + /** + * Create dynamic options. + * + * @param model generate options from this + * @return this + */ + public abstract void prepareOptions(M model); +} http://git-wip-us.apache.org/repos/asf/sqoop/blob/8362c73c/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicFormOptions.java ---------------------------------------------------------------------- diff --git a/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicFormOptions.java b/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicFormOptions.java deleted file mode 100644 index cc63610..0000000 --- a/shell/src/main/java/org/apache/sqoop/shell/utils/DynamicFormOptions.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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.sqoop.shell.utils; - -import org.apache.commons.cli.Options; - -/** - * Automatically create options for different components. - */ -@SuppressWarnings("serial") -public abstract class DynamicFormOptions<M> extends Options { - - /** - * Create dynamic options. - * - * @param model generate options from this - * @return this - */ - public abstract void prepareOptions(M model); -} http://git-wip-us.apache.org/repos/asf/sqoop/blob/8362c73c/shell/src/main/java/org/apache/sqoop/shell/utils/FormDisplayer.java ---------------------------------------------------------------------- diff --git a/shell/src/main/java/org/apache/sqoop/shell/utils/FormDisplayer.java b/shell/src/main/java/org/apache/sqoop/shell/utils/FormDisplayer.java deleted file mode 100644 index dcbccef..0000000 --- a/shell/src/main/java/org/apache/sqoop/shell/utils/FormDisplayer.java +++ /dev/null @@ -1,267 +0,0 @@ -/** - * 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.sqoop.shell.utils; - -import org.apache.commons.lang.StringUtils; -import org.apache.sqoop.common.Direction; -import org.apache.sqoop.model.MAccountableEntity; -import org.apache.sqoop.model.MBooleanInput; -import org.apache.sqoop.model.MLink; -import org.apache.sqoop.model.MConnector; -import org.apache.sqoop.model.MEnumInput; -import org.apache.sqoop.model.MForm; -import org.apache.sqoop.model.MDriverConfig; -import org.apache.sqoop.model.MInput; -import org.apache.sqoop.model.MInputType; -import org.apache.sqoop.model.MIntegerInput; -import org.apache.sqoop.model.MJob; -import org.apache.sqoop.model.MMapInput; -import org.apache.sqoop.model.MStringInput; -import org.apache.sqoop.shell.core.Constants; -import org.apache.sqoop.validation.Message; -import org.apache.sqoop.validation.Status; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; - -import static org.apache.sqoop.shell.ShellEnvironment.*; - -/** - * Convenience static methods for displaying form related information - */ -public final class FormDisplayer { - - public static void displayFormMetadataDetails(MDriverConfig driverConfig, - ResourceBundle bundle) { - displayFormsMetadata( - driverConfig.getConnectionForms().getForms(), - resourceString(Constants.RES_FORMDISPLAYER_CONNECTION), - bundle); - - displayFormsMetadata( - driverConfig.getJobForms().getForms(), - resourceString(Constants.RES_FORMDISPLAYER_JOB), - bundle); - } - - public static void displayFormMetadataDetails(MConnector connector, - ResourceBundle bundle) { - displayFormsMetadata( - connector.getConnectionForms().getForms(), - resourceString(Constants.RES_FORMDISPLAYER_CONNECTION), - bundle); - - // @TODO(Abe): Validate From/To output is correct. - displayFormsMetadata( - connector.getJobForms(Direction.FROM).getForms(), - Direction.FROM.toString() + " " + resourceString(Constants.RES_FORMDISPLAYER_JOB), - bundle); - - displayFormsMetadata( - connector.getJobForms(Direction.TO).getForms(), - Direction.TO.toString() + " " + resourceString(Constants.RES_FORMDISPLAYER_JOB), - bundle); - } - - public static void displayFormsMetadata(List<MForm> forms, - String type, - ResourceBundle bundle) { - Iterator<MForm> fiter = forms.iterator(); - int findx = 1; - while (fiter.hasNext()) { - print(" "); - print(type); - print(" %s ", resourceString(Constants.RES_FORMDISPLAYER_FORM)); - print(findx++); - println(":"); - - MForm form = fiter.next(); - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_NAME)); - println(form.getName()); - - // Label - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_LABEL)); - println(bundle.getString(form.getLabelKey())); - - // Help text - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_HELP)); - println(bundle.getString(form.getHelpKey())); - - List<MInput<?>> inputs = form.getInputs(); - Iterator<MInput<?>> iiter = inputs.iterator(); - int iindx = 1; - while (iiter.hasNext()) { - print(" %s ", resourceString(Constants.RES_FORMDISPLAYER_INPUT)); - print(iindx++); - println(":"); - - MInput<?> input = iiter.next(); - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_NAME)); - println(input.getName()); - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_LABEL)); - println(bundle.getString(input.getLabelKey())); - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_HELP)); - println(bundle.getString(input.getHelpKey())); - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_TYPE)); - println(input.getType()); - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_SENSITIVE)); - println(input.isSensitive()); - if (input.getType() == MInputType.STRING) { - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_SIZE)); - println(((MStringInput)input).getMaxLength()); - } else if(input.getType() == MInputType.ENUM) { - print(" %s: ", resourceString(Constants.RES_FORMDISPLAYER_POSSIBLE_VALUES)); - println(StringUtils.join(((MEnumInput)input).getValues(), ",")); - } - } - } - } - - public static void displayForms(List<MForm> forms, ResourceBundle bundle) { - for(MForm form : forms) { - displayForm(form, bundle); - } - } - - /** - * Method prints the warning message of ACCEPTABLE status - * @param entity - link or job instance - */ - public static void displayFormWarning(MAccountableEntity entity) { - List<MForm> formList = new ArrayList<MForm>(); - boolean showMessage = true; - if (entity instanceof MLink) { - MLink link = (MLink) entity; - formList.addAll(link.getConnectorPart().getForms()); - formList.addAll(link.getFrameworkPart().getForms()); - } else if(entity instanceof MJob) { - MJob job = (MJob) entity; - formList.addAll(job.getConnectorPart(Direction.FROM).getForms()); - formList.addAll(job.getFrameworkPart().getForms()); - formList.addAll(job.getConnectorPart(Direction.TO).getForms()); - } - for(MForm form : formList) { - if(form.getValidationStatus() == Status.ACCEPTABLE) { - if(showMessage) { - print("\n@|yellow %s|@\n", resourceString(Constants.RES_FORMDISPLAYER_FORM_WARNING)); - showMessage = false; - } - for(Message message : form.getValidationMessages()) { - FormFiller.warningMessage(message.getMessage()); - } - } - } - } - - private static void displayForm(MForm form, ResourceBundle bundle) { - print(" "); - println(bundle.getString(form.getLabelKey())); - - for (MInput<?> input : form.getInputs()) { - print(" "); - print(bundle.getString(input.getLabelKey())); - print(": "); - if(!input.isEmpty()) { - if (input.isSensitive()) { - print("(%s)", resourceString(Constants.RES_FORMDISPLAYER_INPUT_SENSITIVE)); - } else { - // Based on the input type, let's perform specific load - switch (input.getType()) { - case STRING: - displayInputString((MStringInput) input); - break; - case INTEGER: - displayInputInteger((MIntegerInput) input); - break; - case BOOLEAN: - displayInputBoolean((MBooleanInput) input); - break; - case MAP: - displayInputMap((MMapInput) input); - break; - case ENUM: - displayInputEnum((MEnumInput) input); - break; - default: - print("\n%s " + input.getType(), resourceString(Constants.RES_FORMDISPLAYER_UNSUPPORTED_DATATYPE)); - return; - } - } - } - println(""); - } - } - - /** - * Display content of String input. - * - * @param input String input - */ - private static void displayInputString(MStringInput input) { - print(input.getValue()); - } - - /** - * Display content of Integer input. - * - * @param input Integer input - */ - private static void displayInputInteger(MIntegerInput input) { - print(input.getValue()); - } - - /** - * Display content of Boolean input. - * - * @param input Boolean input - */ - private static void displayInputBoolean(MBooleanInput input) { - print(input.getValue()); - } - - /** - * Display content of Map input - * - * @param input Map input - */ - private static void displayInputMap(MMapInput input) { - for(Map.Entry<String, String> entry : input.getValue().entrySet()) { - println(); - print(" "); - print(entry.getKey()); - print(" = "); - print(entry.getValue()); - } - } - - /** - * Display content of Enum input - * - * @param input Enum input - */ - private static void displayInputEnum(MEnumInput input) { - print(input.getValue()); - } - - private FormDisplayer() { - // Do not instantiate - } -}