/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jini.config;
import com.sun.jini.logging.Levels;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.security.Security;
/**
* Provides a standard means for obtaining {@link Configuration} instances,
* using a configurable provider. This class cannot be instantiated. The
* configuration provider can be specified by providing a resource named
* "META-INF/services/net.jini.config.Configuration" containing the name of the
* provider class. If multiple resources with that name are available, then the
* one used will be the last one returned by {@link ClassLoader#getResources
* ClassLoader.getResources}. If the resource is not found, the {@link
* ConfigurationFile} class is used. <p>
*
* Downloaded code can specify its own class loader in a call to {@link
* #getInstance(String[],ClassLoader) getInstance(String[], ClassLoader)} in
* order to use the configuration provider specified in the JAR file from which
* it was downloaded. <p>
*
* The provider class must be a public, non-abstract class that implements
* <code>Configuration</code>, and has a public constructor that has
* <code>String[]</code> and <code>ClassLoader</code> parameters. The
* <code>String[]</code> parameter supplies provider-specific options, for
* example specifying a source location or values for particular entries. The
* constructor should throw {@link ConfigurationNotFoundException} if the
* configuration specified by the <code>String[]</code> options argument cannot
* be found, or if <code>null</code> was specified for the options argument and
* the class does not provide defaults. The constructor should use the class
* loader argument when loading resources and classes, and should interpret a
* <code>null</code> class loader argument as signifying the context class
* loader. <p>
*
* The resource file should contain the fully qualified name of the provider
* class. Space and tab characters surrounding each name, as well as blank
* lines, are ignored. The comment character is <tt>'#'</tt> (<tt>0x23</tt>);
* on each line, all characters following the first comment character are
* ignored. The resource file must be encoded in UTF-8.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*
* @com.sun.jini.impl <!-- Implementation Specifics -->
*
* This implementation uses the {@link Logger} named
* <code>net.jini.config</code> to log information at the following logging
* levels: <p>
*
* <table border="1" cellpadding="5" summary="Describes logging performed by
* the ConfigurationProvider class at different logging levels">
*
* <caption halign="center" valign="top"><b><code>
* net.jini.config</code></b></caption>
*
* <tr> <th scope="col"> Level <th scope="col"> Description
*
* <tr> <td> {@link Levels#FAILED FAILED} <td> problems getting a configuration
*
* </table>
*/
public class ConfigurationProvider {
private static final String resourceName =
"META-INF/services/" + Configuration.class.getName();
/** Config logger. */
private static final Logger logger = Logger.getLogger("net.jini.config");
/** This class cannot be instantiated. */
private ConfigurationProvider() {
throw new AssertionError();
}
/**
* Creates and returns an instance of the configuration provider, using the
* specified options. Specifying <code>null</code> for <code>options</code>
* uses provider-specific default options, if available. Uses the current
* thread's context class loader to load resources and classes, and
* supplies <code>null</code> as the class loader to the provider
* constructor.
*
* @param options values to use when constructing the configuration, or
* <code>null</code> if default options should be used
* @return an instance of the configuration provider constructed with the
* specified options and <code>null</code> for the class loader
* @throws ConfigurationNotFoundException if <code>options</code> specifies
* a source location that cannot be found, or if <code>options</code> is
* <code>null</code> and the provider does not supply default options
* @throws ConfigurationException if an I/O exception occurs; if there are
* problems with the <code>options</code> argument or the format of the
* contents of any source location it specifies; if there is a problem with
* the contents of the resource file that names the configuration provider;
* if the configured provider class does not exist, is not public, is
* abstract, does not implement <code>Configuration</code>, or does not
* have a public constructor that has <code>String[]</code> and
* <code>ClassLoader</code> parameters; if the calling thread does not have
* permission to obtain information from the specified source; or if the
* provider does not have permission to access the context class
* loader. Any <code>Error</code> thrown while creating the provider
* instance is propagated to the caller; it is not wrapped in a
* <code>ConfigurationException</code>.
*/
public static Configuration getInstance(String[] options)
throws ConfigurationException
{
return getInstance(options, null);
}
/**
* Creates and returns an instance of the configuration provider, using the
* specified options and class loader. Specifying <code>null</code> for
* <code>options</code> uses provider-specific default options, if
* available. Uses the specified class loader to load resources and
* classes, or the current thread's context class loader if <code>cl</code>
* is <code>null</code>. Supplies <code>cl</code> as the class loader to
* the provider constructor. <p>
*
* Downloaded code can specify its own class loader in a call to this
* method in order to use the configuration provider specified in the JAR
* file from which it was downloaded.
*
* @param options values to use when constructing the configuration, or
* <code>null</code> if default options should be used
* @param cl the class loader to load resources and classes, and to pass
* when constructing the provider. If <code>null</code>, uses the context
* class loader.
* @return an instance of the configuration provider constructed with the
* specified options and class loader
* @throws ConfigurationNotFoundException if <code>options</code> specifies
* a source location that cannot be found, or if <code>options</code> is
* <code>null</code> and the provider does not supply default options
* @throws ConfigurationException if an I/O exception occurs; if there are
* problems with the <code>options</code> argument or the format of the
* contents of any source location it specifies; if there is a problem with
* the contents of the resource file that names the configuration provider;
* if the configured provider class does not exist, is not public, is
* abstract, does not implement <code>Configuration</code>, or does not
* have a public constructor that has <code>String[]</code> and
* <code>ClassLoader</code> parameters; if the calling thread does not have
* permission to obtain information from the specified source; or if
* <code>cl</code> is <code>null</code> and the provider does not have
* permission to access the context class loader. Any <code>Error</code>
* thrown while creating the provider instance is propagated to the caller;
* it is not wrapped in a <code>ConfigurationException</code>.
*/
public static Configuration getInstance(String[] options, ClassLoader cl)
throws ConfigurationException
{
ClassLoader resourceLoader = (cl != null) ? cl :
(ClassLoader) Security.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Thread.currentThread().getContextClassLoader();
}
});
final ClassLoader finalResourceLoader = (resourceLoader == null)
? Utilities.bootstrapResourceLoader : resourceLoader;
String cname = null;
ConfigurationException configEx = null;
try {
cname = (String) Security.doPrivileged(
new PrivilegedExceptionAction() {
public Object run()
throws ConfigurationException, IOException
{
URL resource = null;
Enumeration providers =
finalResourceLoader.getResources(resourceName);
while (providers.hasMoreElements()) {
resource = (URL) providers.nextElement();
}
return (resource == null)
? null : getProviderName(resource);
}
});
} catch (PrivilegedActionException e) {
Exception e2 = e.getException();
if (e2 instanceof ConfigurationException) {
configEx = (ConfigurationException) e2;
} else {
configEx = new ConfigurationException(
"problem accessing provider resources", e2);
}
} catch (RuntimeException e) {
configEx = new ConfigurationException(
"problem accessing provider resources", e);
}
if (configEx != null) {
logger.log(Levels.FAILED, "getting configuration provider throws",
configEx);
throw configEx;
}
if (cname == null) {
return new ConfigurationFile(options, cl);
}
try {
Class cls = Class.forName(cname, true, resourceLoader);
if (!Configuration.class.isAssignableFrom(cls)) {
configEx = new ConfigurationException(
"provider class " + cname +
" does not implement Configuration");
} else {
Constructor cons = cls.getConstructor(
new Class[] { String[].class, ClassLoader.class });
return (Configuration) cons.newInstance(
new Object[] { options, cl });
}
} catch (ClassNotFoundException e) {
configEx = new ConfigurationException(
"provider class " + cname + " not found");
} catch (NoSuchMethodException e) {
configEx = new ConfigurationException(
"provider class " + cname +
" does not have the right constructor");
} catch (IllegalAccessException e) {
configEx = new ConfigurationException(
"provider class " + cname + " constructor is not public");
} catch (InstantiationException e) {
configEx = new ConfigurationException(
"provider class " + cname + " is abstract");
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof ConfigurationException) {
configEx = (ConfigurationException) t;
} else {
configEx = new ConfigurationException(
"problem with provider class", t);
}
} catch (RuntimeException e) {
configEx = new ConfigurationException(
"problem with provider class", e);
}
logger.log(Levels.FAILED, "getting configuration throws", configEx);
throw configEx;
}
/**
* Returns the configuration provider class name specified in the contents
* of the URL.
*/
private static String getProviderName(URL url)
throws ConfigurationException, IOException
{
InputStream in = null;
try {
in = url.openStream();
BufferedReader reader =
new BufferedReader(new InputStreamReader(in, "utf-8"));
String result = null;
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
int commentPos = line.indexOf('#');
if (commentPos >= 0) {
line = line.substring(0, commentPos);
}
line = line.trim();
int len = line.length();
if (len != 0) {
if (result != null) {
throw new ConfigurationException(
"resource specifies multiple providers");
}
result = line;
}
}
if (result == null) {
throw new ConfigurationException(
"resource specifies no providers");
}
return result;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
}