org.apache.sqoop.shell.utils.ConfigFiller.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sqoop.shell.utils.ConfigFiller.java

Source

/*
 * Copyright (C) 2016 Stratio (http://stratio.com)
 *
 * 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.
 */
package org.apache.sqoop.shell.utils;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.lang.StringUtils;
import org.apache.sqoop.model.InputEditable;
import org.apache.sqoop.model.MBooleanInput;
import org.apache.sqoop.model.MDateTimeInput;
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.MListInput;
import org.apache.sqoop.model.MLongInput;
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.validation.Message;
import org.apache.sqoop.validation.Status;
import org.joda.time.DateTime;

import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.ResourceBundle;

import jline.console.ConsoleReader;

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, InputEditable.ANY,
            StringUtils.EMPTY, (short) 25, Collections.EMPTY_LIST);

    /**
     * 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.getFromJobConfig().getConfigs(), job.getToJobConfig().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:
            assert input instanceof MStringInput;
            return fillInputString(prefix, (MStringInput) input, line);
        case INTEGER:
            assert input instanceof MIntegerInput;
            return fillInputInteger(prefix, (MIntegerInput) input, line);
        case LONG:
            assert input instanceof MLongInput;
            return fillInputLong(prefix, (MLongInput) input, line);
        case BOOLEAN:
            assert input instanceof MBooleanInput;
            return fillInputBoolean(prefix, (MBooleanInput) input, line);
        case MAP:
            assert input instanceof MMapInput;
            return fillInputMap(prefix, (MMapInput) input, line);
        case ENUM:
            assert input instanceof MEnumInput;
            return fillInputEnum(prefix, (MEnumInput) input, line);
        case LIST:
            assert input instanceof MListInput;
            return fillInputList(prefix, (MListInput) input, line);
        case DATETIME:
            assert input instanceof MDateTimeInput;
            return fillInputDateTime(prefix, (MDateTimeInput) input, line);
        default:
            println("Unsupported data type " + input.getType());
            return true;
        }
    }

    /**
     * Load CLI options for datetime type.
     *
     * Supports the following two types of format:
     * <ul>
     * <li> ISO8601 format (yyyy-MM-ddTHH:mm:ss.SSSZZ)
     * <li> Long which represents the milliseconds from 1970-01-01T00:00:00Z
     * </ul>
     *
     * @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
     */
    static boolean fillInputDateTime(String prefix, MDateTimeInput input, CommandLine line) throws IOException {
        String opt = ConfigOptions.getOptionKey(prefix, input);
        if (line.hasOption(opt)) {
            DateTime dt = parseDateTime(line.getOptionValue(opt));
            if (dt == null) {
                errorMessage(input, "Input is not valid DateTime format");
                return false;
            }
            input.setValue(dt);
        } else {
            input.setEmpty();
        }
        return true;
    }

    private static DateTime parseDateTime(String value) {
        DateTime dt = null;
        try {
            dt = new DateTime(Long.parseLong(value));
        } catch (NumberFormatException nfe) {
            // value is not numeric string
            try {
                dt = DateTime.parse(value);
            } catch (IllegalArgumentException iae) {
                // value is not valid ISO8601 format
            }
        }
        return dt;
    }

    /**
     * Load CLI options for list type.
     *
     * Parses elements that take the config "<element1>&<element2>&...".
     *
     * @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
     */
    static boolean fillInputList(String prefix, MListInput input, CommandLine line) throws IOException {
        String opt = ConfigOptions.getOptionKey(prefix, input);
        if (line.hasOption(opt)) {
            String value = line.getOptionValue(opt);
            List<String> values = new LinkedList<String>();
            for (String element : value.split("&")) {
                values.add(element);
            }
            input.setValue(values);
        } else {
            input.setEmpty();
        }
        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
     */
    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
     */
    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) {
                String[] keyValue = null;
                if (entry.contains("=")) {
                    keyValue = entry.split("=", 2);
                }
                if (keyValue != null && keyValue.length == 2) {
                    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
     */
    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 long 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
     */
    static boolean fillInputLong(String prefix, MLongInput input, CommandLine line) throws IOException {
        String opt = ConfigOptions.getOptionKey(prefix, input);
        if (line.hasOption(opt)) {
            try {
                input.setValue(Long.valueOf(line.getOptionValue(ConfigOptions.getOptionKey(prefix, input))));
            } catch (NumberFormatException ex) {
                errorMessage(input, "Input is not a valid long ");
                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
     */
    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 ((input.getMaxLength() >= 0) && (value.length() > input.getMaxLength())) {
                errorMessage(input, "Size of input exceeds allowance for this input"
                        + " field. Maximal allowed size is " + input.getMaxLength());
                return false;
            }
            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
     */
    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.getFromJobConfig().getConfigs(), fromConfigBundle,
                job.getToJobConfig().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 fillInputIntegerWithBundle((MIntegerInput) input, reader, bundle);
        case LONG:
            return fillInputLongWithBundle((MLongInput) 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);
        case LIST:
            return fillInputListWithBundle((MListInput) input, reader, bundle);
        case DATETIME:
            return fillInputDateTimeWithBundle((MDateTimeInput) input, reader, bundle);
        default:
            println("Unsupported data type " + input.getType());
            return true;
        }
    }

    /**
     * Load user input for datetime type.
     *
     * This implementation supports the following two types of format:
     * <ul>
     * <li> ISO8601 format (yyyy-MM-ddTHH:mm:ss.SSSZZ)
     * <li> Long which represents the milliseconds from 1970-01-01T00:00:00Z
     * </ul>
     *
     * If user did not enter anything (empty input) finish loading and return from function.
     *
     * @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
     */
    static boolean fillInputDateTimeWithBundle(MDateTimeInput input, ConsoleReader reader, ResourceBundle bundle)
            throws IOException {
        generatePrompt(reader, bundle, input);

        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 {
            DateTime dt = parseDateTime(userTyped);
            if (dt == null) {
                errorMessage("Input is not a valid DateTime");
                return fillInputDateTimeWithBundle(input, reader, bundle);
            }

            input.setValue(dt);
        }

        return true;
    }

    /**
     * Load user input for list type.
     *
     * This implementation will load one element at a time. If user did not
     * enter anything (empty input) finish loading and return from function.
     *
     * @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
     */
    static boolean fillInputListWithBundle(MListInput input, ConsoleReader reader, ResourceBundle bundle)
            throws IOException {
        // Special prompt in List case
        println(bundle.getString(input.getLabelKey()) + ": ");

        // Internal loading list
        List<String> values = input.getValue();
        if (values == null) {
            values = new LinkedList<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 list:");
            if (!input.isSensitive()) {
                for (String value : values) {
                    println(value);
                }
            }

            // Special prompt for List element
            reader.print("element# ");
            reader.flush();

            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 List input, either set input empty
                // if there are no elements or propagate elements to the input
                if (values.size() == 0) {
                    input.setEmpty();
                } else {
                    input.setValue(values);
                }
                return true;
            } else {
                values.add(userTyped);
            }

        }
    }

    /**
     * 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
     */
    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.print("Choose: ");

        // Fill previously filled index when available
        if (lastChoice != -1) {
            reader.putString(Integer.toString(lastChoice));
        }

        reader.flush();

        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
     */
    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.print("entry# ");
            reader.flush();

            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);
                    if (keyValue.length == 2) {
                        values.put(handleUserInput(keyValue[0]), handleUserInput(keyValue[1]));
                    } else {
                        errorMessage("Don't know what to do with " + userTyped);
                    }
                } 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;
    }

    static boolean fillInputIntegerWithBundle(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 fillInputIntegerWithBundle(input, reader, bundle);
            }

            input.setValue(Integer.valueOf(userTyped));
        }

        return true;
    }

    static boolean fillInputLongWithBundle(MLongInput input, ConsoleReader reader, ResourceBundle bundle)
            throws IOException {
        generatePrompt(reader, bundle, input);

        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 {
            Long value;
            try {
                value = Long.valueOf(userTyped);
                input.setValue(value);
            } catch (NumberFormatException ex) {
                errorMessage("Input is not a valid long");
                return fillInputLongWithBundle(input, reader, bundle);
            }

            input.setValue(Long.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());
            reader.flush();
        }

        // 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 ((input.getMaxLength() >= 0) && (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 {
            Boolean value;
            try {
                value = Boolean.valueOf(userTyped);
                input.setValue(value);
            } catch (NumberFormatException ex) {
                errorMessage("Input is not a valid boolean");
                return fillInputBooleanWithBundle(input, reader, bundle);
            }

            // 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.print(bundle.getString(input.getLabelKey()) + ": ");
        reader.flush();
    }

    static String getName(ConsoleReader reader, String name) throws IOException {
        if (name == null) {
            nameInput.setEmpty();
        } else {
            nameInput.setValue(name);
        }

        do {
            fillInputStringWithBundle(nameInput, reader, getResourceBundle());
            if (StringUtils.isEmpty(nameInput.getValue())) {
                errorMessage(nameInput, "Job name or link name cannot be null");
            } else {
                break;
            }
        } while (true);

        return nameInput.getValue();
    }

    /**
     * Print validation message in cases that it's not in state "OK"
     *
     * @param element Validated element
     */
    static void printValidationMessage(MNamedElement element, boolean includeInputPrefix) {
        if (element.getValidationStatus() == Status.getDefault()) {
            return;
        }

        for (Message message : element.getValidationMessages())
            switch (message.getStatus()) {
            case ERROR:
                if (includeInputPrefix) {
                    errorMessage(element, message.getMessage());
                } else {
                    errorMessage(message.getMessage());
                }
                break;
            case WARNING:
                if (includeInputPrefix) {
                    warningMessage(element, message.getMessage());
                } else {
                    warningMessage(message.getMessage());
                }
                break;
            default:
                // Simply ignore all other states for the moment
                break;
            }
    }

    public 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.getFromJobConfig().getConfigs()) {
            for (MInput<?> input : config.getInputs()) {
                printValidationMessage(input, true);
            }
        }

        for (MConfig config : job.getToJobConfig().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
    }
}