8131667: JAX-WS Plugability Layer: using java.util.ServiceLoader

Reviewed-by: alanb
This commit is contained in:
Miroslav Kos 2015-09-18 13:46:58 +02:00
parent 9969836a33
commit 08633d3fe9
5 changed files with 234 additions and 201 deletions

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* This package contains the core JAX-WS APIs.
*/
package javax.xml.ws;

View File

@ -1,30 +0,0 @@
<!--
Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
This code is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 only, as
published by the Free Software Foundation. Oracle designates this
particular file as subject to the "Classpath" exception as provided
by Oracle in the LICENSE file that accompanied this code.
This code is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
version 2 for more details (a copy is included in the LICENSE file that
accompanied this code).
You should have received a copy of the GNU General Public License version
2 along with this work; if not, write to the Free Software Foundation,
Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
or visit www.oracle.com if you need additional information or have any
questions.
-->
<html>
<body>
This package contains the core JAX-WS APIs.
</body>
</html>

View File

@ -27,33 +27,25 @@ package javax.xml.ws.spi;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.ws.WebServiceException;
class FactoryFinder {
/**
* Creates an instance of the specified class using the specified
* {@code ClassLoader} object.
*
* @exception WebServiceException if the given class could not be found
* or could not be instantiated
*/
private static Object newInstance(String className,
ClassLoader classLoader)
{
try {
Class spiClass = safeLoadClass(className, classLoader);
return spiClass.newInstance();
} catch (ClassNotFoundException x) {
throw new WebServiceException(
"Provider " + className + " not found", x);
} catch (Exception x) {
throw new WebServiceException(
"Provider " + className + " could not be instantiated: " + x,
x);
}
}
private static final Logger logger = Logger.getLogger("javax.xml.ws");
private static final ServiceLoaderUtil.ExceptionHandler<WebServiceException> EXCEPTION_HANDLER =
new ServiceLoaderUtil.ExceptionHandler<WebServiceException>() {
@Override
public WebServiceException createException(Throwable throwable, String message) {
return new WebServiceException(message, throwable);
}
};
/**
* Finds the implementation {@code Class} object for the given
@ -67,7 +59,7 @@ class FactoryFinder {
* @return the {@code Class} object of the specified message factory;
* may not be {@code null}
*
* @param factoryId the name of the factory to find, which is
* @param factoryClass the name of the factory to find, which is
* a system property
* @param fallbackClassName the implementation class name, which is
* to be used only if nothing else
@ -75,72 +67,26 @@ class FactoryFinder {
* there is no fallback class name
* @exception WebServiceException if there is an error
*/
static Object find(String factoryId, String fallbackClassName)
{
if (isOsgi()) {
return lookupUsingOSGiServiceLoader(factoryId);
}
ClassLoader classLoader;
try {
classLoader = Thread.currentThread().getContextClassLoader();
} catch (Exception x) {
throw new WebServiceException(x.toString(), x);
}
@SuppressWarnings("unchecked")
static <T> T find(Class<T> factoryClass, String fallbackClassName) {
ClassLoader classLoader = ServiceLoaderUtil.contextClassLoader(EXCEPTION_HANDLER);
String serviceId = "META-INF/services/" + factoryId;
// try to find services in CLASSPATH
BufferedReader rd = null;
try {
InputStream is;
if (classLoader == null) {
is=ClassLoader.getSystemResourceAsStream(serviceId);
} else {
is=classLoader.getResourceAsStream(serviceId);
}
if( is!=null ) {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String factoryClassName = rd.readLine();
if (factoryClassName != null &&
! "".equals(factoryClassName)) {
return newInstance(factoryClassName, classLoader);
}
}
} catch( Exception ignored) {
} finally {
close(rd);
}
T provider = ServiceLoaderUtil.firstByServiceLoader(factoryClass, logger, EXCEPTION_HANDLER);
if (provider != null) return provider;
String factoryId = factoryClass.getName();
// try to read from $java.home/lib/jaxws.properties
FileInputStream inStream = null;
try {
String javah=System.getProperty( "java.home" );
String configFile = javah + File.separator +
"lib" + File.separator + "jaxws.properties";
File f=new File( configFile );
if( f.exists()) {
Properties props=new Properties();
inStream = new FileInputStream(f);
props.load(inStream);
String factoryClassName = props.getProperty(factoryId);
return newInstance(factoryClassName, classLoader);
}
} catch(Exception ignored) {
} finally {
close(inStream);
}
provider = (T) fromJDKProperties(factoryId, fallbackClassName, classLoader);
if (provider != null) return provider;
// Use the system property
try {
String systemProp =
System.getProperty( factoryId );
if( systemProp!=null) {
return newInstance(systemProp, classLoader);
}
} catch (SecurityException ignored) {
provider = (T) fromSystemProperty(factoryId, fallbackClassName, classLoader);
if (provider != null) return provider;
// handling Glassfish (platform specific default)
if (isOsgi()) {
return (T) lookupUsingOSGiServiceLoader(factoryId);
}
if (fallbackClassName == null) {
@ -148,43 +94,51 @@ class FactoryFinder {
"Provider for " + factoryId + " cannot be found", null);
}
return newInstance(fallbackClassName, classLoader);
return (T) ServiceLoaderUtil.newInstance(fallbackClassName,
fallbackClassName, classLoader, EXCEPTION_HANDLER);
}
private static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ignored) {
}
}
}
/**
* Loads the class, provided that the calling thread has an access to the class being loaded.
*/
private static Class safeLoadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
private static Object fromSystemProperty(String factoryId,
String fallbackClassName,
ClassLoader classLoader) {
try {
// make sure that the current thread has an access to the package of the given name.
SecurityManager s = System.getSecurityManager();
if (s != null) {
int i = className.lastIndexOf('.');
if (i != -1) {
s.checkPackageAccess(className.substring(0, i));
}
String systemProp = System.getProperty(factoryId);
if (systemProp != null) {
return ServiceLoaderUtil.newInstance(systemProp,
fallbackClassName, classLoader, EXCEPTION_HANDLER);
}
} catch (SecurityException ignored) {
}
return null;
}
private static Object fromJDKProperties(String factoryId,
String fallbackClassName,
ClassLoader classLoader) {
Path path = null;
try {
String JAVA_HOME = System.getProperty("java.home");
path = Paths.get(JAVA_HOME, "conf", "jaxws.properties");
// to ensure backwards compatibility
if (!Files.exists(path)) {
path = Paths.get(JAVA_HOME, "lib", "jaxws.properties");
}
if (classLoader == null)
return Class.forName(className);
else
return classLoader.loadClass(className);
} catch (SecurityException se) {
// anyone can access the platform default factory class without permission
if (Provider.DEFAULT_JAXWSPROVIDER.equals(className))
return Class.forName(className);
throw se;
if (!Files.exists(path)) {
Properties props = new Properties();
try (InputStream inStream = Files.newInputStream(path)) {
props.load(inStream);
}
String factoryClassName = props.getProperty(factoryId);
return ServiceLoaderUtil.newInstance(factoryClassName,
fallbackClassName, classLoader, EXCEPTION_HANDLER);
}
} catch (Exception ignored) {
logger.log(Level.SEVERE, "Error reading JAX-WS configuration from [" + path +
"] file. Check it is accessible and has correct format.", ignored);
}
return null;
}
private static final String OSGI_SERVICE_LOADER_CLASS_NAME = "com.sun.org.glassfish.hk2.osgiresourcelocator.ServiceLoader";

View File

@ -44,20 +44,13 @@ import org.w3c.dom.Element;
*/
public abstract class Provider {
/**
* A constant representing the property used to lookup the
* name of a {@code Provider} implementation
* class.
*/
static public final String JAXWSPROVIDER_PROPERTY = "javax.xml.ws.spi.Provider";
/**
* A constant representing the name of the default
* {@code Provider} implementation class.
**/
// Using two strings so that package renaming doesn't change it
static final String DEFAULT_JAXWSPROVIDER
= "com.sun"+".xml.internal.ws.spi.ProviderImpl";
private static final String DEFAULT_JAXWSPROVIDER =
"com.sun"+".xml.internal.ws.spi.ProviderImpl";
/**
* Creates a new instance of Provider
@ -72,47 +65,22 @@ public abstract class Provider {
* The algorithm used to locate the provider subclass to use consists
* of the following steps:
* <ul>
* <li>
* If a resource with the name of
* {@code META-INF/services/javax.xml.ws.spi.Provider}
* exists, then its first line, if present, is used as the UTF-8 encoded
* name of the implementation class.
* </li>
* <li>
* If the $java.home/lib/jaxws.properties file exists and it is readable by
* the {@code java.util.Properties.load(InputStream)} method and it contains
* an entry whose key is {@code javax.xml.ws.spi.Provider}, then the value of
* that entry is used as the name of the implementation class.
* </li>
* <li>
* If a system property with the name {@code javax.xml.ws.spi.Provider}
* is defined, then its value is used as the name of the implementation class.
* </li>
* <li>
* Finally, a default implementation class name is used.
* </li>
* <li> Use the service-provider loading facilities, defined by the {@link java.util.ServiceLoader} class,
* to attempt to locate and load an implementation of {@link javax.xml.ws.spi.Provider} service using
* the {@linkplain java.util.ServiceLoader#load(java.lang.Class) default loading mechanism}.
* <li>Use the configuration file "jaxws.properties". The file is in standard
* {@link java.util.Properties} format and typically located in the
* {@code conf} directory of the Java installation. It contains the fully qualified
* name of the implementation class with the key {@code javax.xml.ws.spi.Provider}.
* <li> If a system property with the name {@code javax.xml.ws.spi.Provider}
* is defined, then its value is used as the name of the implementation class.
* <li> Finally, a platform default implementation is used.
* </ul>
*
*/
public static Provider provider() {
try {
Object provider = getProviderUsingServiceLoader();
if (provider == null) {
provider = FactoryFinder.find(JAXWSPROVIDER_PROPERTY, DEFAULT_JAXWSPROVIDER);
}
if (!(provider instanceof Provider)) {
Class pClass = Provider.class;
String classnameAsResource = pClass.getName().replace('.', '/') + ".class";
ClassLoader loader = pClass.getClassLoader();
if(loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
URL targetTypeURL = loader.getResource(classnameAsResource);
throw new LinkageError("ClassCastException: attempting to cast" +
provider.getClass().getClassLoader().getResource(classnameAsResource) +
"to" + targetTypeURL.toString() );
}
return (Provider) provider;
return FactoryFinder.find(Provider.class, DEFAULT_JAXWSPROVIDER);
} catch (WebServiceException ex) {
throw ex;
} catch (Exception ex) {
@ -120,18 +88,6 @@ public abstract class Provider {
}
}
private static Provider getProviderUsingServiceLoader() {
ServiceLoader<Provider> sl;
Iterator<Provider> it;
try {
sl = ServiceLoader.load(Provider.class);
it = (Iterator<Provider>)sl.iterator();
} catch (Exception e) {
throw new WebServiceException("Cannot invoke java.util.ServiceLoader#iterator()", e);
}
return ((it != null) && it.hasNext()) ? it.next() : null;
}
/**
* Creates a service delegate object.
*

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.xml.ws.spi;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Shared ServiceLoader/FactoryFinder Utils shared among SAAJ, JAXB and JAXWS
* Class duplicated to all those projects.
*
* @author Miroslav.Kos@oracle.com
*/
class ServiceLoaderUtil {
static <P, T extends Exception> P firstByServiceLoader(Class<P> spiClass,
Logger logger,
ExceptionHandler<T> handler) throws T {
logger.log(Level.FINE, "Using java.util.ServiceLoader to find {0}", spiClass.getName());
// service discovery
try {
ServiceLoader<P> serviceLoader = ServiceLoader.load(spiClass);
for (P impl : serviceLoader) {
logger.fine("ServiceProvider loading Facility used; returning object [" +
impl.getClass().getName() + "]");
return impl;
}
} catch (Throwable t) {
throw handler.createException(t, "Error while searching for service [" + spiClass.getName() + "]");
}
return null;
}
static void checkPackageAccess(String className) {
// make sure that the current thread has an access to the package of the given name.
SecurityManager s = System.getSecurityManager();
if (s != null) {
int i = className.lastIndexOf('.');
if (i != -1) {
s.checkPackageAccess(className.substring(0, i));
}
}
}
static Class nullSafeLoadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader == null) {
return Class.forName(className);
} else {
return classLoader.loadClass(className);
}
}
// Returns instance of required class. It checks package access (security)
// unless it is defaultClassname. It means if you are trying to instantiate
// default implementation (fallback), pass the class name to both first and second parameter.
static <T extends Exception> Object newInstance(String className,
String defaultImplClassName, ClassLoader classLoader,
final ExceptionHandler<T> handler) throws T {
try {
return safeLoadClass(className, defaultImplClassName, classLoader).newInstance();
} catch (ClassNotFoundException x) {
throw handler.createException(x, "Provider " + className + " not found");
} catch (Exception x) {
throw handler.createException(x, "Provider " + className + " could not be instantiated: " + x);
}
}
static Class safeLoadClass(String className,
String defaultImplClassName,
ClassLoader classLoader) throws ClassNotFoundException {
try {
checkPackageAccess(className);
} catch (SecurityException se) {
// anyone can access the platform default factory class without permission
if (defaultImplClassName != null && defaultImplClassName.equals(className)) {
return Class.forName(className);
}
// not platform default implementation ...
throw se;
}
return nullSafeLoadClass(className, classLoader);
}
static <T extends Exception> ClassLoader contextClassLoader(ExceptionHandler<T> exceptionHandler) throws T {
try {
return Thread.currentThread().getContextClassLoader();
} catch (Exception x) {
throw exceptionHandler.createException(x, x.toString());
}
}
static abstract class ExceptionHandler<T extends Exception> {
public abstract T createException(Throwable throwable, String message);
}
}