/*
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package com.sun.naming.internal;
import java.io.
InputStream;
import java.io.
IOException;
import java.lang.ref.
WeakReference;
import java.lang.reflect.
Method;
import java.lang.reflect.
InvocationTargetException;
import java.util.
HashMap;
import java.util.
Hashtable;
import java.util.
Map;
import java.util.
Properties;
import java.util.
StringTokenizer;
import java.util.
List;
import java.util.
ArrayList;
import java.util.
WeakHashMap;
import javax.naming.*;
/**
* The ResourceManager class facilitates the reading of JNDI resource files.
*
* @author Rosanna Lee
* @author Scott Seligman
*/
public final class
ResourceManager {
/*
* Name of provider resource files (without the package-name prefix.)
*/
private static final
String PROVIDER_RESOURCE_FILE_NAME =
"jndiprovider.properties";
/*
* Name of application resource files.
*/
private static final
String APP_RESOURCE_FILE_NAME = "jndi.properties";
/*
* Name of properties file in <java.home>/lib.
*/
private static final
String JRELIB_PROPERTY_FILE_NAME = "jndi.properties";
/*
* Internal environment property, that when set to "true", disables
* application resource files lookup to prevent recursion issues
* when validating signed JARs.
*/
private static final
String DISABLE_APP_RESOURCE_FILES =
"com.sun.naming.disable.app.resource.files";
/*
* The standard JNDI properties that specify colon-separated lists.
*/
private static final
String[]
listProperties = {
Context.
OBJECT_FACTORIES,
Context.
URL_PKG_PREFIXES,
Context.
STATE_FACTORIES,
// The following shouldn't create a runtime dependence on ldap package.
javax.naming.ldap.
LdapContext.
CONTROL_FACTORIES
};
private static final
VersionHelper helper =
VersionHelper.
getVersionHelper();
/*
* A cache of the properties that have been constructed by
* the ResourceManager. A Hashtable from a provider resource
* file is keyed on a class in the resource file's package.
* One from application resource files is keyed on the thread's
* context class loader.
*/
// WeakHashMap<Class | ClassLoader, Hashtable>
private static final
WeakHashMap<
Object,
Hashtable<? super
String,
Object>>
propertiesCache = new
WeakHashMap<>(11);
/*
* A cache of factory objects (ObjectFactory, StateFactory, ControlFactory).
*
* A two-level cache keyed first on context class loader and then
* on propValue. Value is a list of class or factory objects,
* weakly referenced so as not to prevent GC of the class loader.
* Used in getFactories().
*/
private static final
WeakHashMap<
ClassLoader,
Map<
String,
List<
NamedWeakReference<
Object>>>>
factoryCache = new
WeakHashMap<>(11);
/*
* A cache of URL factory objects (ObjectFactory).
*
* A two-level cache keyed first on context class loader and then
* on classSuffix+propValue. Value is the factory itself (weakly
* referenced so as not to prevent GC of the class loader) or
* NO_FACTORY if a previous search revealed no factory. Used in
* getFactory().
*/
private static final
WeakHashMap<
ClassLoader,
Map<
String,
WeakReference<
Object>>>
urlFactoryCache = new
WeakHashMap<>(11);
private static final
WeakReference<
Object>
NO_FACTORY =
new
WeakReference<>(null);
/**
* A class to allow JNDI properties be specified as applet parameters
* without creating a static dependency on java.applet.
*/
private static class
AppletParameter {
private static final
Class<?>
clazz =
getClass("java.applet.Applet");
private static final
Method getMethod =
getMethod(
clazz, "getParameter",
String.class);
private static
Class<?>
getClass(
String name) {
try {
return
Class.
forName(
name, true, null);
} catch (
ClassNotFoundException e) {
return null;
}
}
private static
Method getMethod(
Class<?>
clazz,
String name,
Class<?>...
paramTypes)
{
if (
clazz != null) {
try {
return
clazz.
getMethod(
name,
paramTypes);
} catch (
NoSuchMethodException e) {
throw new
AssertionError(
e);
}
} else {
return null;
}
}
/**
* Returns the value of the applet's named parameter.
*/
static
Object get(
Object applet,
String name) {
// if clazz is null then applet cannot be an Applet.
if (
clazz == null || !
clazz.
isInstance(
applet))
throw new
ClassCastException(
applet.
getClass().
getName());
try {
return
getMethod.
invoke(
applet,
name);
} catch (
InvocationTargetException |
IllegalAccessException e) {
throw new
AssertionError(
e);
}
}
}
// There should be no instances of this class.
private
ResourceManager() {
}
// ---------- Public methods ----------
/*
* Given the environment parameter passed to the initial context
* constructor, returns the full environment for that initial
* context (never null). This is based on the environment
* parameter, the applet parameters (where appropriate), the
* system properties, and all application resource files.
*
* <p> This method will modify <tt>env</tt> and save
* a reference to it. The caller may no longer modify it.
*
* @param env environment passed to initial context constructor.
* Null indicates an empty environment.
*
* @throws NamingException if an error occurs while reading a
* resource file
*/
@
SuppressWarnings("unchecked")
public static
Hashtable<?, ?>
getInitialEnvironment(
Hashtable<?, ?>
env)
throws
NamingException
{
String[]
props =
VersionHelper.
PROPS; // system/applet properties
if (
env == null) {
env = new
Hashtable<>(11);
}
Object applet =
env.
get(
Context.
APPLET);
// Merge property values from env param, applet params, and system
// properties. The first value wins: there's no concatenation of
// colon-separated lists.
// Read system properties by first trying System.getProperties(),
// and then trying System.getProperty() if that fails. The former
// is more efficient due to fewer permission checks.
//
String[]
jndiSysProps =
helper.
getJndiProperties();
for (int
i = 0;
i <
props.length;
i++) {
Object val =
env.
get(
props[
i]);
if (
val == null) {
if (
applet != null) {
val =
AppletParameter.
get(
applet,
props[
i]);
}
if (
val == null) {
// Read system property.
val = (
jndiSysProps != null)
?
jndiSysProps[
i]
:
helper.
getJndiProperty(
i);
}
if (
val != null) {
((
Hashtable<
String,
Object>)
env).
put(
props[
i],
val);
}
}
}
// Return without merging if application resource files lookup
// is disabled.
String disableAppRes = (
String)
env.
get(
DISABLE_APP_RESOURCE_FILES);
if (
disableAppRes != null &&
disableAppRes.
equalsIgnoreCase("true")) {
return
env;
}
// Merge the above with the values read from all application
// resource files. Colon-separated lists are concatenated.
mergeTables((
Hashtable<
Object,
Object>)
env,
getApplicationResources());
return
env;
}
/**
* Retrieves the property from the environment, or from the provider
* resource file associated with the given context. The environment
* may in turn contain values that come from applet parameters,
* system properties, or application resource files.
*
* If <tt>concat</tt> is true and both the environment and the provider
* resource file contain the property, the two values are concatenated
* (with a ':' separator).
*
* Returns null if no value is found.
*
* @param propName The non-null property name
* @param env The possibly null environment properties
* @param ctx The possibly null context
* @param concat True if multiple values should be concatenated
* @return the property value, or null is there is none.
* @throws NamingException if an error occurs while reading the provider
* resource file.
*/
public static
String getProperty(
String propName,
Hashtable<?,?>
env,
Context ctx, boolean
concat)
throws
NamingException {
String val1 = (
env != null) ? (
String)
env.
get(
propName) : null;
if ((
ctx == null) ||
((
val1 != null) && !
concat)) {
return
val1;
}
String val2 = (
String)
getProviderResource(
ctx).
get(
propName);
if (
val1 == null) {
return
val2;
} else if ((
val2 == null) || !
concat) {
return
val1;
} else {
return (
val1 + ":" +
val2);
}
}
/**
* Retrieves an enumeration of factory classes/object specified by a
* property.
*
* The property is gotten from the environment and the provider
* resource file associated with the given context and concantenated.
* See getProperty(). The resulting property value is a list of class names.
*<p>
* This method then loads each class using the current thread's context
* class loader and keeps them in a list. Any class that cannot be loaded
* is ignored. The resulting list is then cached in a two-level
* hash table, keyed first by the context class loader and then by
* the property's value.
* The next time threads of the same context class loader call this
* method, they can use the cached list.
*<p>
* After obtaining the list either from the cache or by creating one from
* the property value, this method then creates and returns a
* FactoryEnumeration using the list. As the FactoryEnumeration is
* traversed, the cached Class object in the list is instantiated and
* replaced by an instance of the factory object itself. Both class
* objects and factories are wrapped in weak references so as not to
* prevent GC of the class loader.
*<p>
* Note that multiple threads can be accessing the same cached list
* via FactoryEnumeration, which locks the list during each next().
* The size of the list will not change,
* but a cached Class object might be replaced by an instantiated factory
* object.
*
* @param propName The non-null property name
* @param env The possibly null environment properties
* @param ctx The possibly null context
* @return An enumeration of factory classes/objects; null if none.
* @exception NamingException If encounter problem while reading the provider
* property file.
* @see javax.naming.spi.NamingManager#getObjectInstance
* @see javax.naming.spi.NamingManager#getStateToBind
* @see javax.naming.spi.DirectoryManager#getObjectInstance
* @see javax.naming.spi.DirectoryManager#getStateToBind
* @see javax.naming.ldap.ControlFactory#getControlInstance
*/
public static
FactoryEnumeration getFactories(
String propName,
Hashtable<?,?>
env,
Context ctx) throws
NamingException {
String facProp =
getProperty(
propName,
env,
ctx, true);
if (
facProp == null)
return null; // no classes specified; return null
// Cache is based on context class loader and property val
ClassLoader loader =
helper.
getContextClassLoader();
Map<
String,
List<
NamedWeakReference<
Object>>>
perLoaderCache = null;
synchronized (
factoryCache) {
perLoaderCache =
factoryCache.
get(
loader);
if (
perLoaderCache == null) {
perLoaderCache = new
HashMap<>(11);
factoryCache.
put(
loader,
perLoaderCache);
}
}
synchronized (
perLoaderCache) {
List<
NamedWeakReference<
Object>>
factories =
perLoaderCache.
get(
facProp);
if (
factories != null) {
// Cached list
return
factories.
size() == 0 ? null
: new
FactoryEnumeration(
factories,
loader);
} else {
// Populate list with classes named in facProp; skipping
// those that we cannot load
StringTokenizer parser = new
StringTokenizer(
facProp, ":");
factories = new
ArrayList<>(5);
while (
parser.
hasMoreTokens()) {
try {
// System.out.println("loading");
String className =
parser.
nextToken();
Class<?>
c =
helper.
loadClass(
className,
loader);
factories.
add(new
NamedWeakReference<
Object>(
c,
className));
} catch (
Exception e) {
// ignore ClassNotFoundException, IllegalArgumentException
}
}
// System.out.println("adding to cache: " + factories);
perLoaderCache.
put(
facProp,
factories);
return new
FactoryEnumeration(
factories,
loader);
}
}
}
/**
* Retrieves a factory from a list of packages specified in a
* property.
*
* The property is gotten from the environment and the provider
* resource file associated with the given context and concatenated.
* classSuffix is added to the end of this list.
* See getProperty(). The resulting property value is a list of package
* prefixes.
*<p>
* This method then constructs a list of class names by concatenating
* each package prefix with classSuffix and attempts to load and
* instantiate the class until one succeeds.
* Any class that cannot be loaded is ignored.
* The resulting object is then cached in a two-level hash table,
* keyed first by the context class loader and then by the property's
* value and classSuffix.
* The next time threads of the same context class loader call this
* method, they use the cached factory.
* If no factory can be loaded, NO_FACTORY is recorded in the table
* so that next time it'll return quickly.
*
* @param propName The non-null property name
* @param env The possibly null environment properties
* @param ctx The possibly null context
* @param classSuffix The non-null class name
* (e.g. ".ldap.ldapURLContextFactory).
* @param defaultPkgPrefix The non-null default package prefix.
* (e.g., "com.sun.jndi.url").
* @return An factory object; null if none.
* @exception NamingException If encounter problem while reading the provider
* property file, or problem instantiating the factory.
*
* @see javax.naming.spi.NamingManager#getURLContext
* @see javax.naming.spi.NamingManager#getURLObject
*/
public static
Object getFactory(
String propName,
Hashtable<?,?>
env,
Context ctx,
String classSuffix,
String defaultPkgPrefix)
throws
NamingException {
// Merge property with provider property and supplied default
String facProp =
getProperty(
propName,
env,
ctx, true);
if (
facProp != null)
facProp += (":" +
defaultPkgPrefix);
else
facProp =
defaultPkgPrefix;
// Cache factory based on context class loader, class name, and
// property val
ClassLoader loader =
helper.
getContextClassLoader();
String key =
classSuffix + " " +
facProp;
Map<
String,
WeakReference<
Object>>
perLoaderCache = null;
synchronized (
urlFactoryCache) {
perLoaderCache =
urlFactoryCache.
get(
loader);
if (
perLoaderCache == null) {
perLoaderCache = new
HashMap<>(11);
urlFactoryCache.
put(
loader,
perLoaderCache);
}
}
synchronized (
perLoaderCache) {
Object factory = null;
WeakReference<
Object>
factoryRef =
perLoaderCache.
get(
key);
if (
factoryRef ==
NO_FACTORY) {
return null;
} else if (
factoryRef != null) {
factory =
factoryRef.
get();
if (
factory != null) { // check if weak ref has been cleared
return
factory;
}
}
// Not cached; find first factory and cache
StringTokenizer parser = new
StringTokenizer(
facProp, ":");
String className;
while (
factory == null &&
parser.
hasMoreTokens()) {
className =
parser.
nextToken() +
classSuffix;
try {
// System.out.println("loading " + className);
factory =
helper.
loadClass(
className,
loader).
newInstance();
} catch (
InstantiationException e) {
NamingException ne =
new
NamingException("Cannot instantiate " +
className);
ne.
setRootCause(
e);
throw
ne;
} catch (
IllegalAccessException e) {
NamingException ne =
new
NamingException("Cannot access " +
className);
ne.
setRootCause(
e);
throw
ne;
} catch (
Exception e) {
// ignore ClassNotFoundException, IllegalArgumentException,
// etc.
}
}
// Cache it.
perLoaderCache.
put(
key, (
factory != null)
? new
WeakReference<>(
factory)
:
NO_FACTORY);
return
factory;
}
}
// ---------- Private methods ----------
/*
* Returns the properties contained in the provider resource file
* of an object's package. Returns an empty hash table if the
* object is null or the resource file cannot be found. The
* results are cached.
*
* @throws NamingException if an error occurs while reading the file.
*/
private static
Hashtable<? super
String,
Object>
getProviderResource(
Object obj)
throws
NamingException
{
if (
obj == null) {
return (new
Hashtable<>(1));
}
synchronized (
propertiesCache) {
Class<?>
c =
obj.
getClass();
Hashtable<? super
String,
Object>
props =
propertiesCache.
get(
c);
if (
props != null) {
return
props;
}
props = new
Properties();
InputStream istream =
helper.
getResourceAsStream(
c,
PROVIDER_RESOURCE_FILE_NAME);
if (
istream != null) {
try {
((
Properties)
props).
load(
istream);
} catch (
IOException e) {
NamingException ne = new
ConfigurationException(
"Error reading provider resource file for " +
c);
ne.
setRootCause(
e);
throw
ne;
}
}
propertiesCache.
put(
c,
props);
return
props;
}
}
/*
* Returns the Hashtable (never null) that results from merging
* all application resource files available to this thread's
* context class loader. The properties file in <java.home>/lib
* is also merged in. The results are cached.
*
* SECURITY NOTES:
* 1. JNDI needs permission to read the application resource files.
* 2. Any class will be able to use JNDI to view the contents of
* the application resource files in its own classpath. Give
* careful consideration to this before storing sensitive
* information there.
*
* @throws NamingException if an error occurs while reading a resource
* file.
*/
private static
Hashtable<? super
String,
Object>
getApplicationResources()
throws
NamingException {
ClassLoader cl =
helper.
getContextClassLoader();
synchronized (
propertiesCache) {
Hashtable<? super
String,
Object>
result =
propertiesCache.
get(
cl);
if (
result != null) {
return
result;
}
try {
NamingEnumeration<
InputStream>
resources =
helper.
getResources(
cl,
APP_RESOURCE_FILE_NAME);
try {
while (
resources.
hasMore()) {
Properties props = new
Properties();
InputStream istream =
resources.
next();
try {
props.
load(
istream);
} finally {
istream.
close();
}
if (
result == null) {
result =
props;
} else {
mergeTables(
result,
props);
}
}
} finally {
while (
resources.
hasMore()) {
resources.
next().
close();
}
}
// Merge in properties from file in <java.home>/lib.
InputStream istream =
helper.
getJavaHomeLibStream(
JRELIB_PROPERTY_FILE_NAME);
if (
istream != null) {
try {
Properties props = new
Properties();
props.
load(
istream);
if (
result == null) {
result =
props;
} else {
mergeTables(
result,
props);
}
} finally {
istream.
close();
}
}
} catch (
IOException e) {
NamingException ne = new
ConfigurationException(
"Error reading application resource file");
ne.
setRootCause(
e);
throw
ne;
}
if (
result == null) {
result = new
Hashtable<>(11);
}
propertiesCache.
put(
cl,
result);
return
result;
}
}
/*
* Merge the properties from one hash table into another. Each
* property in props2 that is not in props1 is added to props1.
* For each property in both hash tables that is one of the
* standard JNDI properties that specify colon-separated lists,
* the values are concatenated and stored in props1.
*/
private static void
mergeTables(
Hashtable<? super
String,
Object>
props1,
Hashtable<? super
String,
Object>
props2) {
for (
Object key :
props2.
keySet()) {
String prop = (
String)
key;
Object val1 =
props1.
get(
prop);
if (
val1 == null) {
props1.
put(
prop,
props2.
get(
prop));
} else if (
isListProperty(
prop)) {
String val2 = (
String)
props2.
get(
prop);
props1.
put(
prop, ((
String)
val1) + ":" +
val2);
}
}
}
/*
* Is a property one of the standard JNDI properties that specify
* colon-separated lists?
*/
private static boolean
isListProperty(
String prop) {
prop =
prop.
intern();
for (int
i = 0;
i <
listProperties.length;
i++) {
if (
prop ==
listProperties[
i]) {
return true;
}
}
return false;
}
}