1. /*
  2. * @(#)ResourceManager.java 1.12 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.naming.internal;
  8. import java.applet.Applet;
  9. import java.io.InputStream;
  10. import java.io.IOException;
  11. import java.net.URL;
  12. import java.lang.ref.WeakReference;
  13. import java.util.Enumeration;
  14. import java.util.HashMap;
  15. import java.util.Hashtable;
  16. import java.util.Map;
  17. import java.util.Properties;
  18. import java.util.StringTokenizer;
  19. import java.util.List;
  20. import java.util.ArrayList;
  21. import java.util.WeakHashMap;
  22. import javax.naming.*;
  23. /**
  24. * The ResourceManager class facilitates the reading of JNDI resource files.
  25. *
  26. * @author Rosanna Lee
  27. * @author Scott Seligman
  28. * @version 1.12 03/01/23
  29. */
  30. public final class ResourceManager {
  31. /*
  32. * Name of provider resource files (without the package-name prefix.)
  33. */
  34. private static final String PROVIDER_RESOURCE_FILE_NAME =
  35. "jndiprovider.properties";
  36. /*
  37. * Name of application resource files.
  38. */
  39. private static final String APP_RESOURCE_FILE_NAME = "jndi.properties";
  40. /*
  41. * Name of properties file in <java.home>/lib.
  42. */
  43. private static final String JRELIB_PROPERTY_FILE_NAME = "jndi.properties";
  44. /*
  45. * The standard JNDI properties that specify colon-separated lists.
  46. */
  47. private static final String[] listProperties = {
  48. Context.OBJECT_FACTORIES,
  49. Context.URL_PKG_PREFIXES,
  50. Context.STATE_FACTORIES,
  51. // The following shouldn't create a runtime dependence on ldap package.
  52. javax.naming.ldap.LdapContext.CONTROL_FACTORIES
  53. };
  54. private static final VersionHelper helper =
  55. VersionHelper.getVersionHelper();
  56. /*
  57. * A cache of the properties that have been constructed by
  58. * the ResourceManager. A Hashtable from a provider resource
  59. * file is keyed on a class in the resource file's package.
  60. * One from application resource files is keyed on the thread's
  61. * context class loader.
  62. */
  63. private static final WeakHashMap propertiesCache = new WeakHashMap(11);
  64. /*
  65. * A cache of factory objects (ObjectFactory, StateFactory, ControlFactory).
  66. *
  67. * A two-level cache keyed first on context class loader and then
  68. * on propValue. Value is a list of class or factory objects,
  69. * weakly referenced so as not to prevent GC of the class loader.
  70. * Used in getFactories().
  71. */
  72. private static final WeakHashMap factoryCache = new WeakHashMap(11);
  73. /*
  74. * A cache of URL factory objects (ObjectFactory).
  75. *
  76. * A two-level cache keyed first on context class loader and then
  77. * on classSuffix+propValue. Value is the factory itself (weakly
  78. * referenced so as not to prevent GC of the class loader) or
  79. * NO_FACTORY if a previous search revealed no factory. Used in
  80. * getFactory().
  81. */
  82. private static final WeakHashMap urlFactoryCache = new WeakHashMap(11);
  83. private static final WeakReference NO_FACTORY = new WeakReference(null);
  84. // There should be no instances of this class.
  85. private ResourceManager() {
  86. }
  87. // ---------- Public methods ----------
  88. /*
  89. * Given the environment parameter passed to the initial context
  90. * constructor, returns the full environment for that initial
  91. * context (never null). This is based on the environment
  92. * parameter, the applet parameters (where appropriate), the
  93. * system properties, and all application resource files.
  94. *
  95. * <p> This method will modify <tt>env</tt> and save
  96. * a reference to it. The caller may no longer modify it.
  97. *
  98. * @param env environment passed to initial context constructor.
  99. * Null indicates an empty environment.
  100. *
  101. * @throws NamingException if an error occurs while reading a
  102. * resource file
  103. */
  104. public static Hashtable getInitialEnvironment(Hashtable env)
  105. throws NamingException
  106. {
  107. String[] props = VersionHelper.PROPS; // system/applet properties
  108. if (env == null) {
  109. env = new Hashtable(11);
  110. }
  111. Applet applet = (Applet)env.get(Context.APPLET);
  112. // Merge property values from env param, applet params, and system
  113. // properties. The first value wins: there's no concatenation of
  114. // colon-separated lists.
  115. // Read system properties by first trying System.getProperties(),
  116. // and then trying System.getProperty() if that fails. The former
  117. // is more efficient due to fewer permission checks.
  118. //
  119. String[] jndiSysProps = helper.getJndiProperties();
  120. for (int i = 0; i < props.length; i++) {
  121. Object val = env.get(props[i]);
  122. if (val == null) {
  123. if (applet != null) {
  124. val = applet.getParameter(props[i]);
  125. }
  126. if (val == null) {
  127. // Read system property.
  128. val = (jndiSysProps != null)
  129. ? jndiSysProps[i]
  130. : helper.getJndiProperty(i);
  131. }
  132. if (val != null) {
  133. env.put(props[i], val);
  134. }
  135. }
  136. }
  137. // Merge the above with the values read from all application
  138. // resource files. Colon-separated lists are concatenated.
  139. mergeTables(env, getApplicationResources());
  140. return env;
  141. }
  142. /**
  143. * Retrieves the property from the environment, or from the provider
  144. * resource file associated with the given context. The environment
  145. * may in turn contain values that come from applet parameters,
  146. * system properties, or application resource files.
  147. *
  148. * If <tt>concat</tt> is true and both the environment and the provider
  149. * resource file contain the property, the two values are concatenated
  150. * (with a ':' separator).
  151. *
  152. * Returns null if no value is found.
  153. *
  154. * @param propName The non-null property name
  155. * @param env The possibly null environment properties
  156. * @param ctx The possibly null context
  157. * @param concat True if multiple values should be concatenated
  158. * @return the property value, or null is there is none.
  159. * @throws NamingException if an error occurs while reading the provider
  160. * resource file.
  161. */
  162. public static String getProperty(String propName, Hashtable env,
  163. Context ctx, boolean concat)
  164. throws NamingException {
  165. String val1 = (env != null) ? (String)env.get(propName) : null;
  166. if ((ctx == null) ||
  167. ((val1 != null) && !concat)) {
  168. return val1;
  169. }
  170. String val2 = (String)getProviderResource(ctx).get(propName);
  171. if (val1 == null) {
  172. return val2;
  173. } else if ((val2 == null) || !concat) {
  174. return val1;
  175. } else {
  176. return (val1 + ":" + val2);
  177. }
  178. }
  179. /**
  180. * Retrieves an enumeration of factory classes/object specified by a
  181. * property.
  182. *
  183. * The property is gotten from the environment and the provider
  184. * resource file associated with the given context and concantenated.
  185. * See getProperty(). The resulting property value is a list of class names.
  186. *<p>
  187. * This method then loads each class using the current thread's context
  188. * class loader and keeps them in a list. Any class that cannot be loaded
  189. * is ignored. The resulting list is then cached in a two-level
  190. * hash table, keyed first by the context class loader and then by
  191. * the property's value.
  192. * The next time threads of the same context class loader call this
  193. * method, they can use the cached list.
  194. *<p>
  195. * After obtaining the list either from the cache or by creating one from
  196. * the property value, this method then creates and returns a
  197. * FactoryEnumeration using the list. As the FactoryEnumeration is
  198. * traversed, the cached Class object in the list is instantiated and
  199. * replaced by an instance of the factory object itself. Both class
  200. * objects and factories are wrapped in weak references so as not to
  201. * prevent GC of the class loader.
  202. *<p>
  203. * Note that multiple threads can be accessing the same cached list
  204. * via FactoryEnumeration, which locks the list during each next().
  205. * The size of the list will not change,
  206. * but a cached Class object might be replaced by an instantiated factory
  207. * object.
  208. *
  209. * @param propName The non-null property name
  210. * @param env The possibly null environment properties
  211. * @param ctx The possibly null context
  212. * @return An enumeration of factory classes/objects; null if none.
  213. * @exception NamingException If encounter problem while reading the provider
  214. * property file.
  215. * @see javax.naming.spi.NamingManager#getObjectInstance
  216. * @see javax.naming.spi.NamingManager#getStateToBind
  217. * @see javax.naming.spi.DirectoryManager#getObjectInstance
  218. * @see javax.naming.spi.DirectoryManager#getStateToBind
  219. * @see javax.naming.ldap.ControlFactory#getControlInstance
  220. */
  221. public static FactoryEnumeration getFactories(String propName, Hashtable env,
  222. Context ctx) throws NamingException {
  223. String facProp = getProperty(propName, env, ctx, true);
  224. if (facProp == null)
  225. return null; // no classes specified; return null
  226. // Cache is based on context class loader and property val
  227. ClassLoader loader = helper.getContextClassLoader();
  228. Map perLoaderCache = null;
  229. synchronized (factoryCache) {
  230. perLoaderCache = (Map) factoryCache.get(loader);
  231. if (perLoaderCache == null) {
  232. perLoaderCache = new HashMap(11);
  233. factoryCache.put(loader, perLoaderCache);
  234. }
  235. }
  236. synchronized (perLoaderCache) {
  237. List factories = (List) perLoaderCache.get(facProp);
  238. if (factories != null) {
  239. // Cached list
  240. return factories.size() == 0 ? null
  241. : new FactoryEnumeration(factories, loader);
  242. } else {
  243. // Populate list with classes named in facProp; skipping
  244. // those that we cannot load
  245. StringTokenizer parser = new StringTokenizer(facProp, ":");
  246. factories = new ArrayList(5);
  247. while (parser.hasMoreTokens()) {
  248. try {
  249. // System.out.println("loading");
  250. String className = parser.nextToken();
  251. Class c = helper.loadClass(className, loader);
  252. factories.add(new NamedWeakReference(c, className));
  253. } catch (Exception e) {
  254. // ignore ClassNotFoundException, IllegalArgumentException
  255. }
  256. }
  257. // System.out.println("adding to cache: " + factories);
  258. perLoaderCache.put(facProp, factories);
  259. return new FactoryEnumeration(factories, loader);
  260. }
  261. }
  262. }
  263. /**
  264. * Retrieves a factory from a list of packages specified in a
  265. * property.
  266. *
  267. * The property is gotten from the environment and the provider
  268. * resource file associated with the given context and concatenated.
  269. * classSuffix is added to the end of this list.
  270. * See getProperty(). The resulting property value is a list of package
  271. * prefixes.
  272. *<p>
  273. * This method then constructs a list of class names by concatenating
  274. * each package prefix with classSuffix and attempts to load and
  275. * instantiate the class until one succeeds.
  276. * Any class that cannot be loaded is ignored.
  277. * The resulting object is then cached in a two-level hash table,
  278. * keyed first by the context class loader and then by the property's
  279. * value and classSuffix.
  280. * The next time threads of the same context class loader call this
  281. * method, they use the cached factory.
  282. * If no factory can be loaded, NO_FACTORY is recorded in the table
  283. * so that next time it'll return quickly.
  284. *
  285. * @param propName The non-null property name
  286. * @param env The possibly null environment properties
  287. * @param ctx The possibly null context
  288. * @param classSuffix The non-null class name
  289. * (e.g. ".ldap.ldapURLContextFactory).
  290. * @param defaultPkgPrefix The non-null default package prefix.
  291. * (e.g., "com.sun.jndi.url").
  292. * @return An factory object; null if none.
  293. * @exception NamingException If encounter problem while reading the provider
  294. * property file, or problem instantiating the factory.
  295. *
  296. * @see javax.naming.spi.NamingManager#getURLContext
  297. * @see javax.naming.spi.NamingManager#getURLObject
  298. */
  299. public static Object getFactory(String propName, Hashtable env, Context ctx,
  300. String classSuffix, String defaultPkgPrefix) throws NamingException {
  301. // Merge property with provider property and supplied default
  302. String facProp = getProperty(propName, env, ctx, true);
  303. if (facProp != null)
  304. facProp += (":" + defaultPkgPrefix);
  305. else
  306. facProp = defaultPkgPrefix;
  307. // Cache factory based on context class loader, class name, and
  308. // property val
  309. ClassLoader loader = helper.getContextClassLoader();
  310. String key = classSuffix + " " + facProp;
  311. Map perLoaderCache = null;
  312. synchronized (urlFactoryCache) {
  313. perLoaderCache = (Map) urlFactoryCache.get(loader);
  314. if (perLoaderCache == null) {
  315. perLoaderCache = new HashMap(11);
  316. urlFactoryCache.put(loader, perLoaderCache);
  317. }
  318. }
  319. synchronized (perLoaderCache) {
  320. Object factory = null;
  321. WeakReference factoryRef = (WeakReference) perLoaderCache.get(key);
  322. if (factoryRef == NO_FACTORY) {
  323. return null;
  324. } else if (factoryRef != null) {
  325. factory = factoryRef.get();
  326. if (factory != null) { // check if weak ref has been cleared
  327. return factory;
  328. }
  329. }
  330. // Not cached; find first factory and cache
  331. StringTokenizer parser = new StringTokenizer(facProp, ":");
  332. String className;
  333. while (factory == null && parser.hasMoreTokens()) {
  334. className = parser.nextToken() + classSuffix;
  335. try {
  336. // System.out.println("loading " + className);
  337. factory = helper.loadClass(className, loader).newInstance();
  338. } catch (InstantiationException e) {
  339. NamingException ne =
  340. new NamingException("Cannot instantiate " + className);
  341. ne.setRootCause(e);
  342. throw ne;
  343. } catch (IllegalAccessException e) {
  344. NamingException ne =
  345. new NamingException("Cannot access " + className);
  346. ne.setRootCause(e);
  347. throw ne;
  348. } catch (Exception e) {
  349. // ignore ClassNotFoundException, IllegalArgumentException,
  350. // etc.
  351. }
  352. }
  353. // Cache it.
  354. perLoaderCache.put(key, (factory != null)
  355. ? new WeakReference(factory)
  356. : NO_FACTORY);
  357. return factory;
  358. }
  359. }
  360. // ---------- Private methods ----------
  361. /*
  362. * Returns the properties contained in the provider resource file
  363. * of an object's package. Returns an empty hash table if the
  364. * object is null or the resource file cannot be found. The
  365. * results are cached.
  366. *
  367. * @throws NamingException if an error occurs while reading the file.
  368. */
  369. private static Hashtable getProviderResource(Object obj)
  370. throws NamingException
  371. {
  372. if (obj == null) {
  373. return (new Hashtable(1));
  374. }
  375. synchronized (propertiesCache) {
  376. Class c = obj.getClass();
  377. Hashtable props = (Hashtable)propertiesCache.get(c);
  378. if (props != null) {
  379. return props;
  380. }
  381. props = new Properties();
  382. InputStream istream =
  383. helper.getResourceAsStream(c, PROVIDER_RESOURCE_FILE_NAME);
  384. if (istream != null) {
  385. try {
  386. ((Properties)props).load(istream);
  387. } catch (IOException e) {
  388. NamingException ne = new ConfigurationException(
  389. "Error reading provider resource file for " + c);
  390. ne.setRootCause(e);
  391. throw ne;
  392. }
  393. }
  394. propertiesCache.put(c, props);
  395. return props;
  396. }
  397. }
  398. /*
  399. * Returns the Hashtable (never null) that results from merging
  400. * all application resource files available to this thread's
  401. * context class loader. The properties file in <java.home>/lib
  402. * is also merged in. The results are cached.
  403. *
  404. * SECURITY NOTES:
  405. * 1. JNDI needs permission to read the application resource files.
  406. * 2. Any class will be able to use JNDI to view the contents of
  407. * the application resource files in its own classpath. Give
  408. * careful consideration to this before storing sensitive
  409. * information there.
  410. *
  411. * @throws NamingException if an error occurs while reading a resource
  412. * file.
  413. */
  414. private static Hashtable getApplicationResources() throws NamingException {
  415. ClassLoader cl = helper.getContextClassLoader();
  416. synchronized (propertiesCache) {
  417. Hashtable result = (Hashtable)propertiesCache.get(cl);
  418. if (result != null) {
  419. return result;
  420. }
  421. try {
  422. NamingEnumeration resources =
  423. helper.getResources(cl, APP_RESOURCE_FILE_NAME);
  424. while (resources.hasMore()) {
  425. Properties props = new Properties();
  426. props.load((InputStream)resources.next());
  427. if (result == null) {
  428. result = props;
  429. } else {
  430. mergeTables(result, props);
  431. }
  432. }
  433. // Merge in properties from file in <java.home>/lib.
  434. InputStream istream =
  435. helper.getJavaHomeLibStream(JRELIB_PROPERTY_FILE_NAME);
  436. if (istream != null) {
  437. Properties props = new Properties();
  438. props.load(istream);
  439. if (result == null) {
  440. result = props;
  441. } else {
  442. mergeTables(result, props);
  443. }
  444. }
  445. } catch (IOException e) {
  446. NamingException ne = new ConfigurationException(
  447. "Error reading application resource file");
  448. ne.setRootCause(e);
  449. throw ne;
  450. }
  451. if (result == null) {
  452. result = new Hashtable(11);
  453. }
  454. propertiesCache.put(cl, result);
  455. return result;
  456. }
  457. }
  458. /*
  459. * Merge the properties from one hash table into another. Each
  460. * property in props2 that is not in props1 is added to props1.
  461. * For each property in both hash tables that is one of the
  462. * standard JNDI properties that specify colon-separated lists,
  463. * the values are concatenated and stored in props1.
  464. */
  465. private static void mergeTables(Hashtable props1, Hashtable props2) {
  466. Enumeration keys = props2.keys();
  467. while (keys.hasMoreElements()) {
  468. String prop = (String)keys.nextElement();
  469. Object val1 = props1.get(prop);
  470. if (val1 == null) {
  471. props1.put(prop, props2.get(prop));
  472. } else if (isListProperty(prop)) {
  473. String val2 = (String)props2.get(prop);
  474. props1.put(prop, ((String)val1) + ":" + val2);
  475. }
  476. }
  477. }
  478. /*
  479. * Is a property one of the standard JNDI properties that specify
  480. * colon-separated lists?
  481. */
  482. private static boolean isListProperty(String prop) {
  483. prop = prop.intern();
  484. for (int i = 0; i < listProperties.length; i++) {
  485. if (prop == listProperties[i]) {
  486. return true;
  487. }
  488. }
  489. return false;
  490. }
  491. }