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 }