All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.ratis.conf.RaftProperties Maven / Gradle / Ivy

There is a newer version: 3.1.2
Show newest version
/*
 * 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.ratis.conf;

import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.ReflectionUtils;
import org.apache.ratis.util.SizeInBytes;
import org.apache.ratis.util.StringUtils;
import org.apache.ratis.util.TimeDuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

/**
 * Provides access to configuration parameters. The current implementation is a
 * simplified version of hadoop's Configuration.
 */
public class RaftProperties {
  private static final Logger LOG = LoggerFactory.getLogger(RaftProperties.class);

  private final ConcurrentMap properties = new ConcurrentHashMap<>();

  /** A new configuration. */
  public RaftProperties() {
  }

  /**
   * A new RaftProperties with the same settings cloned from another.
   *
   * @param other the RaftProperties from which to clone settings.
   */
  public RaftProperties(RaftProperties other) {
    this.properties.putAll(other.properties);
  }

  private static final int MAX_SUBST = 20;

  private static final int SUB_START_IDX = 0;
  private static final int SUB_END_IDX = SUB_START_IDX + 1;

  /**
   * This is a manual implementation of the following regex
   * "\\$\\{[^\\}\\$\u0020]+\\}".
   *
   * @param eval a string that may contain variables requiring expansion.
   * @return a 2-element int array res such that
   * eval.substring(res[0], res[1]) is "var" for the left-most occurrence of
   * ${var} in eval. If no variable is found -1, -1 is returned.
   */
  private static int[] findSubVariable(String eval) {
    int[] result = {-1, -1};

    int matchStart;
    int leftBrace;

    // scanning for a brace first because it's less frequent than $
    // that can occur in nested class names
    //
    match_loop:
    for (matchStart = 1, leftBrace = eval.indexOf('{', matchStart);
         // minimum left brace position (follows '$')
         leftBrace > 0
         // right brace of a smallest valid expression "${c}"
         && leftBrace + "{c".length() < eval.length();
         leftBrace = eval.indexOf('{', matchStart)) {
      int matchedLen = 0;
      if (eval.charAt(leftBrace - 1) == '$') {
        int subStart = leftBrace + 1; // after '{'
        for (int i = subStart; i < eval.length(); i++) {
          switch (eval.charAt(i)) {
            case '}':
              if (matchedLen > 0) { // match
                result[SUB_START_IDX] = subStart;
                result[SUB_END_IDX] = subStart + matchedLen;
                break match_loop;
              }
              // fall through to skip 1 char
            case ' ':
            case '$':
              matchStart = i + 1;
              continue match_loop;
            default:
              matchedLen++;
          }
        }
        // scanned from "${"  to the end of eval, and no reset via ' ', '$':
        //    no match!
        break;
      } else {
        // not a start of a variable
        //
        matchStart = leftBrace + 1;
      }
    }
    return result;
  }

