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: * <p>This class may disappear in the future, or be moved to another project..
033: * </p>
034: *
035: * <p>Extend the concept of System properties to a hierarchical scheme
036: * based around class loaders. System properties are global in nature,
037: * so using them easily violates sound architectural and design principles
038: * for maintaining separation between components and runtime environments.
039: * Nevertheless, there is a need for properties broader in scope than
040: * class or class instance scope.
041: * </p>
042: *
043: * <p>This class is one solution.
044: * </p>
045: *
046: * <p>Manage properties according to a secure
047: * scheme similar to that used by classloaders:
048: * <ul>
049: * <li><code>ClassLoader</code>s are organized in a tree hierarchy.</li>
050: * <li>each <code>ClassLoader</code> has a reference
051: * to a parent <code>ClassLoader</code>.</li>
052: * <li>the root of the tree is the bootstrap <code>ClassLoader</code>er.</li>
053: * <li>the youngest decendent is the thread context class loader.</li>
054: * <li>properties are bound to a <code>ClassLoader</code> instance
055: * <ul>
056: * <li><i>non-default</i> properties bound to a parent <code>ClassLoader</code>
057: * instance take precedence over all properties of the same name bound
058: * to any decendent.
059: * Just to confuse the issue, this is the default case.</li>
060: * <li><i>default</i> properties bound to a parent <code>ClassLoader</code>
061: * instance may be overriden by (default or non-default) properties of
062: * the same name bound to any decendent.
063: * </li>
064: * </ul>
065: * </li>
066: * <li>System properties take precedence over all other properties</li>
067: * </ul>
068: * </p>
069: *
070: * <p>This is not a perfect solution, as it is possible that
071: * different <code>ClassLoader</code>s load different instances of
072: * <code>ScopedProperties</code>. The 'higher' this class is loaded
073: * within the <code>ClassLoader</code> hierarchy, the more usefull
074: * it will be.
075: * </p>
076: *
077: * @author Richard A. Sitze
078: */
079: public class ManagedProperties {
080: private static Log log = DiscoveryLogFactory
081: .newLog(ManagedProperties.class);
082:
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: * Get value for property bound to the current thread context class loader.
096: *
097: * @param propertyName property name.
098: * @return property value if found, otherwise default.
099: */
100: public static String getProperty(String propertyName) {
101: return getProperty(getThreadContextClassLoader(), propertyName);
102: }
103:
104: /**
105: * Get value for property bound to the current thread context class loader.
106: * If not found, then return default.
107: *
108: * @param propertyName property name.
109: * @param dephault default value.
110: * @return property value if found, otherwise default.
111: */
112: public static String getProperty(String propertyName,
113: String dephault) {
114: return getProperty(getThreadContextClassLoader(), propertyName,
115: dephault);
116: }
117:
118: /**
119: * Get value for property bound to the class loader.
120: *
121: * @param classLoader
122: * @param propertyName property name.
123: * @return property value if found, otherwise default.
124: */
125: public static String getProperty(ClassLoader classLoader,
126: String propertyName) {
127: String value = JDKHooks.getJDKHooks().getSystemProperty(
128: propertyName);
129: if (value == null) {
130: Value val = getValueProperty(classLoader, propertyName);
131: if (val != null) {
132: value = val.value;
133: }
134: } else if (log.isDebugEnabled()) {
135: log.debug("found System property '" + propertyName + "'"
136: + " with value '" + value + "'.");
137: }
138: return value;
139: }
140:
141: /**
142: * Get value for property bound to the class loader.
143: * If not found, then return default.
144: *
145: * @param classLoader
146: * @param propertyName property name.
147: * @param dephault default value.
148: * @return property value if found, otherwise default.
149: */
150: public static String getProperty(ClassLoader classLoader,
151: String propertyName, String dephault) {
152: String value = getProperty(classLoader, propertyName);
153: return (value == null) ? dephault : value;
154: }
155:
156: /**
157: * Set value for property bound to the current thread context class loader.
158: * @param propertyName property name
159: * @param value property value (non-default) If null, remove the property.
160: */
161: public static void setProperty(String propertyName, String value) {
162: setProperty(propertyName, value, false);
163: }
164:
165: /**
166: * Set value for property bound to the current thread context class loader.
167: * @param propertyName property name
168: * @param value property value. If null, remove the property.
169: * @param isDefault determines if property is default or not.
170: * A non-default property cannot be overriden.
171: * A default property can be overriden by a property
172: * (default or non-default) of the same name bound to
173: * a decendent class loader.
174: */
175: public static void setProperty(String propertyName, String value,
176: boolean isDefault) {
177: if (propertyName != null) {
178: synchronized (propertiesCache) {
179: ClassLoader classLoader = getThreadContextClassLoader();
180: HashMap properties = (HashMap) propertiesCache
181: .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();
190: propertiesCache.put(classLoader, properties);
191: }
192:
193: properties.put(propertyName, new Value(value,
194: isDefault));
195: }
196: }
197: }
198: }
199:
200: /**
201: * Set property values for <code>Properties</code> bound to the
202: * current thread context class loader.
203: *
204: * @param newProperties name/value pairs to be bound
205: */
206: public static void setProperties(Map newProperties) {
207: setProperties(newProperties, false);
208: }
209:
210: /**
211: * Set property values for <code>Properties</code> bound to the
212: * current thread context class loader.
213: *
214: * @param newProperties name/value pairs to be bound
215: * @param isDefault determines if properties are default or not.
216: * A non-default property cannot be overriden.
217: * A default property can be overriden by a property
218: * (default or non-default) of the same name bound to
219: * a decendent class loader.
220: */
221: public static void setProperties(Map newProperties,
222: boolean isDefault) {
223: java.util.Iterator it = newProperties.entrySet().iterator();
224:
225: /**
226: * Each entry must be mapped to a Property.
227: * 'setProperty' does this for us.
228: */
229: while (it.hasNext()) {
230: Map.Entry entry = (Map.Entry) it.next();
231: setProperty(String.valueOf(entry.getKey()), String
232: .valueOf(entry.getValue()), isDefault);
233: }
234: }
235:
236: /**
237: * Return list of all property names. This is an expensive
238: * operation: ON EACH CALL it walks through all property lists
239: * associated with the current context class loader upto
240: * and including the bootstrap class loader.
241: */
242: public static Enumeration propertyNames() {
243: Hashtable allProps = new Hashtable();
244:
245: ClassLoader classLoader = getThreadContextClassLoader();
246:
247: /**
248: * Order doesn't matter, we are only going to use
249: * the set of all keys...
250: */
251: while (true) {
252: HashMap properties = null;
253:
254: synchronized (propertiesCache) {
255: properties = (HashMap) propertiesCache.get(classLoader);
256: }
257:
258: if (properties != null) {
259: allProps.putAll(properties);
260: }
261:
262: if (classLoader == null)
263: break;
264:
265: classLoader = getParent(classLoader);
266: }
267:
268: return allProps.keys();
269: }
270:
271: /**
272: * This is an expensive operation.
273: * ON EACH CALL it walks through all property lists
274: * associated with the current context class loader upto
275: * and including the bootstrap class loader.
276: *
277: * @return Returns a <code>java.util.Properties</code> instance
278: * that is equivalent to the current state of the scoped
279: * properties, in that getProperty() will return the same value.
280: * However, this is a copy, so setProperty on the
281: * returned value will not effect the scoped properties.
282: */
283: public static Properties getProperties() {
284: Properties p = new Properties();
285:
286: Enumeration names = propertyNames();
287: while (names.hasMoreElements()) {
288: String name = (String) names.nextElement();
289: p.put(name, getProperty(name));
290: }
291:
292: return p;
293: }
294:
295: /***************** INTERNAL IMPLEMENTATION *****************/
296:
297: private static class Value {
298: final String value;
299: final boolean isDefault;
300:
301: Value(String value, boolean isDefault) {
302: this .value = value;
303: this .isDefault = isDefault;
304: }
305: }
306:
307: /**
308: * Get value for properties bound to the class loader.
309: * Explore up the tree first, as higher-level class
310: * loaders take precedence over lower-level class loaders.
311: */
312: private static final Value getValueProperty(
313: ClassLoader classLoader, String propertyName) {
314: Value value = null;
315:
316: if (propertyName != null) {
317: /**
318: * If classLoader isn't bootstrap loader (==null),
319: * then get up-tree value.
320: */
321: if (classLoader != null) {
322: value = getValueProperty(getParent(classLoader),
323: propertyName);
324: }
325:
326: if (value == null || value.isDefault) {
327: synchronized (propertiesCache) {
328: HashMap properties = (HashMap) propertiesCache
329: .get(classLoader);
330:
331: if (properties != null) {
332: Value altValue = (Value) properties
333: .get(propertyName);
334:
335: // set value only if override exists..
336: // otherwise pass default (or null) on..
337: if (altValue != null) {
338: value = altValue;
339:
340: if (log.isDebugEnabled()) {
341: log.debug("found Managed property '"
342: + propertyName + "'"
343: + " with value '" + value + "'"
344: + " bound to classloader "
345: + classLoader + ".");
346: }
347: }
348: }
349: }
350: }
351: }
352:
353: return value;
354: }
355:
356: private static final ClassLoader getThreadContextClassLoader() {
357: return JDKHooks.getJDKHooks().getThreadContextClassLoader();
358: }
359:
360: private static final ClassLoader getParent(
361: final ClassLoader classLoader) {
362: return (ClassLoader) AccessController
363: .doPrivileged(new PrivilegedAction() {
364: public Object run() {
365: try {
366: return classLoader.getParent();
367: } catch (SecurityException se) {
368: return null;
369: }
370: }
371: });
372: }
373: }
|