001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.discovery.tools;
018
019import java.security.AccessController;
020import java.security.PrivilegedAction;
021import java.util.Collections;
022import java.util.Enumeration;
023import java.util.HashMap;
024import java.util.Hashtable;
025import java.util.Map;
026import java.util.Properties;
027
028import org.apache.commons.discovery.jdk.JDKHooks;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031
032/**
033 * <p>This class may disappear in the future, or be moved to another project..
034 * </p>
035 *
036 * <p>Extend the concept of System properties to a hierarchical scheme
037 * based around class loaders.  System properties are global in nature,
038 * so using them easily violates sound architectural and design principles
039 * for maintaining separation between components and runtime environments.
040 * Nevertheless, there is a need for properties broader in scope than
041 * class or class instance scope.
042 * </p>
043 *
044 * <p>This class is one solution.
045 * </p>
046 *
047 * <p>Manage properties according to a secure
048 * scheme similar to that used by classloaders:
049 * <ul>
050 *   <li><code>ClassLoader</code>s are organized in a tree hierarchy.</li>
051 *   <li>each <code>ClassLoader</code> has a reference
052 *       to a parent <code>ClassLoader</code>.</li>
053 *   <li>the root of the tree is the bootstrap <code>ClassLoader</code>er.</li>
054 *   <li>the youngest decendent is the thread context class loader.</li>
055 *   <li>properties are bound to a <code>ClassLoader</code> instance
056 *   <ul>
057 *     <li><i>non-default</i> properties bound to a parent <code>ClassLoader</code>
058 *         instance take precedence over all properties of the same name bound
059 *         to any decendent.
060 *         Just to confuse the issue, this is the default case.</li>
061 *     <li><i>default</i> properties bound to a parent <code>ClassLoader</code>
062 *         instance may be overriden by (default or non-default) properties of
063 *         the same name bound to any decendent.
064 *         </li>
065 *   </ul>
066 *   </li>
067 *   <li>System properties take precedence over all other properties</li>
068 * </ul>
069 * </p>
070 *
071 * <p>This is not a perfect solution, as it is possible that
072 * different <code>ClassLoader</code>s load different instances of
073 * <code>ScopedProperties</code>.  The 'higher' this class is loaded
074 * within the <code>ClassLoader</code> hierarchy, the more usefull
075 * it will be.
076 * </p>
077 */
078public class ManagedProperties {
079
080    private static Log log = LogFactory.getLog(ManagedProperties.class);
081
082    /**
083     * Sets the {@code Log} for this class.
084     *
085     * @param _log This class {@code Log}
086     * @deprecated This method is not thread-safe
087     */
088    @Deprecated
089    public static void setLog(Log _log) {
090        log = _log;
091    }
092
093    /**
094     * Cache of Properties, keyed by (thread-context) class loaders.
095     * Use <code>HashMap</code> because it allows 'null' keys, which
096     * allows us to account for the (null) bootstrap classloader.
097     */
098    private static final Map<ClassLoader, Map<String, Value>> propertiesCache =
099        new HashMap<ClassLoader, Map<String, Value>>();
100
101    /**
102     * Get value for property bound to the current thread context class loader.
103     *
104     * @param propertyName property name.
105     * @return property value if found, otherwise default.
106     */
107    public static String getProperty(String propertyName) {
108        return getProperty(getThreadContextClassLoader(), propertyName);
109    }
110
111    /**
112     * Get value for property bound to the current thread context class loader.
113     * If not found, then return default.
114     *
115     * @param propertyName property name.
116     * @param dephault default value.
117     * @return property value if found, otherwise default.
118     */
119    public static String getProperty(String propertyName, String dephault) {
120        return getProperty(getThreadContextClassLoader(), propertyName, dephault);
121    }
122
123    /**
124     * Get value for property bound to the class loader.
125     *
126     * @param classLoader The classloader used to load resources.
127     * @param propertyName property name.
128     * @return property value if found, otherwise default.
129     */
130    public static String getProperty(ClassLoader classLoader, String propertyName) {
131        String value = JDKHooks.getJDKHooks().getSystemProperty(propertyName);
132        if (value == null) {
133            Value val = getValueProperty(classLoader, propertyName);
134            if (val != null) {
135                value = val.value;
136            }
137        } else if (log.isDebugEnabled()) {
138            log.debug("found System property '" + propertyName + "'" +
139                      " with value '" + value + "'.");
140        }
141        return value;
142    }
143
144    /**
145     * Get value for property bound to the class loader.
146     * If not found, then return default.
147     *
148     * @param classLoader The classloader used to load resources.
149     * @param propertyName property name.
150     * @param dephault default value.
151     * @return property value if found, otherwise default.
152     */
153    public static String getProperty(ClassLoader classLoader, String propertyName, String dephault) {
154        String value = getProperty(classLoader, propertyName);
155        return (value == null) ? dephault : value;
156    }
157
158    /**
159     * Set value for property bound to the current thread context class loader.
160     * @param propertyName property name
161     * @param value property value (non-default)  If null, remove the property.
162     */
163    public static void setProperty(String propertyName, String value) {
164        setProperty(propertyName, value, false);
165    }
166
167    /**
168     * Set value for property bound to the current thread context class loader.
169     * @param propertyName property name
170     * @param value property value.  If null, remove the property.
171     * @param isDefault determines if property is default or not.
172     *        A non-default property cannot be overriden.
173     *        A default property can be overriden by a property
174     *        (default or non-default) of the same name bound to
175     *        a decendent class loader.
176     */
177    public static void setProperty(String propertyName, String value, boolean isDefault) {
178        if (propertyName != null) {
179            synchronized (propertiesCache) {
180                ClassLoader classLoader = getThreadContextClassLoader();
181                Map<String, Value> properties = propertiesCache.get(classLoader);
182
183                if (value == null) {
184                    if (properties != null) {
185                        properties.remove(propertyName);
186                    }
187                } else {
188                    if (properties == null) {
189                        properties = new HashMap<String, Value>();
190                        propertiesCache.put(classLoader, properties);
191                    }
192
193                    properties.put(propertyName, new Value(value, isDefault));
194                }
195            }
196        }
197    }
198
199    /**
200     * Set property values for <code>Properties</code> bound to the
201     * current thread context class loader.
202     *
203     * @param newProperties name/value pairs to be bound
204     */
205    public static void setProperties(Map<?, ?> newProperties) {
206        setProperties(newProperties, false);
207    }
208
209    /**
210     * Set property values for <code>Properties</code> bound to the
211     * current thread context class loader.
212     *
213     * @param newProperties name/value pairs to be bound
214     * @param isDefault determines if properties are default or not.
215     *        A non-default property cannot be overriden.
216     *        A default property can be overriden by a property
217     *        (default or non-default) of the same name bound to
218     *        a decendent class loader.
219     */
220    public static void setProperties(Map<?, ?> newProperties, boolean isDefault) {
221        /**
222         * Each entry must be mapped to a Property.
223         * 'setProperty' does this for us.
224         */
225        for (Map.Entry<?, ?> entry : newProperties.entrySet()) {
226            setProperty( String.valueOf(entry.getKey()),
227                         String.valueOf(entry.getValue()),
228                         isDefault);
229        }
230    }
231
232    /**
233     * Return list of all property names.  This is an expensive
234     * operation: ON EACH CALL it walks through all property lists 
235     * associated with the current context class loader upto
236     * and including the bootstrap class loader.
237     *
238     * @return The list of all property names
239     */
240    public static Enumeration<String> propertyNames() {
241        Map<String, Value> allProps = new Hashtable<String, Value>();
242
243        ClassLoader classLoader = getThreadContextClassLoader();
244
245        /**
246         * Order doesn't matter, we are only going to use
247         * the set of all keys...
248         */
249        while (true) {
250            Map<String, Value> properties = null;
251
252            synchronized (propertiesCache) {
253                properties = propertiesCache.get(classLoader);
254            }
255
256            if (properties != null) {
257                allProps.putAll(properties);
258            }
259
260            if (classLoader == null) {
261                break;
262            }
263
264            classLoader = getParent(classLoader);
265        }
266
267        return Collections.enumeration(allProps.keySet());
268    }
269
270    /**
271     * This is an expensive operation.
272     * ON EACH CALL it walks through all property lists 
273     * associated with the current context class loader upto
274     * and including the bootstrap class loader.
275     *
276     * @return Returns a <code>java.util.Properties</code> instance
277     * that is equivalent to the current state of the scoped
278     * properties, in that getProperty() will return the same value.
279     * However, this is a copy, so setProperty on the
280     * returned value will not effect the scoped properties.
281     */
282    public static Properties getProperties() {
283        Properties p = new Properties();
284
285        Enumeration<String> names = propertyNames();
286        while (names.hasMoreElements()) {
287            String name = names.nextElement();
288            p.put(name, getProperty(name));
289        }
290
291        return p;
292    }
293
294    /***************** INTERNAL IMPLEMENTATION *****************/
295
296    private static class Value {
297        final String value;
298        final boolean isDefault;
299
300        /**
301         * Creates a new Value instance with string value and
302         * the flag to mark is default value or not.
303         *
304         * @param value String representation of this value
305         * @param isDefault The default flag
306         */
307        Value(String value, boolean isDefault) {
308            this.value = value;
309            this.isDefault = isDefault;
310        }
311    }
312
313    /**
314     * Get value for properties bound to the class loader.
315     * Explore up the tree first, as higher-level class
316     * loaders take precedence over lower-level class loaders.
317     *
318     * 
319     * @param classLoader The class loader as key
320     * @param propertyName The property name to lookup
321     * @return The Value associated to the input class loader and property name
322     */
323    private static final Value getValueProperty(ClassLoader classLoader, String propertyName) {
324        Value value = null;
325
326        if (propertyName != null) {
327            /**
328             * If classLoader isn't bootstrap loader (==null),
329             * then get up-tree value.
330             */
331            if (classLoader != null) {
332                value = getValueProperty(getParent(classLoader), propertyName);
333            }
334
335            if (value == null  ||  value.isDefault) {
336                synchronized (propertiesCache) {
337                    Map<String, Value> properties = propertiesCache.get(classLoader);
338
339                    if (properties != null) {
340                        Value altValue = properties.get(propertyName);
341
342                        // set value only if override exists..
343                        // otherwise pass default (or null) on..
344                        if (altValue != null) {
345                            value = altValue;
346
347                            if (log.isDebugEnabled()) {
348                                log.debug("found Managed property '" + propertyName + "'" +
349                                          " with value '" + value + "'" +
350                                          " bound to classloader " + classLoader + ".");
351                            }
352                        }
353                    }
354                }
355            }
356        }
357
358        return value;
359    }
360
361    /**
362     * Returns the thread context class loader.
363     *
364     * @return The thread context class loader
365     */
366    private static final ClassLoader getThreadContextClassLoader() {
367        return JDKHooks.getJDKHooks().getThreadContextClassLoader();
368    }
369
370    /**
371     * Return the parent class loader of the given class loader.
372     *
373     * @param classLoader The class loader from wich the parent has to be extracted
374     * @return The parent class loader of the given class loader
375     */
376    private static final ClassLoader getParent(final ClassLoader classLoader) {
377        return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
378                    public ClassLoader run() {
379                        try {
380                            return classLoader.getParent();
381                        } catch (SecurityException se){
382                            return null;
383                        }
384                    }
385                });
386    }
387
388}