org.apache.sqoop.model.ConfigUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sqoop.model.ConfigUtils.java

Source

/**
 * 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.model;

import org.apache.commons.lang.StringUtils;
import org.apache.sqoop.common.SqoopException;
import org.apache.sqoop.utils.ClassUtils;
import org.apache.sqoop.validation.ConfigValidationRunner;
import org.apache.sqoop.validation.Message;
import org.apache.sqoop.validation.ConfigValidationResult;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Util class for transforming data from correctly annotated configuration
 * objects to different structures and vice-versa.
 *
 * TODO: This class should see some overhaul into more reusable code, especially expose and re-use the methods at the end.
 */
public class ConfigUtils {

    /**
     * Transform correctly annotated configuration object to corresponding
     * list of configs.
     *
     * Configs will be order according to the occurrence in the configuration
     * class. Inputs will be also ordered based on occurrence.
     *
     * @param configuration Annotated arbitrary configuration object
     * @return Corresponding list of configs
     */
    public static List<MConfig> toConfigs(Object configuration) {
        return toConfigs(configuration.getClass(), configuration);
    }

    public static List<MConfig> toConfigs(Class<?> klass) {
        return toConfigs(klass, null);
    }

    public static List<MConfig> toConfigs(Class<?> klass, Object configuration) {
        Set<String> configNames = new HashSet<String>();

        ConfigurationClass configurationClass = (ConfigurationClass) klass.getAnnotation(ConfigurationClass.class);

        // Each configuration object must have this class annotation
        if (configurationClass == null) {
            throw new SqoopException(ModelError.MODEL_003,
                    "Missing annotation ConfigurationClass on class " + klass.getName());
        }

        List<MConfig> configs = new LinkedList<MConfig>();

        // Iterate over all declared fields
        for (Field field : klass.getDeclaredFields()) {
            field.setAccessible(true);

            // Each field that should be part of user input should have Input
            // annotation.
            Config configAnnotation = field.getAnnotation(Config.class);

            if (configAnnotation != null) {
                String configName = getConfigName(field, configAnnotation, configNames);

                Class<?> type = field.getType();

                Object value = null;
                if (configuration != null) {
                    try {
                        value = field.get(configuration);
                    } catch (IllegalAccessException e) {
                        throw new SqoopException(ModelError.MODEL_005,
                                "Can't retrieve value from " + field.getName(), e);
                    }
                }

                configs.add(toConfig(configName, type, value));
            }
        }

        return configs;
    }

    @SuppressWarnings("unchecked")
    private static MConfig toConfig(String configName, Class klass, Object object) {
        ConfigClass global = (ConfigClass) klass.getAnnotation(ConfigClass.class);

        // Each configuration object must have this class annotation
        if (global == null) {
            throw new SqoopException(ModelError.MODEL_003,
                    "Missing annotation ConfigClass on class " + klass.getName());
        }

        // Intermediate list of inputs
        List<MInput<?>> inputs = new LinkedList<MInput<?>>();

        // Iterate over all declared fields
        for (Field field : klass.getDeclaredFields()) {
            field.setAccessible(true);

            String fieldName = field.getName();
            String inputName = configName + "." + fieldName;

            // Each field that should be part of user input should have Input
            // annotation.
            Input inputAnnotation = field.getAnnotation(Input.class);

            if (inputAnnotation != null) {
                boolean sensitive = inputAnnotation.sensitive();
                short maxLen = inputAnnotation.size();
                Class<?> type = field.getType();

                MInput input;

                // We need to support NULL, so we do not support primitive types
                if (type.isPrimitive()) {
                    throw new SqoopException(ModelError.MODEL_007,
                            "Detected primitive type " + type + " for field " + fieldName);
                }

                // Instantiate corresponding MInput<?> structure
                if (type == String.class) {
                    input = new MStringInput(inputName, sensitive, maxLen);
                } else if (type.isAssignableFrom(Map.class)) {
                    input = new MMapInput(inputName, sensitive);
                } else if (type == Integer.class) {
                    input = new MIntegerInput(inputName, sensitive);
                } else if (type == Boolean.class) {
                    input = new MBooleanInput(inputName, sensitive);
                } else if (type.isEnum()) {
                    input = new MEnumInput(inputName, sensitive, ClassUtils.getEnumStrings(type));
                } else {
                    throw new SqoopException(ModelError.MODEL_004,
                            "Unsupported type " + type.getName() + " for input " + fieldName);
                }

                // Move value if it's present in original configuration object
                if (object != null) {
                    Object value;
                    try {
                        value = field.get(object);
                    } catch (IllegalAccessException e) {
                        throw new SqoopException(ModelError.MODEL_005,
                                "Can't retrieve value from " + field.getName(), e);
                    }
                    if (value == null) {
                        input.setEmpty();
                    } else {
                        input.setValue(value);
                    }
                }

                inputs.add(input);
            }
        }

        return new MConfig(configName, inputs);
    }

