LCOV - code coverage report
Current view: top level - server/config - ConfigUtil.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 151 168 89.9 %
Date: 2022-11-19 15:00:39 Functions: 20 21 95.2 %

          Line data    Source code
       1             : // Copyright (C) 2009 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.config;
      16             : 
      17             : import static java.util.Objects.requireNonNull;
      18             : 
      19             : import com.google.common.flogger.FluentLogger;
      20             : import java.lang.reflect.Field;
      21             : import java.lang.reflect.InvocationTargetException;
      22             : import java.lang.reflect.Modifier;
      23             : import java.util.ArrayList;
      24             : import java.util.Collection;
      25             : import java.util.List;
      26             : import java.util.Map;
      27             : import java.util.concurrent.TimeUnit;
      28             : import java.util.regex.Matcher;
      29             : import java.util.regex.Pattern;
      30             : import org.eclipse.jgit.errors.ConfigInvalidException;
      31             : import org.eclipse.jgit.lib.Config;
      32             : 
      33             : public class ConfigUtil {
      34         153 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      35             : 
      36             :   @SuppressWarnings("unchecked")
      37             :   private static <T> T[] allValuesOf(T defaultValue) {
      38             :     try {
      39           5 :       return (T[]) defaultValue.getClass().getMethod("values").invoke(null);
      40           0 :     } catch (IllegalArgumentException
      41             :         | NoSuchMethodException
      42             :         | InvocationTargetException
      43             :         | IllegalAccessException
      44             :         | SecurityException e) {
      45           0 :       throw new IllegalArgumentException("Cannot obtain enumeration values", e);
      46             :     }
      47             :   }
      48             : 
      49             :   /**
      50             :    * Parse a Java enumeration from the configuration.
      51             :    *
      52             :    * @param <T> type of the enumeration object.
      53             :    * @param section section the key is in.
      54             :    * @param subsection subsection the key is in, or null if not in a subsection.
      55             :    * @param setting name of the setting to read.
      56             :    * @param valueString string value from git Config
      57             :    * @param all all possible values in the enumeration which should be recognized. This should be
      58             :    *     {@code EnumType.values()}.
      59             :    * @return the selected enumeration value, or {@code defaultValue}.
      60             :    */
      61             :   private static <T extends Enum<?>> T getEnum(
      62             :       final String section,
      63             :       final String subsection,
      64             :       final String setting,
      65             :       String valueString,
      66             :       final T[] all) {
      67             : 
      68           3 :     String n = valueString.replace(' ', '_').replace('-', '_');
      69           3 :     for (T e : all) {
      70           3 :       if (e.name().equalsIgnoreCase(n)) {
      71           3 :         return e;
      72             :       }
      73             :     }
      74             : 
      75           1 :     final StringBuilder r = new StringBuilder();
      76           1 :     r.append("Value \"");
      77           1 :     r.append(valueString);
      78           1 :     r.append("\" not recognized in ");
      79           1 :     r.append(section);
      80           1 :     if (subsection != null) {
      81           0 :       r.append(".");
      82           0 :       r.append(subsection);
      83             :     }
      84           1 :     r.append(".");
      85           1 :     r.append(setting);
      86           1 :     r.append("; supported values are: ");
      87           1 :     for (T e : all) {
      88           1 :       r.append(e.name());
      89           1 :       r.append(" ");
      90             :     }
      91             : 
      92           1 :     throw new IllegalArgumentException(r.toString().trim());
      93             :   }
      94             : 
      95             :   /**
      96             :    * Parse a Java enumeration list from the configuration.
      97             :    *
      98             :    * @param <T> type of the enumeration object.
      99             :    * @param config the configuration file to read.
     100             :    * @param section section the key is in.
     101             :    * @param subsection subsection the key is in, or null if not in a subsection.
     102             :    * @param setting name of the setting to read.
     103             :    * @param defaultValue default value to return if the setting was not set. Must not be null as the
     104             :    *     enumeration values are derived from this.
     105             :    * @return the selected enumeration values list, or {@code defaultValue}.
     106             :    */
     107             :   public static <T extends Enum<?>> List<T> getEnumList(
     108             :       final Config config,
     109             :       final String section,
     110             :       final String subsection,
     111             :       final String setting,
     112             :       final T defaultValue) {
     113           5 :     final T[] all = allValuesOf(defaultValue);
     114           5 :     return getEnumList(config, section, subsection, setting, all, defaultValue);
     115             :   }
     116             : 
     117             :   /**
     118             :    * Parse a Java enumeration list from the configuration.
     119             :    *
     120             :    * @param <T> type of the enumeration object.
     121             :    * @param config the configuration file to read.
     122             :    * @param section section the key is in.
     123             :    * @param subsection subsection the key is in, or null if not in a subsection.
     124             :    * @param setting name of the setting to read.
     125             :    * @param all all possible values in the enumeration which should be recognized. This should be
     126             :    *     {@code EnumType.values()}.
     127             :    * @param defaultValue default value to return if the setting was not set. This value may be null.
     128             :    * @return the selected enumeration values list, or {@code defaultValue}.
     129             :    */
     130             :   public static <T extends Enum<?>> List<T> getEnumList(
     131             :       final Config config,
     132             :       final String section,
     133             :       final String subsection,
     134             :       final String setting,
     135             :       final T[] all,
     136             :       final T defaultValue) {
     137         149 :     final List<T> list = new ArrayList<>();
     138         149 :     final String[] values = config.getStringList(section, subsection, setting);
     139         149 :     if (values.length == 0) {
     140         149 :       list.add(defaultValue);
     141             :     } else {
     142           3 :       for (String string : values) {
     143           3 :         if (string != null) {
     144             :           try {
     145           3 :             list.add(getEnum(section, subsection, setting, string, all));
     146           1 :           } catch (IllegalArgumentException ex) {
     147             :             // It's better to ignore a wrongly configured enum, rather than fail to load Gerrit.
     148           1 :             logger.atWarning().log("%s", ex.getMessage());
     149           3 :           }
     150             :         }
     151             :       }
     152             :     }
     153         149 :     return list;
     154             :   }
     155             : 
     156             :   /**
     157             :    * Parse a numerical time unit, such as "1 minute", from the configuration.
     158             :    *
     159             :    * @param config the configuration file to read.
     160             :    * @param section section the key is in.
     161             :    * @param subsection subsection the key is in, or null if not in a subsection.
     162             :    * @param setting name of the setting to read.
     163             :    * @param defaultValue default value to return if no value was set in the configuration file.
     164             :    * @param wantUnit the units of {@code defaultValue} and the return value, as well as the units to
     165             :    *     assume if the value does not contain an indication of the units.
     166             :    * @return the setting, or {@code defaultValue} if not set, expressed in {@code units}.
     167             :    */
     168             :   public static long getTimeUnit(
     169             :       final Config config,
     170             :       final String section,
     171             :       final String subsection,
     172             :       final String setting,
     173             :       final long defaultValue,
     174             :       final TimeUnit wantUnit) {
     175         153 :     final String valueString = config.getString(section, subsection, setting);
     176         153 :     if (valueString == null) {
     177         152 :       return defaultValue;
     178             :     }
     179             : 
     180         140 :     String s = valueString.trim();
     181         140 :     if (s.length() == 0) {
     182           0 :       return defaultValue;
     183             :     }
     184             : 
     185         140 :     if (s.startsWith("-") /* negative */) {
     186           2 :       throw notTimeUnit(section, subsection, setting, valueString);
     187             :     }
     188             : 
     189             :     try {
     190         140 :       return getTimeUnit(s, defaultValue, wantUnit);
     191           1 :     } catch (IllegalArgumentException notTime) {
     192           1 :       throw notTimeUnit(section, subsection, setting, valueString, notTime);
     193             :     }
     194             :   }
     195             : 
     196             :   /**
     197             :    * Parse a numerical time unit, such as "1 minute", from a string.
     198             :    *
     199             :    * @param valueString the string to parse.
     200             :    * @param defaultValue default value to return if no value was set in the configuration file.
     201             :    * @param wantUnit the units of {@code defaultValue} and the return value, as well as the units to
     202             :    *     assume if the value does not contain an indication of the units.
     203             :    * @return the setting, or {@code defaultValue} if not set, expressed in {@code units}.
     204             :    */
     205             :   public static long getTimeUnit(String valueString, long defaultValue, TimeUnit wantUnit) {
     206         144 :     Matcher m = Pattern.compile("^(0|[1-9][0-9]*)\\s*(.*)$").matcher(valueString);
     207         144 :     if (!m.matches()) {
     208           3 :       return defaultValue;
     209             :     }
     210             : 
     211         144 :     String digits = m.group(1);
     212         144 :     String unitName = m.group(2).trim();
     213             : 
     214             :     TimeUnit inputUnit;
     215             :     int inputMul;
     216             : 
     217         144 :     if ("".equals(unitName)) {
     218         139 :       inputUnit = wantUnit;
     219         139 :       inputMul = 1;
     220             : 
     221          11 :     } else if (match(unitName, "ms", "milliseconds")) {
     222           3 :       inputUnit = TimeUnit.MILLISECONDS;
     223           3 :       inputMul = 1;
     224             : 
     225          11 :     } else if (match(unitName, "s", "sec", "second", "seconds")) {
     226           3 :       inputUnit = TimeUnit.SECONDS;
     227           3 :       inputMul = 1;
     228             : 
     229          10 :     } else if (match(unitName, "m", "min", "minute", "minutes")) {
     230          10 :       inputUnit = TimeUnit.MINUTES;
     231          10 :       inputMul = 1;
     232             : 
     233           8 :     } else if (match(unitName, "h", "hr", "hour", "hours")) {
     234           1 :       inputUnit = TimeUnit.HOURS;
     235           1 :       inputMul = 1;
     236             : 
     237           8 :     } else if (match(unitName, "d", "day", "days")) {
     238           5 :       inputUnit = TimeUnit.DAYS;
     239           5 :       inputMul = 1;
     240             : 
     241           8 :     } else if (match(unitName, "w", "week", "weeks")) {
     242           6 :       inputUnit = TimeUnit.DAYS;
     243           6 :       inputMul = 7;
     244             : 
     245           3 :     } else if (match(unitName, "mon", "month", "months")) {
     246           1 :       inputUnit = TimeUnit.DAYS;
     247           1 :       inputMul = 30;
     248             : 
     249           3 :     } else if (match(unitName, "y", "year", "years")) {
     250           1 :       inputUnit = TimeUnit.DAYS;
     251           1 :       inputMul = 365;
     252             : 
     253             :     } else {
     254           3 :       throw notTimeUnit(valueString);
     255             :     }
     256             : 
     257             :     try {
     258         144 :       return wantUnit.convert(Long.parseLong(digits) * inputMul, inputUnit);
     259           0 :     } catch (NumberFormatException nfe) {
     260           0 :       throw notTimeUnit(valueString, nfe);
     261             :     }
     262             :   }
     263             : 
     264             :   public static String getRequired(Config cfg, String section, String name) {
     265           0 :     final String v = cfg.getString(section, null, name);
     266           0 :     if (v == null || "".equals(v)) {
     267           0 :       throw new IllegalArgumentException("No " + section + "." + name + " configured");
     268             :     }
     269           0 :     return v;
     270             :   }
     271             : 
     272             :   /**
     273             :    * Store section by inspecting Java class attributes.
     274             :    *
     275             :    * <p>Optimize the storage by unsetting a variable if it is being set to default value by the
     276             :    * server.
     277             :    *
     278             :    * <p>Fields marked with final or transient modifiers are skipped.
     279             :    *
     280             :    * @param cfg config in which the values should be stored
     281             :    * @param section section
     282             :    * @param sub subsection
     283             :    * @param s instance of class with config values
     284             :    * @param defaults instance of class with default values
     285             :    */
     286             :   public static <T> void storeSection(Config cfg, String section, String sub, T s, T defaults)
     287             :       throws ConfigInvalidException {
     288             :     try {
     289          10 :       for (Field f : s.getClass().getDeclaredFields()) {
     290          10 :         if (skipField(f)) {
     291          10 :           continue;
     292             :         }
     293          10 :         Class<?> t = f.getType();
     294          10 :         String n = f.getName();
     295          10 :         f.setAccessible(true);
     296          10 :         Object c = f.get(s);
     297          10 :         Object d = f.get(defaults);
     298          10 :         if (!isString(t) && !isCollectionOrMap(t)) {
     299          10 :           requireNonNull(d, "Default cannot be null for: " + n);
     300             :         }
     301          10 :         if (c == null || c.equals(d)) {
     302          10 :           cfg.unset(section, sub, n);
     303             :         } else {
     304          10 :           if (isString(t)) {
     305           2 :             cfg.setString(section, sub, n, (String) c);
     306          10 :           } else if (isInteger(t)) {
     307           3 :             cfg.setInt(section, sub, n, (Integer) c);
     308          10 :           } else if (isLong(t)) {
     309           1 :             cfg.setLong(section, sub, n, (Long) c);
     310          10 :           } else if (isBoolean(t)) {
     311           8 :             cfg.setBoolean(section, sub, n, (Boolean) c);
     312           8 :           } else if (t.isEnum()) {
     313           5 :             cfg.setEnum(section, sub, n, (Enum<?>) c);
     314           6 :           } else if (isCollectionOrMap(t)) {
     315             :             // TODO(davido): accept closure passed in from caller
     316           6 :             continue;
     317             :           } else {
     318           0 :             throw new ConfigInvalidException("type is unknown: " + t.getName());
     319             :           }
     320             :         }
     321             :       }
     322           0 :     } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
     323           0 :       throw new ConfigInvalidException("cannot save values", e);
     324          10 :     }
     325          10 :   }
     326             : 
     327             :   /**
     328             :    * Load section by inspecting Java class attributes.
     329             :    *
     330             :    * <p>Config values are stored optimized: no default values are stored. The loading is performed
     331             :    * eagerly: all values are set.
     332             :    *
     333             :    * <p>Fields marked with final or transient modifiers are skipped.
     334             :    *
     335             :    * @param cfg config from which the values are loaded
     336             :    * @param section section
     337             :    * @param sub subsection
     338             :    * @param s instance of class in which the values are set
     339             :    * @param defaults instance of class with default values
     340             :    * @param i instance to merge during the load. When present, the boolean fields are not nullified
     341             :    *     when their values are false
     342             :    * @return loaded instance
     343             :    */
     344             :   public static <T> T loadSection(Config cfg, String section, String sub, T s, T defaults, T i)
     345             :       throws ConfigInvalidException {
     346             :     try {
     347         151 :       for (Field f : s.getClass().getDeclaredFields()) {
     348         151 :         if (skipField(f)) {
     349         151 :           continue;
     350             :         }
     351         151 :         Class<?> t = f.getType();
     352         151 :         String n = f.getName();
     353         151 :         f.setAccessible(true);
     354         151 :         Object d = f.get(defaults);
     355         151 :         if (!isString(t) && !isCollectionOrMap(t)) {
     356         151 :           requireNonNull(d, "Default cannot be null for: " + n);
     357             :         }
     358         151 :         if (isString(t)) {
     359         151 :           String v = cfg.getString(section, sub, n);
     360         151 :           if (v == null) {
     361         151 :             v = (String) d;
     362             :           }
     363         151 :           f.set(s, v);
     364         151 :         } else if (isInteger(t)) {
     365         151 :           f.set(s, cfg.getInt(section, sub, n, (Integer) d));
     366         151 :         } else if (isLong(t)) {
     367           1 :           f.set(s, cfg.getLong(section, sub, n, (Long) d));
     368         151 :         } else if (isBoolean(t)) {
     369         151 :           boolean b = cfg.getBoolean(section, sub, n, (Boolean) d);
     370         151 :           if (b || i != null) {
     371         151 :             f.set(s, b);
     372             :           }
     373         151 :         } else if (t.isEnum()) {
     374         151 :           f.set(s, cfg.getEnum(section, sub, n, (Enum<?>) d));
     375         151 :         } else if (isCollectionOrMap(t)) {
     376             :           // TODO(davido): accept closure passed in from caller
     377         151 :           continue;
     378             :         } else {
     379           0 :           throw new ConfigInvalidException("type is unknown: " + t.getName());
     380             :         }
     381         151 :         if (i != null) {
     382           9 :           Object o = f.get(i);
     383           9 :           if (o != null) {
     384           9 :             f.set(s, o);
     385             :           }
     386             :         }
     387             :       }
     388           0 :     } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
     389           0 :       throw new ConfigInvalidException("cannot load values", e);
     390         151 :     }
     391         151 :     return s;
     392             :   }
     393             : 
     394             :   public static boolean skipField(Field field) {
     395         151 :     int modifiers = field.getModifiers();
     396         151 :     return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
     397             :   }
     398             : 
     399             :   private static boolean isCollectionOrMap(Class<?> t) {
     400         151 :     return Collection.class.isAssignableFrom(t) || Map.class.isAssignableFrom(t);
     401             :   }
     402             : 
     403             :   private static boolean isString(Class<?> t) {
     404         151 :     return String.class == t;
     405             :   }
     406             : 
     407             :   private static boolean isBoolean(Class<?> t) {
     408         151 :     return Boolean.class == t || boolean.class == t;
     409             :   }
     410             : 
     411             :   private static boolean isLong(Class<?> t) {
     412         151 :     return Long.class == t || long.class == t;
     413             :   }
     414             : 
     415             :   private static boolean isInteger(Class<?> t) {
     416         151 :     return Integer.class == t || int.class == t;
     417             :   }
     418             : 
     419             :   private static boolean match(String a, String... cases) {
     420          11 :     for (String b : cases) {
     421          11 :       if (b != null && b.equalsIgnoreCase(a)) {
     422          11 :         return true;
     423             :       }
     424             :     }
     425          11 :     return false;
     426             :   }
     427             : 
     428             :   private static IllegalArgumentException notTimeUnit(
     429             :       String section, String subsection, String setting, String valueString, Throwable why) {
     430           1 :     return notTimeUnit(
     431             :         section
     432           1 :             + (subsection != null ? "." + subsection : "")
     433             :             + "."
     434             :             + setting
     435             :             + " = "
     436             :             + valueString,
     437             :         why);
     438             :   }
     439             : 
     440             :   private static IllegalArgumentException notTimeUnit(
     441             :       String section, String subsection, String setting, String valueString) {
     442           2 :     return notTimeUnit(
     443             :         section
     444           2 :             + (subsection != null ? "." + subsection : "")
     445             :             + "."
     446             :             + setting
     447             :             + " = "
     448             :             + valueString);
     449             :   }
     450             : 
     451             :   private static IllegalArgumentException notTimeUnit(String val) {
     452           4 :     return new IllegalArgumentException("Invalid time unit value: " + val);
     453             :   }
     454             : 
     455             :   private static IllegalArgumentException notTimeUnit(String val, Throwable why) {
     456           1 :     return new IllegalArgumentException("Invalid time unit value: " + val, why);
     457             :   }
     458             : 
     459             :   private ConfigUtil() {}
     460             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750