001package co.codewizards.cloudstore.core.util; 002 003import static co.codewizards.cloudstore.core.io.StreamUtil.*; 004import static co.codewizards.cloudstore.core.oio.OioFileFactory.*; 005import static java.util.Objects.*; 006 007import java.io.IOException; 008import java.io.InputStream; 009import java.io.OutputStream; 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.HashMap; 013import java.util.Map; 014import java.util.Properties; 015import java.util.regex.Matcher; 016import java.util.regex.Pattern; 017 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021import co.codewizards.cloudstore.core.oio.File; 022 023/** 024 * {@link java.util.Properties} utilities. 025 * @author Marc Klinger - marc[at]nightlabs[dot]de 026 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 027 */ 028public final class PropertiesUtil 029{ 030 private static final Logger logger = LoggerFactory.getLogger(PropertiesUtil.class); 031 032 private PropertiesUtil() { } 033 034 /** 035 * Suffix appended to the real property-key to store the boolean flag whether 036 * the real property represents the <code>null</code> value. 037 * <p> 038 * It is not possible to store a <code>null</code> value in a {@link Properties} instance (and neither it is 039 * in a properties file). But sometimes it is necessary to explicitly formulate a <code>null</code> value, 040 * for example when overriding a property in a way as if it had not been specified in the overridden properties. 041 * <p> 042 * For example, let there be these properties declared in a persistence unit: 043 * <pre> 044 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 045 * javax.jdo.option.TransactionType = JTA 046 * javax.persistence.jtaDataSource = jdbc/someDataSource 047 * javax.persistence.transactionType = JTA 048 * </pre> 049 * <p> 050 * If the transaction type is to be overridden by "RESOURCE_LOCAL", this is straight-forward: 051 * <p> 052 * <pre> 053 * javax.jdo.option.TransactionType = RESOURCE_LOCAL 054 * javax.persistence.transactionType = RESOURCE_LOCAL 055 * </pre> 056 * <p> 057 * But to override the datasource properties to be null, is not possible by simply writing 058 * "javax.jdo.option.ConnectionFactoryName = = " as this would 059 * be interpreted as empty string. Therefore it is possible to declare the <code>null</code> value using an additional 060 * key: 061 * <pre> 062 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 063 * javax.jdo.option.ConnectionFactoryName.null = true 064 * javax.persistence.jtaDataSource.null = true 065 * </pre> 066 * It is not necessary to quote the referenced key as shown in the 2nd example ("javax.persistence.jtaDataSource" is 067 * not present). However, if it is present ("javax.jdo.option.ConnectionFactoryName" above), the 068 * null-indicating meta-property "javax.jdo.option.ConnectionFactoryName.null" overrides the value "jdbc/someDataSource". 069 * 070 * @see #getMetaPropertyKeyNullValue(String) 071 * @see #filterProperties(Map, Map) 072 */ 073 public static final String SUFFIX_NULL_VALUE = ".null"; 074 075 /** 076 * Get all matches where property keys match the given pattern. 077 * @param properties The properties to match 078 * @param pattern The pattern to match against 079 * @return The {@link Matcher}s that matched. 080 */ 081 public static Collection<Matcher> getPropertyKeyMatches(final java.util.Properties properties, final Pattern pattern) 082 { 083 final Collection<Matcher> matches = new ArrayList<Matcher>(); 084 for (final Object element : properties.keySet()) { 085 final String key = (String) element; 086 final Matcher m = pattern.matcher(key); 087 if(m.matches()) 088 matches.add(m); 089 } 090 return matches; 091 } 092 093 /** 094 * Get all properties whose keys start with the given prefix. 095 * <p> 096 * The returned property elements have the form <code>(key,value)</code> where <code>key</code> is 097 * the part of the original key after the given <code>keyPrefix</code> and <code>value</code> is 098 * the original value. 099 * </p> 100 * @param properties The properties to filter. 101 * @param keyPrefix The kex prefix to use 102 * @return the properties that start with the given prefix 103 */ 104 public static java.util.Properties getProperties(final java.util.Properties properties, final String keyPrefix) 105 { 106 final java.util.Properties newProperties = new java.util.Properties(); 107 final Collection<Matcher> matches = getPropertyKeyMatches(properties, Pattern.compile("^"+Pattern.quote(keyPrefix)+"(.*)$")); 108 for (final Matcher m : matches) 109 newProperties.put(m.group(1), properties.get(m.group(0))); 110 return newProperties; 111 } 112 113 public static void putAll(final java.util.Properties source, final java.util.Properties target) 114 { 115 for (final Object element : source.keySet()) { 116 final String key = (String) element; 117 target.setProperty(key, source.getProperty(key)); 118 } 119 } 120 121 public static java.util.Properties load(final String filename) throws IOException 122 { 123 return load(filename != null ? createFile(filename) : null); 124 } 125 126 public static java.util.Properties load(final File file) throws IOException 127 { 128 final InputStream in = castStream(file.createInputStream()); 129 try { 130 final java.util.Properties properties = new java.util.Properties(); 131 properties.load(in); 132 return properties; 133 } finally { 134 in.close(); 135 } 136 } 137 138 public static void store(final String filename, final java.util.Properties properties, final String comment) throws IOException 139 { 140 store(filename != null ? createFile(filename) : null, properties, comment); 141 } 142 143 public static void store(final File file, final java.util.Properties properties, final String comment) throws IOException 144 { 145 final OutputStream out = castStream(file.createOutputStream()); 146 try { 147 properties.store(out, comment); 148 } finally { 149 out.close(); 150 } 151 } 152 153 /** 154 * Filter the given raw properties. 155 * <p> 156 * This is a convenience method delegating to 157 * {@link #filterProperties(Map, Map)} with <code>variables == null</code>. 158 * @param rawProperties the properties to be filtered; must not be <code>null</code>. 159 * @return the filtered properties. 160 * @see #filterProperties(Map, Map) 161 */ 162 public static Map<String, String> filterProperties(final Map<?, ?> rawProperties) 163 { 164 return filterProperties(rawProperties, null); 165 } 166 167 /** 168 * Filter the given raw properties. 169 * <p> 170 * <u>Replace null-meta-data to <code>null</code> values:</u> Every property for which the 171 * method {@link #isNullValue(Map, String)} returns <code>true</code> is written with a <code>null</code> 172 * value into the result-map. Every property for which the method {@link #isMetaPropertyKeyNullValue(String)} 173 * returns <code>true</code>, is ignored (i.e. does not occur in the result-map). 174 * <p> 175 * It is not possible to store a <code>null</code> value in a {@link Properties} instance (and neither it is 176 * in a properties file). But sometimes it is necessary to explicitly formulate a <code>null</code> value, 177 * for example when overriding a property in a way as if it had not been specified in the overridden properties. 178 * <p> 179 * For example, let there be these properties declared in a persistence unit: 180 * <pre> 181 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 182 * javax.jdo.option.TransactionType = JTA 183 * javax.persistence.jtaDataSource = jdbc/someDataSource 184 * javax.persistence.transactionType = JTA 185 * </pre> 186 * <p> 187 * If the transaction type is to be overridden by "RESOURCE_LOCAL", this is straight-forward: 188 * <p> 189 * <pre> 190 * javax.jdo.option.TransactionType = RESOURCE_LOCAL 191 * javax.persistence.transactionType = RESOURCE_LOCAL 192 * </pre> 193 * <p> 194 * But to override the datasource properties to be null, is not possible by simply writing 195 * "javax.jdo.option.ConnectionFactoryName = = " as this would 196 * be interpreted as empty string. Therefore it is possible to declare the <code>null</code> value using an additional 197 * key: 198 * <pre> 199 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 200 * javax.jdo.option.ConnectionFactoryName.null = true 201 * javax.persistence.jtaDataSource.null = true 202 * </pre> 203 * It is not necessary to quote the referenced key as shown in the 2nd example ("javax.persistence.jtaDataSource" is 204 * not present). However, if it is present ("javax.jdo.option.ConnectionFactoryName" above), the 205 * null-indicating meta-property "javax.jdo.option.ConnectionFactoryName.null" overrides the value "jdbc/someDataSource". 206 * <p> 207 * <u>Replace template variables:</u> If the optional <code>variables</code> argument is present, every 208 * value is filtered using {@link IOUtil#replaceTemplateVariables(String, Map)}. 209 * <p> 210 * For example, let there be these properties: 211 * <pre> 212 * some.url1 = file:/tmp/myTempDir 213 * some.url2 = http://host.domain.tld/tomcat 214 * </pre> 215 * <p> 216 * If this method is called with {@link System#getProperties()} as <code>variables</code> and the user "marco" is currently 217 * working on a linux machine, the resulting <code>Map</code> will contain the following resolved properties: 218 * <pre> 219 * some.url1 = file:/tmp/myTempDir 220 * some.url2 = http://host.domain.tld/marco 221 * </pre> 222 * @param rawProperties the properties to be filtered; must not be <code>null</code>. 223 * @param variables optional template variables; if present, every value is filtered using 224 * {@link IOUtil#replaceTemplateVariables(String, Map)}. 225 * @return the filtered properties. 226 */ 227 public static Map<String, String> filterProperties(final Map<?, ?> rawProperties, final Map<?, ?> variables) 228 { 229 if (rawProperties == null) 230 throw new IllegalArgumentException("rawProperties == null"); 231 232 final Map<String, String> filteredProperties = new HashMap<String, String>(); 233 for (final Map.Entry<?, ?> me : rawProperties.entrySet()) { 234 final String key = me.getKey() == null ? null : me.getKey().toString(); 235 String value = me.getValue() == null ? null : me.getValue().toString(); 236 237 if (isMetaPropertyKey(key)) { 238 if (isMetaPropertyKeyNullValue(key) && Boolean.parseBoolean(value)) { 239 final String refKey = getReferencedPropertyKeyForMetaPropertyKey(key); 240 filteredProperties.put(refKey, null); 241 } 242 continue; 243 } 244 245 if (value != null && isNullValue(rawProperties, key)) 246 value = null; 247 248 if (value != null && variables != null) 249 value = IOUtil.replaceTemplateVariables(value, variables); 250 251 filteredProperties.put(key, value); 252 } 253 return filteredProperties; 254 } 255 256 /** 257 * Determine, if the given property-key is a <code>null</code>-indicating meta-property for another property. 258 * <p> 259 * It is not possible to store a <code>null</code> value in a {@link Properties} instance (and neither it is 260 * in a properties file). But sometimes it is necessary to explicitly formulate a <code>null</code> value, 261 * for example when overriding a property in a way as if it had not been specified in the overridden properties. 262 * <p> 263 * For example, let there be these properties declared in a persistence unit: 264 * <pre> 265 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 266 * javax.jdo.option.TransactionType = JTA 267 * javax.persistence.jtaDataSource = jdbc/someDataSource 268 * javax.persistence.transactionType = JTA 269 * </pre> 270 * <p> 271 * If the transaction type is to be overridden by "RESOURCE_LOCAL", this is straight-forward: 272 * <p> 273 * <pre> 274 * javax.jdo.option.TransactionType = RESOURCE_LOCAL 275 * javax.persistence.transactionType = RESOURCE_LOCAL 276 * </pre> 277 * <p> 278 * But to override the datasource properties to be null, is not possible by simply writing 279 * "javax.jdo.option.ConnectionFactoryName = = " as this would 280 * be interpreted as empty string. Therefore it is possible to declare the <code>null</code> value using an additional 281 * key: 282 * <pre> 283 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 284 * javax.jdo.option.ConnectionFactoryName.null = true 285 * javax.persistence.jtaDataSource.null = true 286 * </pre> 287 * It is not necessary to quote the referenced key as shown in the 2nd example ("javax.persistence.jtaDataSource" is 288 * not present). However, if it is present ("javax.jdo.option.ConnectionFactoryName" above), the 289 * null-indicating meta-property "javax.jdo.option.ConnectionFactoryName.null" overrides the value "jdbc/someDataSource". 290 * 291 * @param key the property-key to check. 292 * @return <code>true</code>, if the given key references a property that is a <code>null</code>-indicating 293 * meta-property for another property. 294 * @see #isMetaPropertyKey(String) 295 */ 296 public static boolean isMetaPropertyKeyNullValue(final String key) 297 { 298 if (key == null) 299 return false; 300 301 return key.endsWith(SUFFIX_NULL_VALUE); 302 } 303 304 /** 305 * Determine, if the given property-key is a meta-property for another property. 306 * <p> 307 * Currently, this is equivalent to {@link #isMetaPropertyKeyNullValue(String)}, but other 308 * meta-properties might be introduced later. 309 * @param key the property-key to check. 310 * @return <code>true</code>, if the given key references a property that is a meta-property 311 * for another property. 312 */ 313 public static boolean isMetaPropertyKey(final String key) 314 { 315 return isMetaPropertyKeyNullValue(key); 316 } 317 318 /** 319 * Get the referenced property-key for the given meta-property's key. 320 * @param key a meta-property's key - for example the <code>null</code>-indicating property-key 321 * "some.prop.null". 322 * @return the referenced property-key - for example "some.prop". 323 * @see #SUFFIX_NULL_VALUE 324 * @see #getMetaPropertyKeyNullValue(String) 325 */ 326 public static String getReferencedPropertyKeyForMetaPropertyKey(final String key) 327 { 328 if (!isMetaPropertyKeyNullValue(key)) 329 throw new IllegalArgumentException("key='" + key + "' is not a meta-property!"); 330 331 return key.substring(0, key.length() - SUFFIX_NULL_VALUE.length()); 332 } 333 334 /** 335 * Get the <code>null</code>-indicating meta-property's key for the given real property's key. 336 * @param key a property-key - for example "some.prop". 337 * @return the <code>null</code>-indicating meta-property's key - for example "some.prop.null". 338 * @see #SUFFIX_NULL_VALUE 339 * @see #getReferencedPropertyKeyForMetaPropertyKey(String) 340 */ 341 public static String getMetaPropertyKeyNullValue(String key) 342 { 343 if (key == null) 344 key = String.valueOf(key); 345 346 if (isMetaPropertyKeyNullValue(key)) 347 throw new IllegalArgumentException("key='" + key + "' is already a meta-property indicating a null-value!"); 348 349 return key + SUFFIX_NULL_VALUE; 350 } 351 352 /** 353 * Determine, if the property identified by the given <code>key</code> has a <code>null</code>-value. 354 * <p> 355 * It is not possible to store a <code>null</code> value in a {@link Properties} instance (and neither it is 356 * in a properties file). But sometimes it is necessary to explicitly formulate a <code>null</code> value, 357 * for example when overriding a property in a way as if it had not been specified in the overridden properties. 358 * <p> 359 * For example, let there be these properties declared in a persistence unit: 360 * <pre> 361 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 362 * javax.jdo.option.TransactionType = JTA 363 * javax.persistence.jtaDataSource = jdbc/someDataSource 364 * javax.persistence.transactionType = JTA 365 * </pre> 366 * <p> 367 * If the transaction type is to be overridden by "RESOURCE_LOCAL", this is straight-forward: 368 * <p> 369 * <pre> 370 * javax.jdo.option.TransactionType = RESOURCE_LOCAL 371 * javax.persistence.transactionType = RESOURCE_LOCAL 372 * </pre> 373 * <p> 374 * But to override the datasource properties to be null, is not possible by simply writing 375 * "javax.jdo.option.ConnectionFactoryName = " as this would 376 * be interpreted as empty string. Therefore it is possible to declare the <code>null</code> value using an additional 377 * key: 378 * <pre> 379 * javax.jdo.option.ConnectionFactoryName = jdbc/someDataSource 380 * javax.jdo.option.ConnectionFactoryName.null = true 381 * javax.persistence.jtaDataSource.null = true 382 * </pre> 383 * It is not necessary to quote the referenced key as shown in the 2nd example ("javax.persistence.jtaDataSource" is 384 * not present). However, if it is present ("javax.jdo.option.ConnectionFactoryName" above), the 385 * null-indicating meta-property "javax.jdo.option.ConnectionFactoryName.null" overrides the value "jdbc/someDataSource". 386 * 387 * @param properties the properties. Must not be <code>null</code>. 388 * @param key the property-key for which to determine, whether its value is <code>null</code>. 389 * @return <code>true</code>, if the property referenced by the given <code>key</code> is <code>null</code>; 390 * <code>false</code> otherwise. 391 */ 392 public static boolean isNullValue(final Map<?, ?> properties, final String key) 393 { 394 if (properties == null) 395 throw new IllegalArgumentException("properties == null"); 396 397 if (properties.get(key) == null) 398 return true; 399 400 if (isMetaPropertyKeyNullValue(key)) 401 return false; 402 403 final String metaNullValue = String.valueOf(properties.get(getMetaPropertyKeyNullValue(key))); 404 return Boolean.parseBoolean(metaNullValue); 405 } 406 407 public static int getSystemPropertyValueAsInt(final String key, final int defaultValue) { 408 final long value = getSystemPropertyValueAsLong(key, defaultValue); 409 if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) { 410 logger.warn("System property '{}' is set to the value '{}' which is out of range for a 32-bit integer. Falling back to default value {}.", 411 key, value, defaultValue); 412 return defaultValue; 413 } 414 return (int) value; 415 } 416 417 public static long getSystemPropertyValueAsLong(final String key, final long defaultValue) { 418 return getPropertyValueAsLong(System.getProperties(), key, defaultValue); 419 } 420 421 public static long getPropertyValueAsLong(final Properties properties, final String key, long defaultValue) { 422 requireNonNull(properties, "properties"); 423 requireNonNull(key, "key"); 424 425 final String value = properties.getProperty(key); 426 if (value == null) 427 return defaultValue; 428 429 try { 430 return Long.parseLong(value.trim()); 431 } catch (NumberFormatException x) { 432 return defaultValue; 433 } 434 } 435 436 /** 437 * Converts a system property key to an OS environment variable name. 438 * <p> 439 * OS env vars cannot contain a "." and they have a few more restrictions. Most of the restrictions 440 * (e.g. depending on the shell, the name must not start with a digit) are no problem, because our 441 * system properties don't infringe on them. But the "." is commonly used in most of our system properties. 442 * <p> 443 * Therefore, this method throws an {@link IllegalArgumentException}, if any unexpected property key 444 * infringes on a known env var restriction (e.g. starting with a digit). Dots (".") and index-brackets 445 * ("[" and "]") are all converted to underscores ("_"). 446 * <p> 447 * Thus, e.g. the system property "cloudstore.configDir" is equivalent to the env var "cloudstore_configDir" 448 * and the system property "cloudstore.ldap.bindDnTemplate[index]" is equivalent to the env var 449 * "cloudstore_ldap_bindDnTemplate_index_". Please note, that they are case sensitive and the case is not modified! 450 * 451 * @param key the system property key to be converted to an env var name. Must not be <code>null</code>. 452 * @return the env var name. Never <code>null</code>. 453 */ 454 public static String systemPropertyToEnvironmentVariable(final String key) { 455 requireNonNull(key, "key"); 456 457 if (key.isEmpty()) 458 throw new IllegalArgumentException("key is an empty string! At least one character is required!"); 459 460 if (!validSystemPropertyKeyPattern.matcher(key).matches()) { 461 if (Character.isDigit(key.charAt(0))) // be kind: give more precise exception ;-) 462 throw new IllegalArgumentException("key must not start with a digit: " + key); 463 464 throw new IllegalArgumentException("key is not valid according to pattern: " + key); 465 } 466 467 return key.replace('.', '_').replace('[', '_').replace(']', '_'); 468 } 469 470 private static final Pattern validSystemPropertyKeyPattern = Pattern.compile("[a-zA-Z_]+[a-zA-Z0-9_\\.\\[\\]]*"); 471}