    private static Field getFieldFromName(Class<?> klass, String name) {
        Field configField;
        try {
            configField = klass.getDeclaredField(name);
        } catch (NoSuchFieldException e) {
            // reverse lookup config field from custom config name
            if (name != null) {
                for (Field field : klass.getDeclaredFields()) {
                    Config configAnnotation = field.getAnnotation(Config.class);
                    if (configAnnotation == null) {
                        continue;
                    }
                    if (!StringUtils.isEmpty(configAnnotation.name()) && name.equals(configAnnotation.name())) {
                        return field;
                    }
                }
            }
            throw new SqoopException(ModelError.MODEL_006,
                    "Missing field " + name + " on config class " + klass.getCanonicalName(), e);
        }
        return configField;
    }

    /**
     * Convenience method to directly validate given model classes without the need to
     * manually create the configuration instance.
     *
     * @param configs Model representation with filled values
     * @param configClass Configuration class
     * @return Validation result
     */
    public static ConfigValidationResult validateConfigs(List<MConfig> configs, Class configClass) {
        ConfigValidationRunner validationRunner = new ConfigValidationRunner();
        Object configInstance = fromConfigs(configs, configClass);
        return validationRunner.validate(configInstance);
    }

    /**
     * Convenience method to convert given model structure into configuration object
     * that will be created from given class.
     *
     * @param configs Model representation with filled values
     * @param configClass Configuration class
     * @return Created instance based on the class with filled values
     */
    public static Object fromConfigs(List<MConfig> configs, Class configClass) {
        Object configInstance = ClassUtils.instantiate(configClass);
        if (configInstance == null) {
            throw new SqoopException(ModelError.MODEL_016, configClass.getCanonicalName());
        }

        fromConfigs(configs, configInstance);
        return configInstance;
    }

    /**
     * Move config values from config list into corresponding configuration object.
     *
     * @param configs Input config list
     * @param configuration Output configuration object
     */
    @SuppressWarnings("unchecked")
    public static void fromConfigs(List<MConfig> configs, Object configuration) {
        Class klass = configuration.getClass();

        for (MConfig config : configs) {
            Field configField;
            try {
                configField = klass.getDeclaredField(config.getName());
            } catch (NoSuchFieldException e) {
                throw new SqoopException(ModelError.MODEL_006,
                        "Missing field " + config.getName() + " on config class " + klass.getCanonicalName(), e);
            }

            configField = getFieldFromName(klass, config.getName());
            // We need to access this field even if it would be declared as private
            configField.setAccessible(true);
            Class<?> configClass = configField.getType();
            Object newValue = ClassUtils.instantiate(configClass);

            if (newValue == null) {
                throw new SqoopException(ModelError.MODEL_006, "Can't instantiate new config " + configClass);
            }

            for (MInput input : config.getInputs()) {
                String[] splitNames = input.getName().split("\\.");
                if (splitNames.length != 2) {
                    throw new SqoopException(ModelError.MODEL_009, "Invalid name: " + input.getName());
                }

                String inputName = splitNames[1];
                // TODO(jarcec): Names structures fix, handle error cases
                Field inputField;
                try {
                    inputField = configClass.getDeclaredField(inputName);
                } catch (NoSuchFieldException e) {
                    throw new SqoopException(ModelError.MODEL_006, "Missing field " + input.getName(), e);
                }

                // We need to access this field even if it would be declared as private
                inputField.setAccessible(true);

                try {
                    if (input.isEmpty()) {
                        inputField.set(newValue, null);
                    } else {
                        if (input.getType() == MInputType.ENUM) {
                            inputField.set(newValue, Enum.valueOf((Class<? extends Enum>) inputField.getType(),
                                    (String) input.getValue()));
                        } else {
                            inputField.set(newValue, input.getValue());
                        }
                    }
                } catch (IllegalAccessException e) {
                    throw new SqoopException(ModelError.MODEL_005, "Issue with field " + inputField.getName(), e);
                }
            }

            try {
                configField.set(configuration, newValue);
            } catch (IllegalAccessException e) {
                throw new SqoopException(ModelError.MODEL_005, "Issue with field " + configField.getName(), e);
            }
        }
    }

    /**
     * Apply given validations on list of configs.
     *
     * @param configs
     * @param result
     */
    public static void applyValidation(List<MConfig> configs, ConfigValidationResult result) {
        for (MConfig config : configs) {
            applyValidation(config, result);

            for (MInput input : config.getInputs()) {
                applyValidation(input, result);
            }
        }
    }

