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