blob: 15fffa4b00216548f95546efa24213888dad6316 [file] [log] [blame]
/*
* Copyright (c) 2003, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.xml.bind;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class is package private and therefore is not exposed as part of the
* Jakarta XML Binding API.
*
* This code is designed to implement the XML Binding spec pluggability feature
*
* @author <ul><li>Ryan Shoemaker, Sun Microsystems, Inc.</li></ul>
* @see JAXBContext
*/
class ContextFinder {
private static final Logger logger;
/**
* When JAXB is in J2SE, rt.jar has to have a JAXB implementation.
* However, rt.jar cannot have META-INF/services/jakarta.xml.bind.JAXBContext
* because if it has, it will take precedence over any file that applications have
* in their jar files.
*
* <p>
* When the user bundles his own Jakarta XML Binding implementation, we'd like to use it, and we
* want the platform default to be used only when there's no other Jakarta XML Binding provider.
*
* <p>
* For this reason, we have to hard-code the class name into the API.
*/
//XXX: should we define and rely on "default" in jakarta?
static final String DEFAULT_FACTORY_CLASS = "org.glassfish.jaxb.runtime.v2.ContextFactory";
static {
logger = Logger.getLogger("jakarta.xml.bind");
try {
if (AccessController.doPrivileged(new GetPropertyAction("jaxb.debug")) != null) {
// disconnect the logger from a bigger framework (if any)
// and take the matters into our own hands
logger.setUseParentHandlers(false);
logger.setLevel(Level.ALL);
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
logger.addHandler(handler);
} else {
// don't change the setting of this logger
// to honor what other frameworks
// have done on configurations.
}
} catch (Throwable t) {
// just to be extra safe. in particular System.getProperty may throw
// SecurityException.
}
}
private static ServiceLoaderUtil.ExceptionHandler<JAXBException> EXCEPTION_HANDLER =
new ServiceLoaderUtil.ExceptionHandler<JAXBException>() {
@Override
public JAXBException createException(Throwable throwable, String message) {
return new JAXBException(message, throwable);
}
};
/**
* If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped,
* throw the wrapped exception. Otherwise returns exception to be wrapped for further processing.
*/
private static Throwable handleInvocationTargetException(InvocationTargetException x) throws JAXBException {
Throwable t = x.getTargetException();
if (t != null) {
if (t instanceof JAXBException)
// one of our exceptions, just re-throw
throw (JAXBException) t;
if (t instanceof RuntimeException)
// avoid wrapping exceptions unnecessarily
throw (RuntimeException) t;
if (t instanceof Error)
throw (Error) t;
return t;
}
return x;
}
/**
* Determine if two types (JAXBContext in this case) will generate a ClassCastException.
*
* For example, (targetType)originalType
*
* @param originalType
* The Class object of the type being cast
* @param targetType
* The Class object of the type that is being cast to
* @return JAXBException to be thrown.
*/
private static JAXBException handleClassCastException(Class<?> originalType, Class<?> targetType) {
final URL targetTypeURL = which(targetType);
return new JAXBException(Messages.format(Messages.ILLEGAL_CAST,
// we don't care where the impl class is, we want to know where JAXBContext lives in the impl
// class' ClassLoader
getClassClassLoader(originalType).getResource("jakarta/xml/bind/JAXBContext.class"),
targetTypeURL));
}
/**
* Create an instance of a class using the specified ClassLoader
*/
static JAXBContext newInstance(String contextPath,
Class<?>[] contextPathClasses,
String className,
ClassLoader classLoader,
Map<String, ?> properties) throws JAXBException {
try {
Class<?> spFactory = ServiceLoaderUtil.safeLoadClass(className, DEFAULT_FACTORY_CLASS, classLoader);
return newInstance(contextPath, contextPathClasses, spFactory, classLoader, properties);
} catch (ClassNotFoundException x) {
throw new JAXBException(Messages.format(Messages.DEFAULT_PROVIDER_NOT_FOUND), x);
} catch (RuntimeException | JAXBException x) {
// avoid wrapping RuntimeException to JAXBException,
// because it indicates a bug in this code.
// JAXBException re-thrown as is
throw x;
} catch (Exception x) {
// can't catch JAXBException because the method is hidden behind
// reflection. Root element collisions detected in the call to
// createContext() are reported as JAXBExceptions - just re-throw it
// some other type of exception - just wrap it
throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, className, x), x);
}
}
static JAXBContext newInstance(String contextPath,
Class<?>[] contextPathClasses,
Class<?> spFactory,
ClassLoader classLoader,
Map<String, ?> properties) throws JAXBException {
try {
ModuleUtil.delegateAddOpensToImplModule(contextPathClasses, spFactory);
/*
* jakarta.xml.bind.context.factory points to a class which has a
* static method called 'createContext' that
* returns a jakarta.xml.bind.JAXBContext.
*/
Object context = null;
// first check the method that takes Map as the third parameter.
// this is added in 2.0.
try {
Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class, Map.class);
// any failure in invoking this method would be considered fatal
Object obj = instantiateProviderIfNecessary(spFactory);
context = m.invoke(obj, contextPath, classLoader, properties);
} catch (NoSuchMethodException ignored) {
// it's not an error for the provider not to have this method.
}
if (context == null) {
// try the old method that doesn't take properties. compatible with 1.0.
// it is an error for an implementation not to have both forms of the createContext method.
Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class);
Object obj = instantiateProviderIfNecessary(spFactory);
// any failure in invoking this method would be considered fatal
context = m.invoke(obj, contextPath, classLoader);
}
if (!(context instanceof JAXBContext)) {
// the cast would fail, so generate an exception with a nice message
throw handleClassCastException(context.getClass(), JAXBContext.class);
}
return (JAXBContext) context;
} catch (InvocationTargetException x) {
// throw if it is exception not to be wrapped
// otherwise, wrap with a JAXBException
Throwable e = handleInvocationTargetException(x);
throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, e), e);
} catch (Exception x) {
// can't catch JAXBException because the method is hidden behind
// reflection. Root element collisions detected in the call to
// createContext() are reported as JAXBExceptions - just re-throw it
// some other type of exception - just wrap it
throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, x), x);
}
}
private static Object instantiateProviderIfNecessary(final Class<?> implClass) throws JAXBException {
try {
if (JAXBContextFactory.class.isAssignableFrom(implClass)) {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
@Override
public Object run() throws Exception {
return implClass.getConstructor().newInstance();
}
});
}
return null;
} catch (PrivilegedActionException x) {
Throwable e = (x.getCause() == null) ? x : x.getCause();
throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, implClass, e), e);
}
}
/**
* Create an instance of a class using the thread context ClassLoader
*/
private static JAXBContext newInstance(Class<?>[] classes, Map<String, ?> properties, String className) throws JAXBException {
return newInstance(classes, properties, className, getContextClassLoader());
}
/**
* Create an instance of a class using passed in ClassLoader
*/
private static JAXBContext newInstance(Class<?>[] classes, Map<String, ?> properties, String className, ClassLoader loader) throws JAXBException {
Class<?> spi;
try {
spi = ServiceLoaderUtil.safeLoadClass(className, DEFAULT_FACTORY_CLASS, loader);
} catch (ClassNotFoundException e) {
throw new JAXBException(Messages.format(Messages.DEFAULT_PROVIDER_NOT_FOUND), e);
}
if (logger.isLoggable(Level.FINE)) {
// extra check to avoid costly which operation if not logged
logger.log(Level.FINE, "loaded {0} from {1}", new Object[]{className, which(spi)});
}
return newInstance(classes, properties, spi);
}
static JAXBContext newInstance(Class<?>[] classes,
Map<String, ?> properties,
Class<?> spFactory) throws JAXBException {
try {
ModuleUtil.delegateAddOpensToImplModule(classes, spFactory);
Method m = spFactory.getMethod("createContext", Class[].class, Map.class);
Object obj = instantiateProviderIfNecessary(spFactory);
Object context = m.invoke(obj, classes, properties);
if (!(context instanceof JAXBContext)) {
// the cast would fail, so generate an exception with a nice message
throw handleClassCastException(context.getClass(), JAXBContext.class);
}
return (JAXBContext) context;
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new JAXBException(e);
} catch (InvocationTargetException e) {
// throw if it is exception not to be wrapped
// otherwise, wrap with a JAXBException
Throwable x = handleInvocationTargetException(e);
throw new JAXBException(x);
}
}
static JAXBContext find(String factoryId,
String contextPath,
ClassLoader classLoader,
Map<String, ?> properties) throws JAXBException {
if (contextPath == null || contextPath.isEmpty()) {
// no context is specified
throw new JAXBException(Messages.format(Messages.NO_PACKAGE_IN_CONTEXTPATH));
}
//ModuleUtil is mr-jar class, scans context path for jaxb classes on jdk9 and higher
Class<?>[] contextPathClasses = ModuleUtil.getClassesFromContextPath(contextPath, classLoader);
String factoryName = classNameFromSystemProperties();
if (factoryName != null) return newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);
if (properties != null) {
Object factory = properties.get(factoryId);
if (factory != null) {
if (factory instanceof String) {
factoryName = (String) factory;
} else {
throw new JAXBException(Messages.format(Messages.ILLEGAL_CAST, factory.getClass().getName(), "String"));
}
}
if (factoryName != null) {
return newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);
}
}
JAXBContextFactory obj = ServiceLoaderUtil.firstByServiceLoader(
JAXBContextFactory.class, logger, EXCEPTION_HANDLER);
if (obj != null) {
ModuleUtil.delegateAddOpensToImplModule(contextPathClasses, obj.getClass());
return obj.createContext(contextPath, classLoader, properties);
}
Iterable<Class<? extends JAXBContextFactory>> ctxFactories = ServiceLoaderUtil.lookupsUsingOSGiServiceLoader(
JAXBContext.JAXB_CONTEXT_FACTORY, logger);
if (ctxFactories != null) {
for (Class<? extends JAXBContextFactory> ctxFactory : ctxFactories) {
try {
return newInstance(contextPath, contextPathClasses, ctxFactory, classLoader, properties);
} catch (Throwable t) {
logger.log(Level.FINE, t, () -> "Error instantiating provivder " + ctxFactory);
}
}
}
// else no provider found
logger.fine("Trying to create the platform default provider");
return newInstance(contextPath, contextPathClasses, DEFAULT_FACTORY_CLASS, classLoader, properties);
}
static JAXBContext find(Class<?>[] classes, Map<String, ?> properties) throws JAXBException {
String factoryClassName = classNameFromSystemProperties();
if (factoryClassName != null) return newInstance(classes, properties, factoryClassName);
if (properties != null) {
Object ctxFactory = properties.get(JAXBContext.JAXB_CONTEXT_FACTORY);
if (ctxFactory != null) {
if (ctxFactory instanceof String) {
factoryClassName = (String) ctxFactory;
} else {
throw new JAXBException(Messages.format(Messages.ILLEGAL_CAST, ctxFactory.getClass().getName(), "String"));
}
}
if (factoryClassName != null) {
return newInstance(classes, properties, factoryClassName);
}
}
JAXBContextFactory factory =
ServiceLoaderUtil.firstByServiceLoader(JAXBContextFactory.class, logger, EXCEPTION_HANDLER);
if (factory != null) {
ModuleUtil.delegateAddOpensToImplModule(classes, factory.getClass());
return factory.createContext(classes, properties);
}
logger.fine("Trying to create the platform default provider");
Class<?> ctxFactoryClass =
ServiceLoaderUtil.lookupUsingOSGiServiceLoader(JAXBContext.JAXB_CONTEXT_FACTORY, logger);
if (ctxFactoryClass != null) {
return newInstance(classes, properties, ctxFactoryClass);
}
// else no provider found
logger.fine("Trying to create the platform default provider");
return newInstance(classes, properties, DEFAULT_FACTORY_CLASS);
}
private static String classNameFromSystemProperties() throws JAXBException {
String factoryClassName = getSystemProperty(JAXBContext.JAXB_CONTEXT_FACTORY);
if (factoryClassName != null) {
return factoryClassName;
}
return null;
}
private static String getSystemProperty(String property) {
logger.log(Level.FINE, "Checking system property {0}", property);
String value = AccessController.doPrivileged(new GetPropertyAction(property));
if (value != null) {
logger.log(Level.FINE, " found {0}", value);
} else {
logger.log(Level.FINE, " not found");
}
return value;
}
/**
* Search the given ClassLoader for an instance of the specified class and
* return a string representation of the URL that points to the resource.
*
* @param clazz
* The class to search for
* @param loader
* The ClassLoader to search. If this parameter is null, then the
* system class loader will be searched
* @return
* the URL for the class or null if it wasn't found
*/
static URL which(Class<?> clazz, ClassLoader loader) {
String classnameAsResource = clazz.getName().replace('.', '/') + ".class";
if (loader == null) {
loader = getSystemClassLoader();
}
return loader.getResource(classnameAsResource);
}
/**
* Get the URL for the Class from it's ClassLoader.
*
* Convenience method for {@link #which(Class, ClassLoader)}.
*
* Equivalent to calling: which(clazz, clazz.getClassLoader())
*
* @param clazz
* The class to search for
* @return
* the URL for the class or null if it wasn't found
*/
static URL which(Class<?> clazz) {
return which(clazz, getClassClassLoader(clazz));
}
private static ClassLoader getContextClassLoader() {
if (System.getSecurityManager() == null) {
return Thread.currentThread().getContextClassLoader();
} else {
return AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
});
}
}
private static ClassLoader getClassClassLoader(final Class<?> c) {
if (System.getSecurityManager() == null) {
return c.getClassLoader();
} else {
return AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return c.getClassLoader();
}
});
}
}
private static ClassLoader getSystemClassLoader() {
if (System.getSecurityManager() == null) {
return ClassLoader.getSystemClassLoader();
} else {
return AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return ClassLoader.getSystemClassLoader();
}
});
}
}
}