    /**
     * Apply validation messages on given element.
     *
     * Element's state will be set to default if there are no associated messages.
     *
     * @param element
     * @param result
     */
    public static void applyValidation(MValidatedElement element, ConfigValidationResult result) {
        List<Message> messages = result.getMessages().get(element.getName());

        if (messages != null) {
            element.setValidationMessages(messages);
        } else {
            element.resetValidationMessages();
        }
    }

    /**
     * Convert configuration object to JSON. Only filled properties are serialized,
     * properties with null value are skipped.
     *
     * @param configuration Correctly annotated configuration object
     * @return String of JSON representation
     */
    @SuppressWarnings("unchecked")
    public static String toJson(Object configuration) {
        Class klass = configuration.getClass();
        Set<String> configNames = new HashSet<String>();
        ConfigurationClass configurationClass = (ConfigurationClass) klass.getAnnotation(ConfigurationClass.class);

        // Each configuration object must have this class annotation
        if (configurationClass == null) {
            throw new SqoopException(ModelError.MODEL_003,
                    "Missing annotation ConfigurationGroup on class " + klass.getName());
        }

        JSONObject jsonOutput = new JSONObject();

        // Iterate over all declared fields
        for (Field configField : klass.getDeclaredFields()) {
            configField.setAccessible(true);

            // We're processing only config validations
            Config configAnnotation = configField.getAnnotation(Config.class);
            if (configAnnotation == null) {
                continue;
            }
            String configName = getConfigName(configField, configAnnotation, configNames);

            Object configValue;
            try {
                configValue = configField.get(configuration);
            } catch (IllegalAccessException e) {
                throw new SqoopException(ModelError.MODEL_005, "Issue with field " + configName, e);
            }

            JSONObject jsonConfig = new JSONObject();

            // Now process each input on the config
            for (Field inputField : configField.getType().getDeclaredFields()) {
                inputField.setAccessible(true);
                String inputName = inputField.getName();

                Object value;
                try {
                    value = inputField.get(configValue);
                } catch (IllegalAccessException e) {
                    throw new SqoopException(ModelError.MODEL_005,
                            "Issue with field " + configName + "." + inputName, e);
                }

                Input inputAnnotation = inputField.getAnnotation(Input.class);

                // Do not serialize all values
                if (inputAnnotation != null && value != null) {
                    Class<?> type = inputField.getType();

                    // We need to support NULL, so we do not support primitive types
                    if (type.isPrimitive()) {
                        throw new SqoopException(ModelError.MODEL_007,
                                "Detected primitive type " + type + " for field " + configName + "." + inputName);
                    }

                    if (type == String.class) {
                        jsonConfig.put(inputName, value);
                    } else if (type.isAssignableFrom(Map.class)) {
                        JSONObject map = new JSONObject();
                        for (Object key : ((Map) value).keySet()) {
                            map.put(key, ((Map) value).get(key));
                        }
                        jsonConfig.put(inputName, map);
                    } else if (type == Integer.class) {
                        jsonConfig.put(inputName, value);
                    } else if (type.isEnum()) {
                        jsonConfig.put(inputName, value.toString());
                    } else if (type == Boolean.class) {
                        jsonConfig.put(inputName, value);
                    } else {
                        throw new SqoopException(ModelError.MODEL_004, "Unsupported type " + type.getName()
                                + " for input " + configName + "." + inputName);
                    }
                }
            }

            jsonOutput.put(configName, jsonConfig);
        }
        return jsonOutput.toJSONString();
    }

