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