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.Enumeration;
22  import java.util.HashMap;
23  import java.util.Hashtable;
24  import java.util.Map;
25  import java.util.Properties;
26  
27  import org.apache.commons.discovery.jdk.JDKHooks;
28  import org.apache.commons.discovery.log.DiscoveryLogFactory;
29  import org.apache.commons.logging.Log;
30  
31  
32  
33  /***
34   * <p>This class may disappear in the future, or be moved to another project..
35   * </p>
36   * 
37   * <p>Extend the concept of System properties to a hierarchical scheme
38   * based around class loaders.  System properties are global in nature,
39   * so using them easily violates sound architectural and design principles
40   * for maintaining separation between components and runtime environments.
41   * Nevertheless, there is a need for properties broader in scope than
42   * class or class instance scope.
43   * </p>
44   * 
45   * <p>This class is one solution.
46   * </p>
47   * 
48   * <p>Manage properties according to a secure
49   * scheme similar to that used by classloaders:
50   * <ul>
51   *   <li><code>ClassLoader</code>s are organized in a tree hierarchy.</li>
52   *   <li>each <code>ClassLoader</code> has a reference
53   *       to a parent <code>ClassLoader</code>.</li>
54   *   <li>the root of the tree is the bootstrap <code>ClassLoader</code>er.</li>
55   *   <li>the youngest decendent is the thread context class loader.</li>
56   *   <li>properties are bound to a <code>ClassLoader</code> instance
57   *   <ul>
58   *     <li><i>non-default</i> properties bound to a parent <code>ClassLoader</code>
59   *         instance take precedence over all properties of the same name bound
60   *         to any decendent.
61   *         Just to confuse the issue, this is the default case.</li>
62   *     <li><i>default</i> properties bound to a parent <code>ClassLoader</code>
63   *         instance may be overriden by (default or non-default) properties of
64   *         the same name bound to any decendent.
65   *         </li>
66   *   </ul>
67   *   </li>
68   *   <li>System properties take precedence over all other properties</li>
69   * </ul>
70   * </p>
71   * 
72   * <p>This is not a perfect solution, as it is possible that
73   * different <code>ClassLoader</code>s load different instances of
74   * <code>ScopedProperties</code>.  The 'higher' this class is loaded
75   * within the <code>ClassLoader</code> hierarchy, the more usefull
76   * it will be.
77   * </p>
78   * 
79   * @author Richard A. Sitze
80   */
81  public class ManagedProperties {
82      private static Log log = DiscoveryLogFactory.newLog(ManagedProperties.class);
83      public static void setLog(Log _log) {
84          log = _log;
85      }
86  
87      /***
88       * Cache of Properties, keyed by (thread-context) class loaders.
89       * Use <code>HashMap</code> because it allows 'null' keys, which
90       * allows us to account for the (null) bootstrap classloader.
91       */
92      private static final HashMap propertiesCache = new HashMap();
93      
94                                                          
95      /***
96       * Get value for property bound to the current thread context class loader.
97       * 
98       * @param propertyName property name.
99       * @return property value if found, otherwise default.
100      */
101     public static String getProperty(String propertyName) {
102         return getProperty(getThreadContextClassLoader(), propertyName);
103     }
104     
105     /***
106      * Get value for property bound to the current thread context class loader.
107      * If not found, then return default.
108      * 
109      * @param propertyName property name.
110      * @param dephault default value.
111      * @return property value if found, otherwise default.
112      */
113     public static String getProperty(String propertyName, String dephault) {
114         return getProperty(getThreadContextClassLoader(), propertyName, dephault);
115     }
116     
117     /***
118      * Get value for property bound to the class loader.
119      * 
120      * @param classLoader
121      * @param propertyName property name.
122      * @return property value if found, otherwise default.
123      */
124     public static String getProperty(ClassLoader classLoader, String propertyName) {
125         String value = JDKHooks.getJDKHooks().getSystemProperty(propertyName);
126         if (value == null) {
127             Value val = getValueProperty(classLoader, propertyName);
128             if (val != null) {
129                 value = val.value;
130             }
131         } else if (log.isDebugEnabled()) {
132             log.debug("found System property '" + propertyName + "'" +
133                       " with value '" + value + "'.");
134         }
135         return value;
136     }
137     
138     /***
139      * Get value for property bound to the class loader.
140      * If not found, then return default.
141      * 
142      * @param classLoader
143      * @param propertyName property name.
144      * @param dephault default value.
145      * @return property value if found, otherwise default.
146      */
147     public static String getProperty(ClassLoader classLoader, String propertyName, String dephault) {
148         String value = getProperty(classLoader, propertyName);
149         return (value == null) ? dephault : value;
150     }
151 
152     /***
153      * Set value for property bound to the current thread context class loader.
154      * @param propertyName property name
155      * @param value property value (non-default)  If null, remove the property.
156      */
157     public static void setProperty(String propertyName, String value) {
158         setProperty(propertyName, value, false);
159     }
160     
161     /***
162      * Set value for property bound to the current thread context class loader.
163      * @param propertyName property name
164      * @param value property value.  If null, remove the property.
165      * @param isDefault determines if property is default or not.
166      *        A non-default property cannot be overriden.
167      *        A default property can be overriden by a property
168      *        (default or non-default) of the same name bound to
169      *        a decendent class loader.
170      */
171     public static void setProperty(String propertyName, String value, boolean isDefault) {
172         if (propertyName != null) {
173             synchronized (propertiesCache) {
174                 ClassLoader classLoader = getThreadContextClassLoader();
175                 HashMap properties = (HashMap)propertiesCache.get(classLoader);
176                 
177                 if (value == null) {
178                     if (properties != null) {
179                         properties.remove(propertyName);
180                     }
181                 } else {
182                     if (properties == null) {
183                         properties = new HashMap();
184                         propertiesCache.put(classLoader, properties);
185                     }
186 
187                     properties.put(propertyName, new Value(value, isDefault));
188                 }
189             }
190         }
191     }
192     
193     /***
194      * Set property values for <code>Properties</code> bound to the
195      * current thread context class loader.
196      * 
197      * @param newProperties name/value pairs to be bound
198      */
199     public static void setProperties(Map newProperties) {
200         setProperties(newProperties, false);
201     }
202     
203     
204     /***
205      * Set property values for <code>Properties</code> bound to the
206      * current thread context class loader.
207      * 
208      * @param newProperties name/value pairs to be bound
209      * @param isDefault determines if properties are default or not.
210      *        A non-default property cannot be overriden.
211      *        A default property can be overriden by a property
212      *        (default or non-default) of the same name bound to
213      *        a decendent class loader.
214      */
215     public static void setProperties(Map newProperties, boolean isDefault) {
216         java.util.Iterator it = newProperties.entrySet().iterator();
217 
218         /***
219          * Each entry must be mapped to a Property.
220          * 'setProperty' does this for us.
221          */
222         while (it.hasNext()) {
223             Map.Entry entry = (Map.Entry)it.next();
224             setProperty( String.valueOf(entry.getKey()),
225                          String.valueOf(entry.getValue()),
226                          isDefault);
227         }
228     }
229 
230     
231     /***
232      * Return list of all property names.  This is an expensive
233      * operation: ON EACH CALL it walks through all property lists 
234      * associated with the current context class loader upto
235      * and including the bootstrap class loader.
236      */
237     public static Enumeration propertyNames() {
238         Hashtable allProps = new Hashtable();
239 
240         ClassLoader classLoader = getThreadContextClassLoader();
241 
242         /***
243          * Order doesn't matter, we are only going to use
244          * the set of all keys...
245          */
246         while (true) {
247             HashMap properties = null;
248 
249             synchronized (propertiesCache) {
250                 properties = (HashMap)propertiesCache.get(classLoader);
251             }
252 
253             if (properties != null) {
254                 allProps.putAll(properties);
255             }
256 
257             if (classLoader == null) break;
258             
259             classLoader = getParent(classLoader);
260         }
261         
262         return allProps.keys();
263     }
264     
265     /***
266      * This is an expensive operation.
267      * ON EACH CALL it walks through all property lists 
268      * associated with the current context class loader upto
269      * and including the bootstrap class loader.
270      * 
271      * @return Returns a <code>java.util.Properties</code> instance
272      * that is equivalent to the current state of the scoped
273      * properties, in that getProperty() will return the same value.
274      * However, this is a copy, so setProperty on the
275      * returned value will not effect the scoped properties.
276      */
277     public static Properties getProperties() {
278         Properties p = new Properties();
279         
280         Enumeration names = propertyNames();
281         while (names.hasMoreElements()) {
282             String name = (String)names.nextElement();
283             p.put(name, getProperty(name));
284         }
285         
286         return p;
287     }
288 
289 
290     /****************** INTERNAL IMPLEMENTATION *****************/
291 
292     private static class Value {
293         final String value;
294         final boolean isDefault;
295         
296         Value(String value, boolean isDefault) {
297             this.value = value;
298             this.isDefault = isDefault;
299         }
300     }
301 
302     /***
303      * Get value for properties bound to the class loader.
304      * Explore up the tree first, as higher-level class
305      * loaders take precedence over lower-level class loaders.
306      */
307     private static final Value getValueProperty(ClassLoader classLoader, String propertyName) {
308         Value value = null;
309 
310         if (propertyName != null) {
311             /***
312              * If classLoader isn't bootstrap loader (==null),
313              * then get up-tree value.
314              */
315             if (classLoader != null) {
316                 value = getValueProperty(getParent(classLoader), propertyName);
317             }
318             
319             if (value == null  ||  value.isDefault) {
320                 synchronized (propertiesCache) {
321                     HashMap properties = (HashMap)propertiesCache.get(classLoader);
322                         
323                     if (properties != null) {
324                         Value altValue = (Value)properties.get(propertyName);
325                         
326                         // set value only if override exists..
327                         // otherwise pass default (or null) on..
328                         if (altValue != null) {
329                             value = altValue;
330 
331                             if (log.isDebugEnabled()) {
332                                 log.debug("found Managed property '" + propertyName + "'" +
333                                           " with value '" + value + "'" +
334                                           " bound to classloader " + classLoader + ".");
335                             }
336                         }
337                     }
338                 }
339             }
340         }
341         
342         return value;
343     }
344     
345     private static final ClassLoader getThreadContextClassLoader() {
346         return JDKHooks.getJDKHooks().getThreadContextClassLoader();
347     }
348 
349     private static final ClassLoader getParent(final ClassLoader classLoader) {
350         return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
351                     public Object run() {
352                         try {
353                             return classLoader.getParent();
354                         } catch (SecurityException se){
355                             return null;
356                         }
357                     }
358                 });
359     }
360 }