1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
327
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 }