blob: eb4ce7d9b594bc220fff192283a5d35bdad2d1c1 [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.storm.validation;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.storm.Config;
import org.apache.storm.utils.Utils;
import org.apache.storm.validation.ConfigValidationAnnotations.ValidatorParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides functionality for validating configuration fields.
*/
public class ConfigValidation {
private static final Logger LOG = LoggerFactory.getLogger(ConfigValidation.class);
//We follow the model of service loaders (Even though it is not a service).
private static final String CONFIG_CLASSES_NAME = "META-INF/services/" + Validated.class.getName();
/*
* Validator definitions
*/
//The following come from the JVm Specification table 4.4
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.5
private static final int ACC_PUBLIC = 0x0001;
private static final int ACC_STATIC = 0x0008;
private static final int ACC_FINAL = 0x0010;
private static final int DESIRED_FIELD_ACC = ACC_PUBLIC | ACC_STATIC | ACC_FINAL;
private static List<Class<?>> configClasses = null;
public static synchronized List<Class<?>> getConfigClasses() {
if (configClasses == null) {
List<Class<?>> ret = new ArrayList<>();
Set<String> classesToScan = new HashSet<>();
classesToScan.add(Config.class.getName());
for (URL url : Utils.findResources(CONFIG_CLASSES_NAME)) {
try {
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()))) {
String line;
while ((line = in.readLine()) != null) {
line = line.replaceAll("#.*$", "").trim();
if (!line.isEmpty()) {
classesToScan.add(line);
}
}
}
} catch (IOException e) {
throw new RuntimeException("Error trying to read " + url, e);
}
}
for (String clazz : classesToScan) {
try {
ret.add(Class.forName(clazz));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
LOG.info("Will use {} for validation", ret);
configClasses = ret;
}
return configClasses;
}
/**
* Validates a field given field name as string uses Config.java as the default config class
*
* @param fieldName provided as a string
* @param conf map of confs
*/
public static void validateField(String fieldName, Map<String, Object> conf) {
validateField(fieldName, conf, getConfigClasses());
}
/**
* Validates a field given field name as string.
*
* @param fieldName provided as a string
* @param conf map of confs
* @param configs config class
*/
public static void validateField(String fieldName, Map<String, Object> conf, List<Class<?>> configs) {
Field field = null;
for (Class<?> clazz : configs) {
try {
field = clazz.getField(fieldName);
} catch (NoSuchFieldException e) {
//Ignored
}
}
if (field == null) {
throw new RuntimeException("Could not find " + fieldName + " in any of " + configs);
}
validateField(field, conf);
}
/**
* Validates a field given field. Calls correct ValidatorField method based on which fields are declared for the corresponding
* annotation.
*
* @param field field that needs to be validated
* @param conf map of confs
*/
public static void validateField(Field field, Map<String, Object> conf) {
Annotation[] annotations = field.getAnnotations();
if (annotations.length == 0) {
LOG.warn("Field {} does not have validator annotation", field);
}
try {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(Deprecated.class)) {
LOG.warn("{} is a deprecated config please see {}.{} for more information.",
field.get(null), field.getDeclaringClass(), field.getName());
continue;
}
String type = annotation.annotationType().getName();
Class<?> validatorClass = null;
Class<?>[] classes = ConfigValidationAnnotations.class.getDeclaredClasses();
//check if annotation is one of our
for (Class<?> clazz : classes) {
if (clazz.getName().equals(type)) {
validatorClass = clazz;
break;
}
}
if (validatorClass != null) {
Object v = validatorClass.cast(annotation);
String key = (String) field.get(null);
@SuppressWarnings("unchecked")
Class<Validator> clazz = (Class<Validator>) validatorClass
.getMethod(ConfigValidationAnnotations.ValidatorParams.VALIDATOR_CLASS).invoke(v);
Validator o = null;
Map<String, Object> params = getParamsFromAnnotation(validatorClass, v);
//two constructor signatures used to initialize validators.
//One constructor takes input a Map of arguments, the other doesn't take any arguments (default constructor)
//If validator has a constructor that takes a Map as an argument call that constructor
if (hasConstructor(clazz, Map.class)) {
o = clazz.getConstructor(Map.class).newInstance(params);
} else { //If not call default constructor
o = clazz.newInstance();
}
o.validateField(field.getName(), conf.get(key));
}
}
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Validate topology conf.
* @param topoConf The topology conf.
*/
public static void validateTopoConf(Map<String, Object> topoConf) {
validateFields(topoConf, Arrays.asList(Config.class));
}
/**
* Validate all confs in map.
*
* @param conf map of configs
*/
public static void validateFields(Map<String, Object> conf) {
validateFields(conf, getConfigClasses());
}
/**
* Validate all confs in map.
*
* @param conf map of configs
* @param classes config class
*/
public static void validateFields(Map<String, Object> conf, List<Class<?>> classes) {
for (Class<?> clazz : classes) {
for (Field field : clazz.getDeclaredFields()) {
if (!isFieldAllowed(field)) {
continue;
}
Object keyObj = null;
try {
keyObj = field.get(null);
} catch (IllegalAccessException e) {
//This should not happen because we checked for PUBLIC in isFieldAllowed
throw new RuntimeException(e);
}
//make sure that defined key is string in case wrong stuff got put into Config.java
if (keyObj instanceof String) {
String confKey = (String) keyObj;
if (conf.containsKey(confKey)) {
validateField(field, conf);
}
}
}
}
}
public static boolean isFieldAllowed(Field field) {
return field.getAnnotation(NotConf.class) == null
&& String.class.equals(field.getType())
&& ((field.getModifiers() & DESIRED_FIELD_ACC) == DESIRED_FIELD_ACC) && !field.isSynthetic();
}
private static Map<String, Object> getParamsFromAnnotation(Class<?> validatorClass, Object v)
throws InvocationTargetException, IllegalAccessException {
Map<String, Object> params = new HashMap<String, Object>();
for (Method method : validatorClass.getDeclaredMethods()) {
Object value = null;
try {
value = (Object) method.invoke(v);
} catch (IllegalArgumentException ex) {
value = null;
}
if (value != null) {
params.put(method.getName(), value);
}
}
return params;
}
private static boolean hasConstructor(Class<?> clazz, Class<?> paramClass) {
Class<?>[] classes = { paramClass };
try {
clazz.getConstructor(classes);
} catch (NoSuchMethodException e) {
return false;
}
return true;
}
public abstract static class Validator {
public Validator(Map<String, Object> params) {
}
public Validator() {
}
public abstract void validateField(String name, Object o);
}
/**
* Validates if an object is not null.
*/
public static class NotNullValidator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
throw new IllegalArgumentException("Field " + name + "cannot be null! Actual value: " + o);
}
}
}
/**
* Validates basic types.
*/
public static class SimpleTypeValidator extends Validator {
private Class<?> type;
public SimpleTypeValidator(Map<String, Object> params) {
this.type = (Class<?>) params.get(ConfigValidationAnnotations.ValidatorParams.TYPE);
}
public static void validateField(String name, Class<?> type, Object o) {
if (o == null) {
return;
}
if (type.isInstance(o)) {
return;
}
throw new IllegalArgumentException(
"Field " + name + " must be of type " + type + ". Object: " + o + " actual type: " + o.getClass());
}
@Override
public void validateField(String name, Object o) {
validateField(name, this.type, o);
}
}
/**
* Checks if the named type derives from the specified Class.
*/
public static class DerivedTypeValidator extends Validator {
private Class<?> baseType;
public DerivedTypeValidator(Map<String, Object> params) {
this.baseType = (Class<?>) params.get(ValidatorParams.BASE_TYPE);
}
public static void validateField(String name, Class<?> baseType, Object actualTypeName) {
if (actualTypeName == null) {
return;
}
try {
Class<?> actualType = Class.forName(actualTypeName.toString());
if (baseType.isAssignableFrom(actualType)) {
return;
}
throw new IllegalArgumentException(
"Field " + name + " must represent a type that derives from '" + baseType + "'. Specified type = " + actualTypeName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
@Override
public void validateField(String name, Object actualTypeName) {
validateField(name, this.baseType, actualTypeName);
}
}
public static class StringValidator extends Validator {
private HashSet<String> acceptedValues = null;
public StringValidator() {
}
public StringValidator(Map<String, Object> params) {
this.acceptedValues =
new HashSet<String>(Arrays.asList((String[]) params.get(ConfigValidationAnnotations.ValidatorParams.ACCEPTED_VALUES)));
if (this.acceptedValues.isEmpty() || (this.acceptedValues.size() == 1 && this.acceptedValues.contains(""))) {
this.acceptedValues = null;
}
}
@Override
public void validateField(String name, Object o) {
SimpleTypeValidator.validateField(name, String.class, o);
if (this.acceptedValues != null) {
if (!this.acceptedValues.contains((String) o)) {
throw new IllegalArgumentException(
"Field " + name + " is not an accepted value. Value: " + o + " Accepted values: " + this.acceptedValues);
}
}
}
}
public static class BooleanValidator extends Validator {
@Override
public void validateField(String name, Object o) {
SimpleTypeValidator.validateField(name, Boolean.class, o);
}
}
public static class NumberValidator extends Validator {
@Override
public void validateField(String name, Object o) {
SimpleTypeValidator.validateField(name, Number.class, o);
}
}
public static class DoubleValidator extends Validator {
@Override
public void validateField(String name, Object o) {
SimpleTypeValidator.validateField(name, Double.class, o);
}
}
/**
* Validates a Integer.
*/
public static class IntegerValidator extends Validator {
@Override
public void validateField(String name, Object o) {
validateInteger(name, o);
}
public void validateInteger(String name, Object o) {
if (o == null) {
return;
}
final long i;
if (o instanceof Number
&& (i = ((Number) o).longValue()) == ((Number) o).doubleValue()) {
if (i <= Integer.MAX_VALUE && i >= Integer.MIN_VALUE) {
return;
}
}
throw new IllegalArgumentException("Field " + name + " must be an Integer within type range.");
}
}
/**
* Validates an entry for ImpersonationAclUser.
*/
public static class ImpersonationAclUserEntryValidator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
ConfigValidationUtils.NestableFieldValidator validator =
ConfigValidationUtils.mapFv(ConfigValidationUtils.fv(String.class, false),
ConfigValidationUtils.listFv(String.class, false), false);
validator.validateField(name, o);
@SuppressWarnings("unchecked")
Map<String, List<String>> mapObject = (Map<String, List<String>>) o;
if (!mapObject.containsKey("hosts")) {
throw new IllegalArgumentException(name + " should contain Map entry with key: hosts");
}
if (!mapObject.containsKey("groups")) {
throw new IllegalArgumentException(name + " should contain Map entry with key: groups");
}
}
}
/**
* validates a list of has no duplicates.
*/
public static class NoDuplicateInListValidator extends Validator {
@Override
public void validateField(String name, Object field) {
if (field == null) {
return;
}
//check if iterable
SimpleTypeValidator.validateField(name, Iterable.class, field);
HashSet<Object> objectSet = new HashSet<Object>();
for (Object o : (Iterable<?>) field) {
if (objectSet.contains(o)) {
throw new IllegalArgumentException(name + " should contain no duplicate elements. Duplicated element: " + o);
}
objectSet.add(o);
}
}
}
/**
* Validates a String or a list of Strings.
*/
public static class StringOrStringListValidator extends Validator {
private ConfigValidationUtils.FieldValidator fv = ConfigValidationUtils.listFv(String.class, false);
@Override
public void validateField(String name, Object o) {
if (o == null || o instanceof String) {
// A null value or a String value is acceptable
return;
}
this.fv.validateField(name, o);
}
}
/**
* Validates Kryo Registration.
*/
public static class KryoRegValidator extends Validator {
@SuppressWarnings("unchecked")
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
if (o instanceof Iterable) {
for (Object e : (Iterable<?>) o) {
if (e instanceof Map) {
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) e).entrySet()) {
if (!(entry.getKey() instanceof String)
|| !(entry.getValue() instanceof String)) {
throw new IllegalArgumentException(
"Each element of the list " + name + " must be a String or a Map of Strings");
}
}
} else if (!(e instanceof String)) {
throw new IllegalArgumentException(
"Each element of the list " + name + " must be a String or a Map of Strings");
}
}
return;
}
throw new IllegalArgumentException(
"Field " + name + " must be an Iterable containing only Strings or Maps of Strings");
}
}
/*
* Methods for validating confs
*/
/**
* Validates if a number is a power of 2.
*/
public static class PowerOf2Validator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
final long i;
if (o instanceof Number
&& (i = ((Number) o).longValue()) == ((Number) o).doubleValue()) {
// Test whether the integer is a power of 2.
if (i > 0 && (i & (i - 1)) == 0) {
return;
}
}
throw new IllegalArgumentException("Field " + name + " must be a power of 2.");
}
}
/**
* Validates each entry in a list.
*/
public static class ListEntryTypeValidator extends Validator {
private Class<?> type;
public ListEntryTypeValidator(Map<String, Object> params) {
this.type = (Class<?>) params.get(ConfigValidationAnnotations.ValidatorParams.TYPE);
}
public static void validateField(String name, Class<?> type, Object o) {
ConfigValidationUtils.NestableFieldValidator validator = ConfigValidationUtils.listFv(type, false);
validator.validateField(name, o);
}
@Override
public void validateField(String name, Object o) {
validateField(name, this.type, o);
}
}
/**
* Validates each entry in a list against a list of custom Validators. Each validator in the list of validators must inherit or be an
* instance of Validator class
*/
public static class ListEntryCustomValidator extends Validator {
private Class<?>[] entryValidators;
public ListEntryCustomValidator(Map<String, Object> params) {
this.entryValidators = (Class<?>[]) params.get(ConfigValidationAnnotations.ValidatorParams.ENTRY_VALIDATOR_CLASSES);
}
public static void validateField(String name, Class<?>[] validators, Object o)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
if (o == null) {
return;
}
//check if iterable
SimpleTypeValidator.validateField(name, Iterable.class, o);
for (Object entry : (Iterable<?>) o) {
for (Class<?> validator : validators) {
Object v = validator.getConstructor().newInstance();
if (v instanceof Validator) {
((Validator) v).validateField(name + " list entry", entry);
} else {
LOG.warn(
"validator: {} cannot be used in ListEntryCustomValidator. Individual entry validators must a instance of "
+ "Validator class",
validator.getName());
}
}
}
}
@Override
public void validateField(String name, Object o) {
try {
validateField(name, this.entryValidators, o);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
/**
* validates each key and value in a map of a certain type.
*/
public static class MapEntryTypeValidator extends Validator {
private Class<?> keyType;
private Class<?> valueType;
public MapEntryTypeValidator(Map<String, Object> params) {
this.keyType = (Class<?>) params.get(ConfigValidationAnnotations.ValidatorParams.KEY_TYPE);
this.valueType = (Class<?>) params.get(ConfigValidationAnnotations.ValidatorParams.VALUE_TYPE);
}
public static void validateField(String name, Class<?> keyType, Class<?> valueType, Object o) {
ConfigValidationUtils.NestableFieldValidator validator = ConfigValidationUtils.mapFv(keyType, valueType, false);
validator.validateField(name, o);
}
@Override
public void validateField(String name, Object o) {
validateField(name, this.keyType, this.valueType, o);
}
}
/**
* validates each key and each value against the respective arrays of validators.
*/
public static class MapEntryCustomValidator extends Validator {
private Class<?>[] keyValidators;
private Class<?>[] valueValidators;
public MapEntryCustomValidator(Map<String, Object> params) {
this.keyValidators = (Class<?>[]) params.get(ConfigValidationAnnotations.ValidatorParams.KEY_VALIDATOR_CLASSES);
this.valueValidators = (Class<?>[]) params.get(ConfigValidationAnnotations.ValidatorParams.VALUE_VALIDATOR_CLASSES);
}
@SuppressWarnings("unchecked")
public static void validateField(String name, Class<?>[] keyValidators, Class<?>[] valueValidators, Object o)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
if (o == null) {
return;
}
//check if Map
SimpleTypeValidator.validateField(name, Map.class, o);
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) o).entrySet()) {
for (Class<?> kv : keyValidators) {
Object keyValidator = kv.getConstructor().newInstance();
if (keyValidator instanceof Validator) {
((Validator) keyValidator).validateField(name + " Map key", entry.getKey());
} else {
LOG.warn(
"validator: {} cannot be used in MapEntryCustomValidator to validate keys. Individual entry validators must "
+ "a instance of Validator class",
kv.getName());
}
}
for (Class<?> vv : valueValidators) {
Object valueValidator = vv.getConstructor().newInstance();
if (valueValidator instanceof Validator) {
((Validator) valueValidator).validateField(name + " Map value", entry.getValue());
} else {
LOG.warn(
"validator: {} cannot be used in MapEntryCustomValidator to validate values. Individual entry validators "
+ "must a instance of Validator class",
vv.getName());
}
}
}
}
@Override
public void validateField(String name, Object o) {
try {
validateField(name, this.keyValidators, this.valueValidators, o);
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
/**
* Validates a positive number.
*/
public static class PositiveNumberValidator extends Validator {
private boolean includeZero;
public PositiveNumberValidator() {
this.includeZero = false;
}
public PositiveNumberValidator(Map<String, Object> params) {
this.includeZero = (boolean) params.get(ConfigValidationAnnotations.ValidatorParams.INCLUDE_ZERO);
}
public static void validateField(String name, boolean includeZero, Object o) {
if (o == null) {
return;
}
if (o instanceof Number) {
if (includeZero) {
if (((Number) o).doubleValue() >= 0.0) {
return;
}
} else {
if (((Number) o).doubleValue() > 0.0) {
return;
}
}
}
throw new IllegalArgumentException("Field " + name + " must be a Positive Number");
}
@Override
public void validateField(String name, Object o) {
validateField(name, this.includeZero, o);
}
}
public static class ClusterMetricRegistryValidator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
SimpleTypeValidator.validateField(name, Map.class, o);
if (!((Map<?, ?>) o).containsKey("class")) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: class");
}
SimpleTypeValidator.validateField(name, String.class, ((Map<?, ?>) o).get("class"));
}
}
public static class MetricRegistryValidator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
SimpleTypeValidator.validateField(name, Map.class, o);
if (!((Map<?, ?>) o).containsKey("class")) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: class");
}
if (!((Map<?, ?>) o).containsKey("parallelism.hint")) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: parallelism.hint");
}
SimpleTypeValidator.validateField(name, String.class, ((Map<?, ?>) o).get("class"));
new IntegerValidator().validateField(name, ((Map<?, ?>) o).get("parallelism.hint"));
}
}
public static class MetricReportersValidator extends Validator {
private static final String NIMBUS = "nimbus";
private static final String SUPERVISOR = "supervisor";
private static final String WORKER = "worker";
private static final String CLASS = "class";
private static final String FILTER = "filter";
private static final String DAEMONS = "daemons";
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
SimpleTypeValidator.validateField(name, Map.class, o);
if (!((Map) o).containsKey(CLASS)) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: class");
}
if (!((Map) o).containsKey(DAEMONS)) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: daemons");
} else {
// daemons can only be 'nimbus', 'supervisor', or 'worker'
Object list = ((Map) o).get(DAEMONS);
if (!(list instanceof List)) {
throw new IllegalArgumentException("Field 'daemons' must be a non-null list.");
}
List daemonList = (List) list;
for (Object string : daemonList) {
if (string instanceof String
&& (string.equals(NIMBUS) || string.equals(SUPERVISOR) || string.equals(WORKER))) {
continue;
}
throw new IllegalArgumentException("Field 'daemons' must contain at least one of the following:"
+ " \"nimbus\", \"supervisor\", or \"worker\"");
}
}
if (((Map) o).containsKey(FILTER)) {
Map filterMap = (Map) ((Map) o).get(FILTER);
SimpleTypeValidator.validateField(CLASS, String.class, filterMap.get(CLASS));
}
SimpleTypeValidator.validateField(name, String.class, ((Map) o).get(CLASS));
}
}
public static class EventLoggerRegistryValidator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
SimpleTypeValidator.validateField(name, Map.class, o);
if (!((Map<?, ?>) o).containsKey("class")) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: class");
}
SimpleTypeValidator.validateField(name, String.class, ((Map<?, ?>) o).get("class"));
if (((Map<?, ?>) o).containsKey("arguments")) {
SimpleTypeValidator.validateField(name, Map.class, ((Map<?, ?>) o).get("arguments"));
}
}
}
public static class MapOfStringToMapOfStringToObjectValidator extends Validator {
@Override
public void validateField(String name, Object o) {
ConfigValidationUtils.NestableFieldValidator validator =
ConfigValidationUtils.mapFv(ConfigValidationUtils.fv(String.class, false),
ConfigValidationUtils.mapFv(String.class, Object.class, true), true);
validator.validateField(name, o);
}
}
public static class PacemakerAuthTypeValidator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
throw new IllegalArgumentException("Field " + name + " must be set.");
}
if (o instanceof String
&& (((String) o).equals("NONE") || ((String) o).equals("DIGEST")
|| ((String) o).equals("KERBEROS"))) {
return;
}
throw new IllegalArgumentException("Field " + name + " must be one of \"NONE\", \"DIGEST\", or \"KERBEROS\"");
}
}
public static class UserResourcePoolEntryValidator extends Validator {
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
SimpleTypeValidator.validateField(name, Map.class, o);
Map<?, ?> m = (Map<?, ?>) o;
if (!m.containsKey("cpu")) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: cpu");
}
if (!m.containsKey("memory")) {
throw new IllegalArgumentException("Field " + name + " must have map entry with key: memory");
}
SimpleTypeValidator.validateField(name, Number.class, m.get("cpu"));
SimpleTypeValidator.validateField(name, Number.class, m.get("memory"));
}
}
public static class ImplementsClassValidator extends Validator {
Class<?> classImplements;
public ImplementsClassValidator(Map<String, Object> params) {
this.classImplements = (Class<?>) params.get(ConfigValidationAnnotations.ValidatorParams.IMPLEMENTS_CLASS);
}
@Override
public void validateField(String name, Object o) {
if (o == null) {
return;
}
SimpleTypeValidator.validateField(name, String.class, o);
String className = (String) o;
try {
Class<?> objectClass = Class.forName(className);
if (!this.classImplements.isAssignableFrom(objectClass)) {
throw new IllegalArgumentException("Field " + name + " with value " + o
+ " does not implement " + this.classImplements.getName());
}
} catch (ClassNotFoundException e) {
//To support topologies of older version to run, we might have to loose the constraints so that
//the configs of older version can pass the validation.
if (className.startsWith("backtype.storm")) {
LOG.error("ClassNotFoundException: {}", className);
LOG.warn("Replace backtype.storm with org.apache.storm and try to validate again");
LOG.warn("We loosen some constraints here to support topologies of older version running on the current version");
validateField(name, className.replace("backtype.storm", "org.apache.storm"));
} else {
throw new RuntimeException("Failed to validate config " + name + " with value " + className, e);
}
}
}
}
/**
* Validates a list of a list of Strings.
*/
public static class ListOfListOfStringValidator extends Validator {
@Override
public void validateField(String name, Object o) throws IllegalArgumentException {
if (o == null) {
return;
}
if (o instanceof List) {
for (Object entry1 : (List) o) {
if (entry1 instanceof List) {
for (Object entry2 : (List) entry1) {
if (!(entry2 instanceof String)) {
throw new IllegalArgumentException(
"Field " + name + " must be an Iterable containing only List of List of Strings");
}
}
} else {
throw new IllegalArgumentException(
"Field " + name + " must be an Iterable containing only List of List of Strings");
}
}
} else {
throw new IllegalArgumentException(
"Field " + name + " must be an Iterable containing only List of List of Strings");
}
}
}
}