Java tutorial
/** * 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); } } }