  /**
   * Attempts to repeatedly expand the value {@code expr} by replacing the
   * left-most substring of the form "${var}" in the following precedence order
   * 
    *
  1. by the value of the environment variable "var" if defined
  2. *
  3. by the value of the Java system property "var" if defined
  4. *
  5. by the value of the configuration key "var" if defined
  6. *
* * If var is unbounded the current state of expansion "prefix${var}suffix" is * returned. * * If a cycle is detected: replacing var1 requires replacing var2 ... requires * replacing var1, i.e., the cycle is shorter than * {@link RaftProperties#MAX_SUBST} then the original expr is returned. * * @param expr the literal value of a config key * @return null if expr is null, otherwise the value resulting from expanding * expr using the algorithm above. * @throws IllegalArgumentException when more than * {@link RaftProperties#MAX_SUBST} replacements are required */ private String substituteVars(String expr) { if (expr == null) { return null; } String eval = expr; Set evalSet = null; for(int s = 0; s < MAX_SUBST; s++) { final int[] varBounds = findSubVariable(eval); if (varBounds[SUB_START_IDX] == -1) { return eval; } final String var = eval.substring(varBounds[SUB_START_IDX], varBounds[SUB_END_IDX]); String val = null; try { if (var.startsWith("env.") && 4 < var.length()) { String v = var.substring(4); int i = 0; for (; i < v.length(); i++) { char c = v.charAt(i); if (c == ':' && i < v.length() - 1 && v.charAt(i + 1) == '-') { val = getenv(v.substring(0, i)); if (val == null || val.length() == 0) { val = v.substring(i + 2); } break; } else if (c == '-') { val = getenv(v.substring(0, i)); if (val == null) { val = v.substring(i + 1); } break; } } if (i == v.length()) { val = getenv(v); } } else { val = getProperty(var); } } catch(SecurityException se) { LOG.warn("Unexpected SecurityException in Configuration", se); } if (val == null) { val = getRaw(var); } if (val == null) { return eval; // return literal ${var}: var is unbound } // prevent recursive resolution // final int dollar = varBounds[SUB_START_IDX] - "${".length(); final int afterRightBrace = varBounds[SUB_END_IDX] + "}".length(); final String refVar = eval.substring(dollar, afterRightBrace); if (evalSet == null) { evalSet = new HashSet<>(); } if (!evalSet.add(refVar)) { return expr; // return original expression if there is a loop } // substitute eval = eval.substring(0, dollar) + val + eval.substring(afterRightBrace); } throw new IllegalStateException("Variable substitution depth too large: " + MAX_SUBST + " " + expr); } String getenv(String name) { return System.getenv(name); } String getProperty(String key) { return System.getProperty(key); } /** * Get the value of the name property, null if * no such property exists. If the key is deprecated, it returns the value of * the first key which replaces the deprecated key and is not null. * * Values are processed for variable expansion * before being returned. * * @param name the property name, will be trimmed before get value. * @return the value of the name or its replacing property, * or null if no such property exists. */ public String get(String name) { return substituteVars(getRaw(name)); } /** * Get the value of the name property as a trimmed String, * null if no such property exists. * If the key is deprecated, it returns the value of * the first key which replaces the deprecated key and is not null *

* Values are processed for variable expansion * before being returned. *

* Underscores can be optionally removed. * * @param name the property name. * @param removeUnderscores should the underscores be removed? * * @return the value of the name or its replacing property, * or null if no such property exists. */ public String getTrimmed(String name, boolean removeUnderscores) { String value = get(name); if (null == value) { return null; } value = value.trim(); if (removeUnderscores) { value = value.replace("_", ""); } return value; } /** The same as getTrimmed(name, false). */ public String getTrimmed(String name) { return getTrimmed(name, false); } /** * Get the value of the name property, without doing * variable expansion.If the key is * deprecated, it returns the value of the first key which replaces * the deprecated key and is not null. * * @param name the property name. * @return the value of the name property or * its replacing property and null if no such property exists. */ public String getRaw(String name) { return properties.get(Objects.requireNonNull(name, "name == null").trim()); } /** * Set the value of the name property. If * name is deprecated, it also sets the value to * the keys that replace the deprecated key. Name will be trimmed before put * into configuration. * * @param name property name. * @param value property value. * @throws IllegalArgumentException when the value or name is null. */ public void set(String name, String value) { final String trimmed = Objects.requireNonNull(name, "name == null").trim(); Objects.requireNonNull(value, () -> "value == null for " + trimmed); properties.put(trimmed, value); } /** * Unset a previously set property. */ public void unset(String name) { properties.remove(name); } /** * Sets a property if it is currently unset. * @param name the property name * @param value the new value */ public synchronized void setIfUnset(String name, String value) { if (get(name) == null) { set(name, value); } } /** * Get the value of the name. If the key is deprecated, * it returns the value of the first key which replaces the deprecated key * and is not null. * If no such property exists, * then defaultValue is returned. * * @param name property name, will be trimmed before get value. * @param defaultValue default value. * @return property value, or defaultValue if the property * doesn't exist. */ public String get(String name, String defaultValue) { return substituteVars(properties.getOrDefault(name, defaultValue)); } /** * Get the value of the name property as an int. * * If no such property exists, the provided default value is returned, * or if the specified value is not a valid int, * then an error is thrown. * * @param name property name. * @param defaultValue default value. * @throws NumberFormatException when the value is invalid * @return property value as an int, * or defaultValue. */ public Integer getInt(String name, Integer defaultValue) { final String valueString = getTrimmed(name, true); if (valueString == null) { return defaultValue; } String hexString = getHexDigits(valueString); if (hexString != null) { return Integer.parseInt(hexString, 16); } return Integer.parseInt(valueString); } /** * Set the value of the name property to an int. * * @param name property name. * @param value int value of the property. */ public void setInt(String name, int value) { set(name, Integer.toString(value)); } /** * Get the value of the name property as a long. * If no such property exists, the provided default value is returned, * or if the specified value is not a valid long, * then an error is thrown. * * @param name property name. * @param defaultValue default value. * @throws NumberFormatException when the value is invalid * @return property value as a long, * or defaultValue. */ public Long getLong(String name, Long defaultValue) { final String valueString = getTrimmed(name, true); if (valueString == null) { return defaultValue; } String hexString = getHexDigits(valueString); if (hexString != null) { return Long.parseLong(hexString, 16); } return Long.parseLong(valueString); } /** @return property value; if it is not set, return the default value. */ public File getFile(String name, File defaultValue) { final String valueString = getTrimmed(name); return valueString == null? defaultValue: new File(valueString); } /** * Get the value of the name property as a list * of File. * The value of the property specifies a list of comma separated path names. * If no such property is specified, then defaultValue is * returned. * * @param name the property name. * @param defaultValue default value. * @return property value as a List of File, or defaultValue. */ public List getFiles(String name, List defaultValue) { String valueString = getRaw(name); if (null == valueString) { return defaultValue; } final String[] paths = StringUtils.getTrimmedStrings(get(name)); return Arrays.stream(paths).map(File::new).collect(Collectors.toList()); } public void setFile(String name, File value) { try { set(name, value.getCanonicalPath()); } catch (IOException e) { throw new IllegalArgumentException( "Failed to get canonical path from file " + value + " for " + name, e); } } public void setFiles(String name, List value) { String paths = value.stream().map(File::getAbsolutePath) .collect(Collectors.joining(",")); set(name, paths); } /** @return property value; if it is not set, return the default value. */ public SizeInBytes getSizeInBytes(String name, SizeInBytes defaultValue) { final String valueString = getTrimmed(name); return valueString == null? defaultValue: SizeInBytes.valueOf(valueString); } private String getHexDigits(String value) { boolean negative = false; String str = value; String hexString; if (value.startsWith("-")) { negative = true; str = value.substring(1); } if (str.startsWith("0x") || str.startsWith("0X")) { hexString = str.substring(2); if (negative) { hexString = "-" + hexString; } return hexString; } return null; } /** * Set the value of the name property to a long. * * @param name property name. * @param value long value of the property. */ public void setLong(String name, long value) { set(name, Long.toString(value)); } /** * Get the value of the name property as a double. * If no such property exists, the provided default value is returned, * or if the specified value is not a valid double, * then an error is thrown. * * @param name property name. * @param defaultValue default value. * @throws NumberFormatException when the value is invalid * @return property value as a double, * or defaultValue. */ public double getDouble(String name, double defaultValue) { final String valueString = getTrimmed(name, true); if (valueString == null) { return defaultValue; } return Double.parseDouble(valueString); } /** * Set the value of the name property to a double. * * @param name property name. * @param value property value. */ public void setDouble(String name, double value) { set(name,Double.toString(value)); } /** * Get the value of the name property as a boolean. * If no such property is specified, or if the specified value is not a valid * boolean, then defaultValue is returned. * * @param name property name. * @param defaultValue default value. * @return property value as a boolean, * or defaultValue. */ public Boolean getBoolean(String name, Boolean defaultValue) { String valueString = getTrimmed(name); return StringUtils.string2boolean(valueString, defaultValue); } /** * Set the value of the name property to a boolean. * * @param name property name. * @param value boolean value of the property. */ public void setBoolean(String name, boolean value) { set(name, Boolean.toString(value)); } /** * Set the value of the name property to the given type. This * is equivalent to set(<name>, value.toString()). * @param name property name * @param value new value */ public > void setEnum(String name, T value) { set(name, value.toString()); } /** * Get the enum value mapped the given property name. * * @param name Property name * @param enumClass the {@link Class} of the enum * @param defaultValue Value returned if no mapping exists */ public > T getEnum(String name, Class enumClass, T defaultValue) { final String val = getTrimmed(name); return null == val? defaultValue : Enum.valueOf(enumClass, val); } /** The same as getEnum(name, defaultValue.getDeclaringClass(), defaultValue). */ public > T getEnum(String name, T defaultValue) { Objects.requireNonNull(defaultValue, "defaultValue == null"); return getEnum(name, defaultValue.getDeclaringClass(), defaultValue); } /** * Set the value of name to the given time duration. This * is equivalent to set(<name>, value + <time suffix>). * @param name Property name * @param value Time duration */ public void setTimeDuration(String name, TimeDuration value) { set(name, value.toString()); } /** * Return time duration in the given time unit. Valid units are encoded in * properties as suffixes: nanoseconds (ns), microseconds (us), milliseconds * (ms), seconds (s), minutes (m), hours (h), and days (d). * @param name Property name * @param defaultValue Value returned if no mapping exists. * @throws NumberFormatException If the property stripped of its unit is not * a number */ public TimeDuration getTimeDuration( String name, TimeDuration defaultValue, TimeUnit defaultUnit) { final String value = getTrimmed(name); if (null == value) { return defaultValue; } try { return TimeDuration.valueOf(value, defaultUnit); } catch(NumberFormatException e) { throw new IllegalArgumentException("Failed to parse " + name + " = " + value, e); } } public BiFunction getTimeDuration(TimeUnit defaultUnit) { return (key, defaultValue) -> getTimeDuration(key, defaultValue, defaultUnit); } /** * Get the value of the name property as a Class. * If no such property is specified, then defaultValue is * returned. * * @param name the class name. * @param defaultValue default value. * @return property value as a Class, * or defaultValue. */ public Class getClass(String name, Class defaultValue) { String valueString = getTrimmed(name); if (valueString == null) { return defaultValue; } try { return ReflectionUtils.getClassByName(valueString); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * Get the value of the name property as a Class * implementing the interface specified by xface. * * If no such property is specified, then defaultValue is * returned. * * An exception is thrown if the returned class does not implement the named * interface. * * @param name the class name. * @param defaultValue default value. * @param xface the interface implemented by the named class. * @return property value as a Class, * or defaultValue. */ public Class getClass( String name, Class defaultValue, Class xface) { try { Class theClass = getClass(name, defaultValue); if (theClass != null && !xface.isAssignableFrom(theClass)) { throw new RuntimeException(theClass+" not "+xface.getName()); } else if (theClass != null) { return theClass.asSubclass(xface); } else { return null; } } catch (Exception e) { throw new RuntimeException(e); } } /** * Set the value of the name property to the name of a * theClass implementing the given interface xface. * * An exception is thrown if theClass does not implement the * interface xface. * * @param name property name. * @param theClass property value. * @param xface the interface implemented by the named class. */ public void setClass(String name, Class theClass, Class xface) { if (!xface.isAssignableFrom(theClass)) { throw new RuntimeException(theClass+" not "+xface.getName()); } set(name, theClass.getName()); } /** @return number of keys in the properties. */ public int size() { return properties.size(); } /** * Clears all keys from the configuration. */ public void clear() { properties.clear(); } /** @return the property entry set. */ Set> entrySet() { return properties.entrySet(); } @Override public String toString() { return JavaUtils.getClassSimpleName(getClass()) + ":" + size(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy