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 }