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