View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.discovery.tools;
18  
19  import java.security.AccessController;
20  import java.security.PrivilegedAction;
21  import java.util.Collections;
22  import java.util.Enumeration;
23  import java.util.HashMap;
24  import java.util.Hashtable;
25  import java.util.Map;
26  import java.util.Properties;
27  
28  import org.apache.commons.discovery.jdk.JDKHooks;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  /**
33   * <p>This class may disappear in the future, or be moved to another project..
34   * </p>
35   *
36   * <p>Extend the concept of System properties to a hierarchical scheme
37   * based around class loaders.  System properties are global in nature,
38   * so using them easily violates sound architectural and design principles
39   * for maintaining separation between components and runtime environments.
40   * Nevertheless, there is a need for properties broader in scope than
41   * class or class instance scope.
42   * </p>
43   *
44   * <p>This class is one solution.
45   * </p>
46   *
47   * <p>Manage properties according to a secure
48   * scheme similar to that used by classloaders:
49   * <ul>
50   *   <li><code>ClassLoader</code>s are organized in a tree hierarchy.</li>
51   *   <li>each <code>ClassLoader</code> has a reference
52   *       to a parent <code>ClassLoader</code>.</li>
53   *   <li>the root of the tree is the bootstrap <code>ClassLoader</code>er.</li>
54   *   <li>the youngest decendent is the thread context class loader.</li>
55   *   <li>properties are bound to a <code>ClassLoader</code> instance
56   *   <ul>
57   *     <li><i>non-default</i> properties bound to a parent <code>ClassLoader</code>
58   *         instance take precedence over all properties of the same name bound
59   *         to any decendent.
60   *         Just to confuse the issue, this is the default case.</li>
61   *     <li><i>default</i> properties bound to a parent <code>ClassLoader</code>
62   *         instance may be overriden by (default or non-default) properties of
63   *         the same name bound to any decendent.
64   *         </li>
65   *   </ul>
66   *   </li>
67   *   <li>System properties take precedence over all other properties</li>
68   * </ul>
69   * </p>
70   *
71   * <p>This is not a perfect solution, as it is possible that
72   * different <code>ClassLoader</code>s load different instances of
73   * <code>ScopedProperties</code>.  The 'higher' this class is loaded
74   * within the <code>ClassLoader</code> hierarchy, the more usefull
75   * it will be.
76   * </p>
77   */
78  public class ManagedProperties {
79  
80      private static Log log = LogFactory.getLog(ManagedProperties.class);
81  
82      /**
83       * Sets the {@code Log} for this class.
84       *
85       * @param _log This class {@code Log}
86       * @deprecated This method is not thread-safe
87       */
88      @Deprecated
89      public static void setLog(Log _log) {
90          log = _log;
91      }
92  
93      /**
94       * Cache of Properties, keyed by (thread-context) class loaders.
95       * Use <code>HashMap</code> because it allows 'null' keys, which
96       * allows us to account for the (null) bootstrap classloader.
97       */
98      private static final Map<ClassLoader, Map<String, Value>> propertiesCache =
99          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 }