001/* 002 * Copyright 2021 Global Biodiversity Information Facility (GBIF) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.gbif.utils.file.properties; 017 018import org.gbif.utils.PreconditionUtils; 019import org.gbif.utils.file.FileUtils; 020import org.gbif.utils.file.ResourcesUtil; 021 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileReader; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.UnsupportedEncodingException; 028import java.net.URL; 029import java.util.Iterator; 030import java.util.Objects; 031import java.util.Properties; 032 033import org.apache.commons.lang3.BooleanUtils; 034import org.apache.commons.lang3.StringUtils; 035 036/** 037 * Utility class for handling properties files. 038 * TODO this class should probably be in a "properties" package at the same level as "file" 039 */ 040public final class PropertiesUtil { 041 042 /** 043 * When we encode strings, we always specify UTF8 encoding 044 */ 045 public static final String UTF8_ENCODING = FileUtils.UTF8; 046 047 /** 048 * Private default constructor. 049 */ 050 private PropertiesUtil() { 051 // empty block 052 } 053 054 /** 055 * Loads a properties file. 056 * The file should be available in the classpath, the default {@link ClassLoader} is used to load the file. 057 * 058 * @throws IOException Should there be an issue in loading the file 059 * @throws IllegalArgumentException If the file does not exist 060 */ 061 public static Properties loadProperties(String propertiesFile) throws IOException, IllegalArgumentException { 062 Properties tempProperties = new Properties(); 063 File file = new File(propertiesFile); 064 065 if (file.exists()) { // first tries to load the file as a external file 066 try (InputStream is = new FileInputStream(file)) { 067 tempProperties.load(is); 068 } 069 } else { // tries to load the file as a resource 070 URL configFileURL = ResourcesUtil.getResource(propertiesFile); 071 try (InputStream is = configFileURL.openStream()) { 072 tempProperties.load(is); 073 } 074 } 075 076 return tempProperties; 077 } 078 079 /** 080 * Reads a property file from an absolute filepath. 081 */ 082 public static Properties readFromFile(String filepath) throws IOException, IllegalArgumentException { 083 PreconditionUtils.checkArgument(StringUtils.isNotBlank(filepath), "No properties file given"); 084 File pf = new File(filepath); 085 if (!pf.exists()) { 086 throw new IllegalArgumentException("Cannot find properties file " + filepath); 087 } 088 Properties properties = new Properties(); 089 090 try (FileReader reader = new FileReader(pf)) { 091 properties.load(reader); 092 } 093 return properties; 094 } 095 096 /** 097 * Reads and casts the named property as an Double. 098 * 099 * @param p The properties file to read from. 100 * @param key To read the value of. 101 * @param exceptionForNull If true, and the property is not found an IAE is thrown, otherwise defaultValue is 102 * returned 103 * @param defaultValue If the property is not found, and exceptionForNull is false, this is returned for missing 104 * properties. 105 * @return The property at the key as an Double 106 * @throws IllegalArgumentException if the property is invalid (can't be cast to a double) or not found and we are 107 * instructed to throw it. 108 */ 109 public static Double propertyAsDouble(Properties p, String key, boolean exceptionForNull, Double defaultValue) 110 throws IllegalArgumentException { 111 String v = p.getProperty(key); 112 if (v != null) { 113 try { 114 return Double.parseDouble(v); 115 } catch (NumberFormatException e) { 116 throw new IllegalArgumentException("Invalid value[" + v + "] supplied for " + key); 117 } 118 } else { 119 if (exceptionForNull) { 120 throw new IllegalArgumentException("Missing property for " + key); 121 } else { 122 return defaultValue; 123 } 124 } 125 } 126 127 /** 128 * Reads and casts the named property as an Float. 129 * 130 * @param p The properties file to read from. 131 * @param key To read the value of. 132 * @param exceptionForNull If true, and the property is not found an IAE is thrown, otherwise defaultValue is 133 * returned 134 * @param defaultValue If the property is not found, and exceptionForNull is false, this is returned for missing 135 * properties. 136 * @return The property at the key as an Float 137 * @throws IllegalArgumentException if the property is invalid (can't be cast to a float) or not found and we are 138 * instructed to throw it. 139 */ 140 public static Float propertyAsFloat(Properties p, String key, boolean exceptionForNull, Float defaultValue) 141 throws IllegalArgumentException { 142 String v = p.getProperty(key); 143 if (v != null) { 144 try { 145 return Float.parseFloat(v); 146 } catch (NumberFormatException e) { 147 throw new IllegalArgumentException("Invalid value[" + v + "] supplied for " + key); 148 } 149 } else { 150 if (exceptionForNull) { 151 throw new IllegalArgumentException("Missing property for " + key); 152 } else { 153 return defaultValue; 154 } 155 } 156 } 157 158 /** 159 * Reads and casts the named property as an Integer. 160 * 161 * @param p The properties file to read from. 162 * @param key To read the value of. 163 * @param exceptionForNull If true, and the property is not found an IAE is thrown, otherwise defaultValue is 164 * returned 165 * @param defaultValue If the property is not found, and exceptionForNull is false, this is returned for missing 166 * properties. 167 * @return The property at the key as an int 168 * @throws IllegalArgumentException if the property is invalid (can't be cast to an int) or not found and we are 169 * instructed to throw it. 170 */ 171 public static Integer propertyAsInt(Properties p, String key, boolean exceptionForNull, Integer defaultValue) 172 throws IllegalArgumentException { 173 String v = p.getProperty(key); 174 if (v != null) { 175 try { 176 return Integer.parseInt(v); 177 } catch (NumberFormatException e) { 178 throw new IllegalArgumentException("Invalid value[" + v + "] supplied for " + key); 179 } 180 } else { 181 if (exceptionForNull) { 182 throw new IllegalArgumentException("Missing property for " + key); 183 } else { 184 return defaultValue; 185 } 186 } 187 } 188 189 /** 190 * Reads and casts the named property as a boolean. 191 * Case insensitive values for 'true', 'on', 'yes', 't' and 'y' return true values, 192 * 'false', 'off', 'no', 'f' and 'n' return false. 193 * Otherwise or in case of a missing property the default will be used. 194 * 195 * @param p The properties file to read from. 196 * @param key To read the value of. 197 * @param defaultValue If the property is not found this is returned for missing properties. 198 * @return The property at the key as a boolean 199 */ 200 public static boolean propertyAsBool(Properties p, String key, boolean defaultValue) { 201 Boolean val = BooleanUtils.toBooleanObject(p.getProperty(key, null)); 202 return val == null ? defaultValue : val; 203 } 204 205 /** 206 * Reads and converts the named property as UTF8 bytes. 207 * 208 * @param p The properties file to read from. 209 * @param key To read the value of. 210 * @param exceptionForNull If true, and the property is not found an IAE is thrown, otherwise defaultValue is 211 * returned 212 * @param defaultValue If the property is not found, and exceptionForNull is false, this is returned for missing 213 * properties. 214 * @return The property at the key as byte[]t 215 * @throws IllegalArgumentException if the property is not found and we are instructed to throw it. 216 */ 217 public static byte[] propertyAsUTF8Bytes(Properties p, String key, boolean exceptionForNull, byte[] defaultValue) 218 throws IllegalArgumentException { 219 String v = p.getProperty(key); 220 if (v != null) { 221 try { 222 return v.getBytes(UTF8_ENCODING); 223 } catch (UnsupportedEncodingException e) { 224 // never one would hope 225 throw new RuntimeException("System does not support " + UTF8_ENCODING + " encoding"); 226 } 227 } else { 228 if (exceptionForNull) { 229 throw new IllegalArgumentException("Missing property for " + key); 230 } else { 231 return defaultValue; 232 } 233 } 234 } 235 236 /** 237 * Filters and translates Properties with a prefix. 238 * The resulting Properties will only include the properties that start with the provided prefix with that prefix 239 * removed (e.g. myprefix.key1 will be returned as key1 if prefix = "myprefix.") 240 * 241 * @param properties to filter and translate 242 * @param prefix prefix used to filter the properties. (e.g. "myprefix.") 243 * @return new Properties object with filtered and translated properties. Never null. 244 */ 245 public static Properties filterProperties(final Properties properties, String prefix) { 246 Objects.requireNonNull(properties, "Can't filter a null Properties"); 247 PreconditionUtils.checkState(StringUtils.isNotBlank(prefix), 248 "Can't filter using a blank prefix [" + properties + "]"); 249 250 Properties filtered = new Properties(); 251 for (String key : properties.stringPropertyNames()) { 252 if (key.startsWith(prefix)) { 253 filtered.setProperty(key.substring(prefix.length()), properties.getProperty(key)); 254 } 255 } 256 return filtered; 257 } 258 259 /** 260 * Returns a new Properties object that contains only the elements where the key starts by the provided 261 * prefix. The same keys will be used in the returned Properties. 262 * @param original 263 * @param prefix 264 * @return 265 */ 266 public static Properties subsetProperties(final Properties original, String prefix) { 267 return propertiesByPrefix(original, prefix, false); 268 } 269 270 /** 271 * Remove properties from the original object and return the removed element(s) as new Properties object. 272 * The same keys will be used in the returned Properties. 273 * @param original original object in which the element will be removed if key starts with provided prefix. 274 * @param prefix 275 * @return 276 */ 277 public static Properties removeProperties(final Properties original, String prefix) { 278 return propertiesByPrefix(original, prefix, true); 279 } 280 281 /** 282 * Get a a new Properties object that only contains the elements that start with the prefix. 283 * The same keys will be used in the returned Properties. 284 * @param original 285 * @param prefix 286 * @param remove should the element(s) be removed from the original Properties object 287 * @return 288 */ 289 private static Properties propertiesByPrefix(final Properties original, String prefix, boolean remove) { 290 Objects.requireNonNull(original, "Can't filter a null Properties"); 291 PreconditionUtils.checkState(StringUtils.isNotBlank(prefix), "Can't filter using a blank prefix [" + original + "]"); 292 293 Properties filtered = new Properties(); 294 295 if(original.isEmpty()){ 296 return filtered; 297 } 298 299 Iterator<Object> keysIt = original.keySet().iterator(); 300 String key; 301 while (keysIt.hasNext()) { 302 key = String.valueOf(keysIt.next()); 303 if (key.startsWith(prefix)) { 304 filtered.setProperty(key, original.getProperty(key)); 305 if(remove){ 306 keysIt.remove(); 307 } 308 } 309 } 310 return filtered; 311 } 312 313}