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 }