blob: 5149cd2b8ac01922c0b3df97c5a9ce7ae72ba5f7 [file] [log] [blame]
/**
* 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.InputEditable;
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.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.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, InputEditable.ANY, StringUtils.EMPTY, (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.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:
return fillInputString(prefix, (MStringInput) input, line);
case INTEGER:
return fillInputInteger(prefix, (MIntegerInput) input, line);
case LONG:
return fillInputLong(prefix, (MLongInput) 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 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
*/
private 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
*/
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.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 fillInputInteger((MIntegerInput) input, reader, bundle);
case LONG:
return fillInputLong((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);
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;
}
private static boolean fillInputLong(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 fillInputLong(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());
}
// 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 "OK"
*
* @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 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;
}
}
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
}
}