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     */
017    package org.apache.commons.discovery.tools;
018    
019    import java.security.AccessController;
020    import java.security.PrivilegedAction;
021    import java.util.Collections;
022    import java.util.Enumeration;
023    import java.util.HashMap;
024    import java.util.Hashtable;
025    import java.util.Map;
026    import java.util.Properties;
027    
028    import org.apache.commons.discovery.jdk.JDKHooks;
029    import org.apache.commons.logging.Log;
030    import 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     */
078    public 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    }