/** * 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.validation; import org.apache.sqoop.classification.InterfaceAudience; import org.apache.sqoop.classification.InterfaceStability; import org.apache.sqoop.common.SqoopException; import org.apache.sqoop.model.ConfigurationClass; import org.apache.sqoop.model.Config; import org.apache.sqoop.model.ConfigClass; import org.apache.sqoop.model.ConfigUtils; import org.apache.sqoop.model.Input; import org.apache.sqoop.model.Validator; import org.apache.sqoop.utils.ClassUtils; import org.apache.sqoop.validation.validators.AbstractValidator; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; /** * Validation runner that will run validators associated with given configuration * class or config object. * * Execution follows following rules: * * Run children first (Inputs -> Config -> Class) * * If any children is not suitable (canProceed = false), skip running parent * * Which means that config validator don't have to repeat it's input validators as it will * be never called if the input's are not valid. Similarly Class validators won't be called * unless all configs will pass validators. * */ @InterfaceAudience.Public @InterfaceStability.Unstable public class ConfigValidationRunner { /** * Private cache of instantiated validators. * * We're expecting that this cache will be very small as the number of possible validators * is driven to high extent by the number of connectors and hence we don't have a cache * eviction at the moment. */ private Map<Class<? extends AbstractValidator>, AbstractValidator> cache; public ConfigValidationRunner() { cache = new HashMap<Class<? extends AbstractValidator>, AbstractValidator>(); } /** * Validate given configuration instance. * * @param config Configuration instance * @return */ public ConfigValidationResult validate(Object config) { ConfigValidationResult result = new ConfigValidationResult(); ConfigurationClass globalAnnotation = ConfigUtils.getConfigurationClassAnnotation(config, true); // Iterate over all declared config and call their validators for (Field field : config.getClass().getDeclaredFields()) { field.setAccessible(true); Config configAnnotation = ConfigUtils.getConfigAnnotation(field, false); if(configAnnotation == null) { continue; } String configName = ConfigUtils.getName(field, configAnnotation); ConfigValidationResult r = validateConfig(configName, ConfigUtils.getFieldValue(field, config)); result.mergeValidatorResult(r); } // Call class validator only as long as we are in suitable state if(result.getStatus().canProceed()) { ConfigValidationResult r = validateArray("", config, globalAnnotation.validators()); result.mergeValidatorResult(r); } return result; } /** * Validate given config instance. * * @param configName Config's name to build full name for all inputs. * @param config Config instance * @return */ public ConfigValidationResult validateConfig(String configName, Object config) { ConfigValidationResult result = new ConfigValidationResult(); ConfigClass configAnnotation = ConfigUtils.getConfigClassAnnotation(config, true); // Iterate over all declared inputs and call their validators for (Field field : config.getClass().getDeclaredFields()) { Input inputAnnotation = ConfigUtils.getInputAnnotation(field, false); if(inputAnnotation == null) { continue; } String name = configName + "." + ConfigUtils.getName(field, inputAnnotation); ConfigValidationResult r = validateArray(name, ConfigUtils.getFieldValue(field, config), inputAnnotation.validators()); result.mergeValidatorResult(r); } // Call config validator only as long as we are in suitable state if(result.getStatus().canProceed()) { ConfigValidationResult r = validateArray(configName, config, configAnnotation.validators()); result.mergeValidatorResult(r); } return result; } /** * Execute array of validators on given object (can be input/config/class). * * @param name Full name of the object * @param object Input, Config or Class instance * @param validators Validators array * @return */ private ConfigValidationResult validateArray(String name, Object object, Validator[] validators) { ConfigValidationResult result = new ConfigValidationResult(); for (Validator validator : validators) { AbstractValidator v = executeValidator(object, validator); result.addValidatorResult(name, v); } return result; } /** * Execute single validator. * * @param object Input, Config or Class instance * @param validator Validator annotation * @return */ private AbstractValidator executeValidator(Object object, Validator validator) { // Try to get validator instance from the cache AbstractValidator instance = cache.get(validator.value()); if(instance == null) { instance = (AbstractValidator) ClassUtils.instantiate(validator.value()); // This could happen if we would be missing some connector's jars on our classpath if(instance == null) { throw new SqoopException(ConfigValidationError.VALIDATION_0004, validator.value().getName()); } cache.put(validator.value(), instance); } else { instance.reset(); } instance.setStringArgument(validator.strArg()); instance.validate(object); return instance; } }