| /* |
| * 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.accumulo.core.conf; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Objects; |
| import java.util.TreeMap; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Predicate; |
| |
| import org.apache.accumulo.core.Constants; |
| import org.apache.accumulo.core.client.AccumuloException; |
| import org.apache.accumulo.core.client.Connector; |
| import org.apache.accumulo.core.client.TableNotFoundException; |
| import org.apache.accumulo.core.client.impl.Tables; |
| import org.apache.accumulo.core.conf.PropertyType.PortRange; |
| import org.apache.accumulo.core.util.Pair; |
| import org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * A configuration object. |
| */ |
| public abstract class AccumuloConfiguration implements Iterable<Entry<String,String>> { |
| |
| /** |
| * A filter that accepts properties whose keys are an exact match. |
| */ |
| public static class MatchFilter implements Predicate<String> { |
| |
| private String match; |
| |
| /** |
| * Creates a new filter. |
| * |
| * @param match |
| * prefix of property keys to accept |
| */ |
| public MatchFilter(String match) { |
| this.match = match; |
| } |
| |
| @Override |
| public boolean test(String key) { |
| return Objects.equals(match, key); |
| } |
| } |
| |
| /** |
| * A filter that accepts properties whose keys begin with a prefix. |
| */ |
| public static class PrefixFilter implements Predicate<String> { |
| |
| private String prefix; |
| |
| /** |
| * Creates a new filter. |
| * |
| * @param prefix |
| * prefix of property keys to accept |
| */ |
| public PrefixFilter(String prefix) { |
| this.prefix = prefix; |
| } |
| |
| @Override |
| public boolean test(String key) { |
| return key.startsWith(prefix); |
| } |
| } |
| |
| private static final Logger log = LoggerFactory.getLogger(AccumuloConfiguration.class); |
| |
| /** |
| * Gets a property value from this configuration. |
| * |
| * <p> |
| * Note: this is inefficient, but convenient on occasion. For retrieving multiple properties, use {@link #getProperties(Map, Predicate)} with a custom filter. |
| * |
| * @param property |
| * property to get |
| * @return property value |
| */ |
| public String get(String property) { |
| Map<String,String> propMap = new HashMap<>(1); |
| getProperties(propMap, new MatchFilter(property)); |
| return propMap.get(property); |
| } |
| |
| /** |
| * Gets a property value from this configuration. |
| * |
| * @param property |
| * property to get |
| * @return property value |
| */ |
| public abstract String get(Property property); |
| |
| /** |
| * Returns property key/value pairs in this configuration. The pairs include those defined in this configuration which pass the given filter, and those |
| * supplied from the parent configuration which are not included from here. |
| * |
| * @param props |
| * properties object to populate |
| * @param filter |
| * filter for accepting properties from this configuration |
| */ |
| public abstract void getProperties(Map<String,String> props, Predicate<String> filter); |
| |
| /** |
| * Returns an iterator over property key/value pairs in this configuration. Some implementations may elect to omit properties. |
| * |
| * @return iterator over properties |
| */ |
| @Override |
| public Iterator<Entry<String,String>> iterator() { |
| Predicate<String> all = x -> true; |
| TreeMap<String,String> entries = new TreeMap<>(); |
| getProperties(entries, all); |
| return entries.entrySet().iterator(); |
| } |
| |
| private void checkType(Property property, PropertyType type) { |
| if (!property.getType().equals(type)) { |
| String msg = "Configuration method intended for type " + type + " called with a " + property.getType() + " argument (" + property.getKey() + ")"; |
| IllegalArgumentException err = new IllegalArgumentException(msg); |
| log.error(msg, err); |
| throw err; |
| } |
| } |
| |
| /** |
| * Gets all properties under the given prefix in this configuration. |
| * |
| * @param property |
| * prefix property, must be of type PropertyType.PREFIX |
| * @return a map of property keys to values |
| * @throws IllegalArgumentException |
| * if property is not a prefix |
| */ |
| public Map<String,String> getAllPropertiesWithPrefix(Property property) { |
| checkType(property, PropertyType.PREFIX); |
| |
| Map<String,String> propMap = new HashMap<>(); |
| getProperties(propMap, new PrefixFilter(property.getKey())); |
| return propMap; |
| } |
| |
| /** |
| * Gets a property of type {@link PropertyType#MEMORY}, interpreting the value properly. |
| * |
| * @param property |
| * property to get |
| * @return property value |
| * @throws IllegalArgumentException |
| * if the property is of the wrong type |
| * @see #getMemoryInBytes(String) |
| */ |
| public long getMemoryInBytes(Property property) { |
| checkType(property, PropertyType.MEMORY); |
| |
| String memString = get(property); |
| return getMemoryInBytes(memString); |
| } |
| |
| /** |
| * Interprets a string specifying a memory size. A memory size is specified as a long integer followed by an optional B (bytes), K (KB), M (MB), or G (GB). |
| * |
| * @param str |
| * string value |
| * @return interpreted memory size |
| */ |
| static public long getMemoryInBytes(String str) { |
| char lastChar = str.charAt(str.length() - 1); |
| |
| if (lastChar == 'b') { |
| log.warn("The 'b' in " + str + " is being considered as bytes. " + "Setting memory by bits is not supported"); |
| } |
| try { |
| int multiplier; |
| switch (Character.toUpperCase(lastChar)) { |
| case 'G': |
| multiplier = 30; |
| break; |
| case 'M': |
| multiplier = 20; |
| break; |
| case 'K': |
| multiplier = 10; |
| break; |
| case 'B': |
| multiplier = 0; |
| break; |
| default: |
| return Long.parseLong(str); |
| } |
| return Long.parseLong(str.substring(0, str.length() - 1)) << multiplier; |
| } catch (Exception ex) { |
| throw new IllegalArgumentException("The value '" + str + "' is not a valid memory setting. A valid value would a number " |
| + "possibily followed by an optional 'G', 'M', 'K', or 'B'."); |
| } |
| } |
| |
| /** |
| * Gets a property of type {@link PropertyType#TIMEDURATION}, interpreting the value properly. |
| * |
| * @param property |
| * property to get |
| * @return property value |
| * @throws IllegalArgumentException |
| * if the property is of the wrong type |
| * @see #getTimeInMillis(String) |
| */ |
| public long getTimeInMillis(Property property) { |
| checkType(property, PropertyType.TIMEDURATION); |
| |
| return getTimeInMillis(get(property)); |
| } |
| |
| /** |
| * Interprets a string specifying a time duration. A time duration is specified as a long integer followed by an optional d (days), h (hours), m (minutes), s |
| * (seconds), or ms (milliseconds). A value without a unit is interpreted as seconds. |
| * |
| * @param str |
| * string value |
| * @return interpreted time duration in milliseconds |
| */ |
| public static long getTimeInMillis(String str) { |
| TimeUnit timeUnit; |
| int unitsLen = 1; |
| switch (str.charAt(str.length() - 1)) { |
| case 'd': |
| timeUnit = TimeUnit.DAYS; |
| break; |
| case 'h': |
| timeUnit = TimeUnit.HOURS; |
| break; |
| case 'm': |
| timeUnit = TimeUnit.MINUTES; |
| break; |
| case 's': |
| timeUnit = TimeUnit.SECONDS; |
| if (str.endsWith("ms")) { |
| timeUnit = TimeUnit.MILLISECONDS; |
| unitsLen = 2; |
| } |
| break; |
| default: |
| timeUnit = TimeUnit.SECONDS; |
| unitsLen = 0; |
| break; |
| } |
| return timeUnit.toMillis(Long.parseLong(str.substring(0, str.length() - unitsLen))); |
| } |
| |
| /** |
| * Gets a property of type {@link PropertyType#BOOLEAN}, interpreting the value properly (using <code>Boolean.parseBoolean()</code>). |
| * |
| * @param property |
| * property to get |
| * @return property value |
| * @throws IllegalArgumentException |
| * if the property is of the wrong type |
| */ |
| public boolean getBoolean(Property property) { |
| checkType(property, PropertyType.BOOLEAN); |
| return Boolean.parseBoolean(get(property)); |
| } |
| |
| /** |
| * Gets a property of type {@link PropertyType#FRACTION}, interpreting the value properly. |
| * |
| * @param property |
| * property to get |
| * @return property value |
| * @throws IllegalArgumentException |
| * if the property is of the wrong type |
| * @see #getFraction(String) |
| */ |
| public double getFraction(Property property) { |
| checkType(property, PropertyType.FRACTION); |
| |
| return getFraction(get(property)); |
| } |
| |
| /** |
| * Interprets a string specifying a fraction. A fraction is specified as a double. An optional % at the end signifies a percentage. |
| * |
| * @param str |
| * string value |
| * @return interpreted fraction as a decimal value |
| */ |
| public double getFraction(String str) { |
| if (str.length() > 0 && str.charAt(str.length() - 1) == '%') |
| return Double.parseDouble(str.substring(0, str.length() - 1)) / 100.0; |
| return Double.parseDouble(str); |
| } |
| |
| /** |
| * Gets a property of type {@link PropertyType#PORT}, interpreting the value properly (as an integer within the range of non-privileged ports). |
| * |
| * @param property |
| * property to get |
| * @return property value |
| * @throws IllegalArgumentException |
| * if the property is of the wrong type |
| * @see #getTimeInMillis(String) |
| */ |
| public int[] getPort(Property property) { |
| checkType(property, PropertyType.PORT); |
| |
| String portString = get(property); |
| int[] ports = null; |
| try { |
| Pair<Integer,Integer> portRange = PortRange.parse(portString); |
| int low = portRange.getFirst(); |
| int high = portRange.getSecond(); |
| ports = new int[high - low + 1]; |
| for (int i = 0, j = low; j <= high; i++, j++) { |
| ports[i] = j; |
| } |
| } catch (IllegalArgumentException e) { |
| ports = new int[1]; |
| try { |
| int port = Integer.parseInt(portString); |
| if (port != 0) { |
| if (port < 1024 || port > 65535) { |
| log.error("Invalid port number " + port + "; Using default " + property.getDefaultValue()); |
| ports[0] = Integer.parseInt(property.getDefaultValue()); |
| } else { |
| ports[0] = port; |
| } |
| } else { |
| ports[0] = port; |
| } |
| } catch (NumberFormatException e1) { |
| throw new IllegalArgumentException("Invalid port syntax. Must be a single positive integers or a range (M-N) of positive integers"); |
| } |
| } |
| return ports; |
| } |
| |
| /** |
| * Gets a property of type {@link PropertyType#COUNT}, interpreting the value properly (as an integer). |
| * |
| * @param property |
| * property to get |
| * @return property value |
| * @throws IllegalArgumentException |
| * if the property is of the wrong type |
| * @see #getTimeInMillis(String) |
| */ |
| public int getCount(Property property) { |
| checkType(property, PropertyType.COUNT); |
| |
| String countString = get(property); |
| return Integer.parseInt(countString); |
| } |
| |
| /** |
| * Gets a property of type {@link PropertyType#PATH}, interpreting the value properly, replacing supported environment variables. |
| * |
| * @param property |
| * property to get |
| * @return property value |
| * @throws IllegalArgumentException |
| * if the property is of the wrong type |
| * @see Constants#PATH_PROPERTY_ENV_VARS |
| */ |
| public String getPath(Property property) { |
| checkType(property, PropertyType.PATH); |
| |
| String pathString = get(property); |
| if (pathString == null) |
| return null; |
| |
| for (String replaceableEnvVar : Constants.PATH_PROPERTY_ENV_VARS) { |
| String envValue = System.getenv(replaceableEnvVar); |
| if (envValue != null) |
| pathString = pathString.replace("$" + replaceableEnvVar, envValue); |
| } |
| |
| return pathString; |
| } |
| |
| /** |
| * Gets the default configuration. |
| * |
| * @return default configuration |
| * @see DefaultConfiguration#getInstance() |
| */ |
| public static synchronized DefaultConfiguration getDefaultConfiguration() { |
| return DefaultConfiguration.getInstance(); |
| } |
| |
| /** |
| * Gets the configuration specific to a table. |
| * |
| * @param conn |
| * connector (used to find table name) |
| * @param tableId |
| * table ID |
| * @return configuration containing table properties |
| * @throws TableNotFoundException |
| * if the table is not found |
| * @throws AccumuloException |
| * if there is a problem communicating to Accumulo |
| */ |
| public static AccumuloConfiguration getTableConfiguration(Connector conn, String tableId) throws TableNotFoundException, AccumuloException { |
| String tableName = Tables.getTableName(conn.getInstance(), tableId); |
| return new ConfigurationCopy(conn.tableOperations().getProperties(tableName)); |
| } |
| |
| /** |
| * Gets the maximum number of files per tablet from this configuration. |
| * |
| * @return maximum number of files per tablet |
| * @see Property#TABLE_FILE_MAX |
| * @see Property#TSERV_SCAN_MAX_OPENFILES |
| */ |
| public int getMaxFilesPerTablet() { |
| int maxFilesPerTablet = getCount(Property.TABLE_FILE_MAX); |
| if (maxFilesPerTablet <= 0) { |
| maxFilesPerTablet = getCount(Property.TSERV_SCAN_MAX_OPENFILES) - 1; |
| log.debug("Max files per tablet " + maxFilesPerTablet); |
| } |
| |
| return maxFilesPerTablet; |
| } |
| |
| /** |
| * Invalidates the <code>ZooCache</code> used for storage and quick retrieval of properties for this configuration. |
| */ |
| public void invalidateCache() {} |
| |
| /** |
| * Creates a new instance of a class specified in a configuration property. |
| * |
| * @param property |
| * property specifying class name |
| * @param base |
| * base class of type |
| * @param defaultInstance |
| * instance to use if creation fails |
| * @return new class instance, or default instance if creation failed |
| * @see AccumuloVFSClassLoader |
| */ |
| public <T> T instantiateClassProperty(Property property, Class<T> base, T defaultInstance) { |
| String clazzName = get(property); |
| T instance = null; |
| |
| try { |
| Class<? extends T> clazz = AccumuloVFSClassLoader.loadClass(clazzName, base); |
| instance = clazz.newInstance(); |
| log.info("Loaded class : " + clazzName); |
| } catch (Exception e) { |
| log.warn("Failed to load class ", e); |
| } |
| |
| if (instance == null) { |
| log.info("Using " + defaultInstance.getClass().getName()); |
| instance = defaultInstance; |
| } |
| return instance; |
| } |
| |
| } |