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.Enumeration;
022    import java.util.HashMap;
023    import java.util.Hashtable;
024    import java.util.Map;
025    import java.util.Properties;
026    
027    import org.apache.commons.discovery.jdk.JDKHooks;
028    import org.apache.commons.discovery.log.DiscoveryLogFactory;
029    import org.apache.commons.logging.Log;
030    
031    
032    
033    /**
034     * <p>This class may disappear in the future, or be moved to another project..
035     * </p>
036     * 
037     * <p>Extend the concept of System properties to a hierarchical scheme
038     * based around class loaders.  System properties are global in nature,
039     * so using them easily violates sound architectural and design principles
040     * for maintaining separation between components and runtime environments.
041     * Nevertheless, there is a need for properties broader in scope than
042     * class or class instance scope.
043     * </p>
044     * 
045     * <p>This class is one solution.
046     * </p>
047     * 
048     * <p>Manage properties according to a secure
049     * scheme similar to that used by classloaders:
050     * <ul>
051     *   <li><code>ClassLoader</code>s are organized in a tree hierarchy.</li>
052     *   <li>each <code>ClassLoader</code> has a reference
053     *       to a parent <code>ClassLoader</code>.</li>
054     *   <li>the root of the tree is the bootstrap <code>ClassLoader</code>er.</li>
055     *   <li>the youngest decendent is the thread context class loader.</li>
056     *   <li>properties are bound to a <code>ClassLoader</code> instance
057     *   <ul>
058     *     <li><i>non-default</i> properties bound to a parent <code>ClassLoader</code>
059     *         instance take precedence over all properties of the same name bound
060     *         to any decendent.
061     *         Just to confuse the issue, this is the default case.</li>
062     *     <li><i>default</i> properties bound to a parent <code>ClassLoader</code>
063     *         instance may be overriden by (default or non-default) properties of
064     *         the same name bound to any decendent.
065     *         </li>
066     *   </ul>
067     *   </li>
068     *   <li>System properties take precedence over all other properties</li>
069     * </ul>
070     * </p>
071     * 
072     * <p>This is not a perfect solution, as it is possible that
073     * different <code>ClassLoader</code>s load different instances of
074     * <code>ScopedProperties</code>.  The 'higher' this class is loaded
075     * within the <code>ClassLoader</code> hierarchy, the more usefull
076     * it will be.
077     * </p>
078     * 
079     * @author Richard A. Sitze
080     */
081    public class ManagedProperties {
082        private static Log log = DiscoveryLogFactory.newLog(ManagedProperties.class);
083        public static void setLog(Log _log) {
084            log = _log;
085        }
086    
087        /**
088         * Cache of Properties, keyed by (thread-context) class loaders.
089         * Use <code>HashMap</code> because it allows 'null' keys, which
090         * allows us to account for the (null) bootstrap classloader.
091         */
092        private static final HashMap propertiesCache = new HashMap();
093        
094                                                            
095        /**
096         * Get value for property bound to the current thread context class loader.
097         * 
098         * @param propertyName property name.
099         * @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    }