    /**
     * Parse given input JSON string and move it's values to given configuration
     * object.
     *
     * @param json JSON representation of the configuration object
     * @param configuration ConfigurationGroup object to be filled
     */
    @SuppressWarnings("unchecked")
    public static void fillValues(String json, Object configuration) {
        Class<?> klass = configuration.getClass();

        Set<String> configNames = new HashSet<String>();
        JSONObject jsonConfigs = (JSONObject) JSONValue.parse(json);

        for (Field configField : klass.getDeclaredFields()) {
            configField.setAccessible(true);
            String configName = configField.getName();

            // We're processing only config validations
            Config configAnnotation = configField.getAnnotation(Config.class);
            if (configAnnotation == null) {
                continue;
            }
            configName = getConfigName(configField, configAnnotation, configNames);

            try {
                configField.set(configuration, configField.getType().newInstance());
            } catch (Exception e) {
                throw new SqoopException(ModelError.MODEL_005, "Issue with field " + configName, e);
            }

            JSONObject jsonInputs = (JSONObject) jsonConfigs.get(configField.getName());
            if (jsonInputs == null) {
                continue;
            }

            Object configValue;
            try {
                configValue = configField.get(configuration);
            } catch (IllegalAccessException e) {
                throw new SqoopException(ModelError.MODEL_005, "Issue with field " + configName, e);
            }

            for (Field inputField : configField.getType().getDeclaredFields()) {
                inputField.setAccessible(true);
                String inputName = inputField.getName();

                Input inputAnnotation = inputField.getAnnotation(Input.class);

                if (inputAnnotation == null || jsonInputs.get(inputName) == null) {
                    try {
                        inputField.set(configValue, null);
                    } catch (IllegalAccessException e) {
                        throw new SqoopException(ModelError.MODEL_005,
                                "Issue with field " + configName + "." + inputName, e);
                    }
                    continue;
                }

                Class<?> type = inputField.getType();

                try {
                    if (type == String.class) {
                        inputField.set(configValue, jsonInputs.get(inputName));
                    } else if (type.isAssignableFrom(Map.class)) {
                        Map<String, String> map = new HashMap<String, String>();
                        JSONObject jsonObject = (JSONObject) jsonInputs.get(inputName);
                        for (Object key : jsonObject.keySet()) {
                            map.put((String) key, (String) jsonObject.get(key));
                        }
                        inputField.set(configValue, map);
                    } else if (type == Integer.class) {
                        inputField.set(configValue, ((Long) jsonInputs.get(inputName)).intValue());
                    } else if (type.isEnum()) {
                        inputField.set(configValue, Enum.valueOf((Class<? extends Enum>) inputField.getType(),
                                (String) jsonInputs.get(inputName)));
                    } else if (type == Boolean.class) {
                        inputField.set(configValue, (Boolean) jsonInputs.get(inputName));
                    } else {
                        throw new SqoopException(ModelError.MODEL_004, "Unsupported type " + type.getName()
                                + " for input " + configName + "." + inputName);
                    }
                } catch (IllegalAccessException e) {
                    throw new SqoopException(ModelError.MODEL_005,
                            "Issue with field " + configName + "." + inputName, e);
                }
            }
        }
    }

    private static String getConfigName(Field member, Config annotation, Set<String> existingConfigNames) {
        if (StringUtils.isEmpty(annotation.name())) {
            return member.getName();
        } else {
            checkForValidConfigName(existingConfigNames, annotation.name());
            existingConfigNames.add(annotation.name());
            return annotation.name();
        }
    }

    private static void checkForValidConfigName(Set<String> existingConfigNames, String customConfigName) {
        // uniqueness across fields check
        if (existingConfigNames.contains(customConfigName)) {
            throw new SqoopException(ModelError.MODEL_012, "Issue with field config name " + customConfigName);
        }

        if (!Character.isJavaIdentifierStart(customConfigName.toCharArray()[0])) {
            throw new SqoopException(ModelError.MODEL_013, "Issue with field config name " + customConfigName);
        }
        for (Character c : customConfigName.toCharArray()) {
            if (Character.isJavaIdentifierPart(c))
                continue;
            throw new SqoopException(ModelError.MODEL_013, "Issue with field config name " + customConfigName);
        }

        if (customConfigName.length() > 30) {
            throw new SqoopException(ModelError.MODEL_014, "Issue with field config name " + customConfigName);

        }
    }

    public static String getName(Field input, Input annotation) {
        return input.getName();
    }

    public static String getName(Field config, Config annotation) {
        return config.getName();
    }

    public static ConfigurationClass getConfigurationClassAnnotation(Object object, boolean strict) {
        ConfigurationClass annotation = object.getClass().getAnnotation(ConfigurationClass.class);

        if (strict && annotation == null) {
            throw new SqoopException(ModelError.MODEL_003,
                    "Missing annotation ConfigurationGroupClass on class " + object.getClass().getName());
        }

        return annotation;
    }

    public static ConfigClass getConfigClassAnnotation(Object object, boolean strict) {
        ConfigClass annotation = object.getClass().getAnnotation(ConfigClass.class);

        if (strict && annotation == null) {
            throw new SqoopException(ModelError.MODEL_003,
                    "Missing annotation ConfigurationGroupClass on class " + object.getClass().getName());
        }

        return annotation;
    }

    public static Config getConfigAnnotation(Field field, boolean strict) {
        Config annotation = field.getAnnotation(Config.class);

        if (strict && annotation == null) {
            throw new SqoopException(ModelError.MODEL_003, "Missing annotation Config on Field " + field.getName()
                    + " on class " + field.getDeclaringClass().getName());
        }

        return annotation;
    }

    public static Input getInputAnnotation(Field field, boolean strict) {
        Input annotation = field.getAnnotation(Input.class);

        if (strict && annotation == null) {
            throw new SqoopException(ModelError.MODEL_003, "Missing annotation Input on Field " + field.getName()
                    + " on class " + field.getDeclaringClass().getName());
        }

        return annotation;
    }

    public static Object getFieldValue(Field field, Object object) {
        try {
            field.setAccessible(true);
            return field.get(object);
        } catch (IllegalAccessException e) {
            throw new SqoopException(ModelError.MODEL_015, e);
        }
    }
}