001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.conf;
019
020import java.util.Map;
021import java.util.concurrent.ConcurrentHashMap;
022import java.util.function.Predicate;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.yetus.audience.InterfaceAudience;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * Utility class for basic validation of configuration values.
030 */
031@InterfaceAudience.Private
032public final class ConfigKey {
033  private static final Logger LOG = LoggerFactory.getLogger(ConfigKey.class);
034
035  private ConfigKey() {
036  }
037
038  @FunctionalInterface
039  private interface Validator {
040    void validate(Configuration conf);
041  }
042
043  // Map of configuration keys to validators
044  private static final Map<String, Validator> validators = new ConcurrentHashMap<>();
045
046  /**
047   * Registers the configuration key that expects an integer.
048   * @param key        configuration key
049   * @param predicates additional predicates to validate the value
050   * @return the key
051   */
052  @SafeVarargs
053  public static String INT(String key, Predicate<Integer>... predicates) {
054    return register(key,
055      conf -> validateNumeric(key, "an integer", () -> conf.getInt(key, 0), predicates));
056  }
057
058  /**
059   * Registers the configuration key that expects a long.
060   * @param key        configuration key
061   * @param predicates additional predicates to validate the value
062   * @return the key
063   */
064  @SafeVarargs
065  public static String LONG(String key, Predicate<Long>... predicates) {
066    return register(key,
067      conf -> validateNumeric(key, "a long", () -> conf.getLong(key, 0), predicates));
068  }
069
070  /**
071   * Registers the configuration key that expects a float.
072   * @param key        configuration key
073   * @param predicates additional predicates to validate the value
074   * @return the key
075   */
076  @SafeVarargs
077  public static String FLOAT(String key, Predicate<Float>... predicates) {
078    return register(key,
079      conf -> validateNumeric(key, "a float", () -> conf.getFloat(key, 0), predicates));
080  }
081
082  /**
083   * Registers the configuration key that expects a double.
084   * @param key        configuration key
085   * @param predicates additional predicates to validate the value
086   * @return the key
087   */
088  @SafeVarargs
089  public static String DOUBLE(String key, Predicate<Double>... predicates) {
090    return register(key,
091      conf -> validateNumeric(key, "a double", () -> conf.getDouble(key, 0), predicates));
092  }
093
094  /**
095   * Registers the configuration key that expects a class.
096   * @param key      configuration key
097   * @param expected the expected class or interface the value should implement
098   * @return the key
099   */
100  public static <T> String CLASS(String key, Class<T> expected) {
101    return register(key, conf -> {
102      String value = conf.get(key);
103      try {
104        if (!expected.isAssignableFrom(Class.forName(value))) {
105          throw new IllegalArgumentException(
106            String.format("%s ('%s') is not compatible to %s.", value, key, expected.getName()));
107        }
108      } catch (ClassNotFoundException e) {
109        throw new IllegalArgumentException(String.format("'%s' must be a class.", key), e);
110      }
111    });
112  }
113
114  /**
115   * Registers the configuration key that expects a boolean value. We actually don't register a
116   * validator, because {@link Configuration#getBoolean} doesn't throw an exception even if the
117   * value is not a boolean. So this is only for documentation purposes.
118   * @param key configuration key
119   * @return the key
120   */
121  public static String BOOLEAN(String key) {
122    return key;
123  }
124
125  /**
126   * Validates the configuration.
127   * @param conf a configuration to validate
128   */
129  public static void validate(Configuration conf) {
130    conf.iterator().forEachRemaining(entry -> {
131      Validator validator = validators.get(entry.getKey());
132      if (validator != null) {
133        validator.validate(conf);
134      }
135    });
136  }
137
138  @FunctionalInterface
139  private interface NumberGetter<T> {
140    T get() throws NumberFormatException;
141  }
142
143  private static <T> void validateNumeric(String name, String expected, NumberGetter<T> getter,
144    Predicate<T>... predicates) {
145    try {
146      T value = getter.get();
147      for (Predicate<T> predicate : predicates) {
148        if (!predicate.test(value)) {
149          throw new IllegalArgumentException("Invalid value for '" + name + "': " + value + ".");
150        }
151      }
152    } catch (NumberFormatException e) {
153      throw new IllegalArgumentException(String.format("'%s' must be %s.", name, expected), e);
154    }
155  }
156
157  private static String register(String key, Validator validator) {
158    LOG.debug("Registering config validator for {}", key);
159    validators.put(key, validator);
160    return key;
161  }
162}