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 }