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