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 }