Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#194: Simplify implementation lookup #195

Merged
merged 1 commit into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 4 additions & 173 deletions jaxb-api/src/main/java/jakarta/xml/bind/ContextFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@

package jakarta.xml.bind;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
Expand All @@ -32,16 +30,13 @@
* 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 JAXB 1.0 spec pluggability feature
* 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 {

// previous value of JAXBContext.JAXB_CONTEXT_FACTORY, using also this to ensure backwards compatibility
private static final String JAXB_CONTEXT_FACTORY_DEPRECATED = "jakarta.xml.bind.context.factory";

private static final Logger logger;

/**
Expand Down Expand Up @@ -303,18 +298,6 @@ static JAXBContext find(String factoryId,
//ModuleUtil is mr-jar class, scans context path for jaxb classes on jdk9 and higher
Class<?>[] contextPathClasses = ModuleUtil.getClassesFromContextPath(contextPath, classLoader);

//first try with classloader#getResource
String factoryClassName = jaxbProperties(contextPath, classLoader, factoryId);
if (factoryClassName == null && contextPathClasses != null) {
//try with class#getResource
factoryClassName = jaxbProperties(contextPathClasses, factoryId);
}

if (factoryClassName != null) {
return newInstance(contextPath, contextPathClasses, factoryClassName, classLoader, properties);
}


String factoryName = classNameFromSystemProperties();
if (factoryName != null) return newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);

Expand All @@ -326,12 +309,8 @@ static JAXBContext find(String factoryId,
return obj.createContext(contextPath, classLoader, properties);
}

// to ensure backwards compatibility
factoryName = firstByServiceLoaderDeprecated(JAXBContext.class, classLoader);
if (factoryName != null) return newInstance(contextPath, contextPathClasses, factoryName, classLoader, properties);

Class<?> ctxFactory = (Class<?>) ServiceLoaderUtil.lookupUsingOSGiServiceLoader(
"jakarta.xml.bind.JAXBContext", logger);
JAXBContext.JAXB_CONTEXT_FACTORY, logger);

if (ctxFactory != null) {
return newInstance(contextPath, contextPathClasses, ctxFactory, classLoader, properties);
Expand All @@ -343,34 +322,6 @@ static JAXBContext find(String factoryId,
}

static JAXBContext find(Class<?>[] classes, Map<String, ?> properties) throws JAXBException {

// search for jaxb.properties in the class loader of each class first
logger.fine("Searching jaxb.properties");
for (final Class<?> c : classes) {
// this classloader is used only to load jaxb.properties, so doing this should be safe.
// this is possible for primitives, arrays, and classes that are
// loaded by poorly implemented ClassLoaders
if (c.getPackage() == null) continue;

// TODO: do we want to optimize away searching the same package? org.Foo, org.Bar, com.Baz
// classes from the same package might come from different class loades, so it might be a bad idea
// TODO: it's easier to look things up from the class
// c.getResourceAsStream("jaxb.properties");

URL jaxbPropertiesUrl = getResourceUrl(c, "jaxb.properties");

if (jaxbPropertiesUrl != null) {

String factoryClassName =
classNameFromPackageProperties(
jaxbPropertiesUrl,
JAXBContext.JAXB_CONTEXT_FACTORY, JAXB_CONTEXT_FACTORY_DEPRECATED);

return newInstance(classes, properties, factoryClassName, getClassClassLoader(c));
}

}

String factoryClassName = classNameFromSystemProperties();
if (factoryClassName != null) return newInstance(classes, properties, factoryClassName);

Expand All @@ -382,22 +333,9 @@ static JAXBContext find(Class<?>[] classes, Map<String, ?> properties) throws JA
return factory.createContext(classes, properties);
}

// to ensure backwards compatibility
ClassLoader loader = getContextClassLoader();
// it is guaranteed classes are not null but it is not guaranteed, that array is not empty
if (classes.length > 0) {
ClassLoader c = getClassClassLoader(classes[0]);
//switch to classloader which loaded the class if it is not a bootstrap cl
if (c != null) {
loader = c;
}
}
String className = firstByServiceLoaderDeprecated(JAXBContext.class, loader);
if (className != null) return newInstance(classes, properties, className, loader);

logger.fine("Trying to create the platform default provider");
Class<?> ctxFactoryClass =
(Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader("jakarta.xml.bind.JAXBContext", logger);
(Class) ServiceLoaderUtil.lookupUsingOSGiServiceLoader(JAXBContext.JAXB_CONTEXT_FACTORY, logger);

if (ctxFactoryClass != null) {
return newInstance(classes, properties, ctxFactoryClass);
Expand All @@ -408,53 +346,14 @@ static JAXBContext find(Class<?>[] classes, Map<String, ?> properties) throws JA
return newInstance(classes, properties, DEFAULT_FACTORY_CLASS);
}


/**
* first factoryId should be the preferred one,
* more of those can be provided to support backwards compatibility
*/
private static String classNameFromPackageProperties(URL packagePropertiesUrl,
String ... factoryIds) throws JAXBException {

logger.log(Level.FINE, "Trying to locate {0}", packagePropertiesUrl.toString());
Properties props = loadJAXBProperties(packagePropertiesUrl);
for(String factoryId : factoryIds) {
if (props.containsKey(factoryId)) {
return props.getProperty(factoryId);
}
}
//Factory key not found
String propertiesUrl = packagePropertiesUrl.toExternalForm();
String packageName = propertiesUrl.substring(0, propertiesUrl.indexOf("/jaxb.properties"));
throw new JAXBException(Messages.format(Messages.MISSING_PROPERTY, packageName, factoryIds[0]));
}

private static String classNameFromSystemProperties() throws JAXBException {

String factoryClassName = getSystemProperty(JAXBContext.JAXB_CONTEXT_FACTORY);
if (factoryClassName != null) {
return factoryClassName;
}
// leave this here to assure compatibility
factoryClassName = getDeprecatedSystemProperty(JAXB_CONTEXT_FACTORY_DEPRECATED);
if (factoryClassName != null) {
return factoryClassName;
}
// leave this here to assure compatibility
factoryClassName = getDeprecatedSystemProperty(JAXBContext.class.getName());
if (factoryClassName != null) {
return factoryClassName;
}
return null;
}

private static String getDeprecatedSystemProperty(String property) {
String value = getSystemProperty(property);
if (value != null) {
logger.log(Level.WARNING, "Using non-standard property: {0}. Property {1} should be used instead.",
new Object[] {property, JAXBContext.JAXB_CONTEXT_FACTORY});
}
return value;
return null;
}

private static String getSystemProperty(String property) {
Expand Down Expand Up @@ -588,72 +487,4 @@ public ClassLoader run() {
}
}

// ServiceLoaderUtil.firstByServiceLoaderDeprecated should be used instead.
@Deprecated
static String firstByServiceLoaderDeprecated(Class<?> spiClass,
ClassLoader classLoader) throws JAXBException {

final String jaxbContextFQCN = spiClass.getName();

logger.fine("Searching META-INF/services");

// search META-INF services next
BufferedReader r = null;
final String resource = "META-INF/services/" + jaxbContextFQCN;
try {
final InputStream resourceStream =
(classLoader == null) ?
ClassLoader.getSystemResourceAsStream(resource) :
classLoader.getResourceAsStream(resource);

if (resourceStream != null) {
r = new BufferedReader(new InputStreamReader(resourceStream, "UTF-8"));
String factoryClassName = r.readLine();
if (factoryClassName != null) {
factoryClassName = factoryClassName.trim();
}
r.close();
logger.log(Level.FINE, "Configured factorty class:{0}", factoryClassName);
return factoryClassName;
} else {
logger.log(Level.FINE, "Unable to load:{0}", resource);
return null;
}
} catch (IOException e) {
throw new JAXBException(e);
} finally {
try {
if (r != null) {
r.close();
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Unable to close resource: " + resource, ex);
}
}
}

private static String jaxbProperties(String contextPath, ClassLoader classLoader, String factoryId) throws JAXBException {
String[] packages = contextPath.split(":");

for (String pkg : packages) {
String pkgUrl = pkg.replace('.', '/');
URL jaxbPropertiesUrl = getResourceUrl(classLoader, pkgUrl + "/jaxb.properties");
if (jaxbPropertiesUrl != null) {
return classNameFromPackageProperties(jaxbPropertiesUrl,
factoryId, JAXB_CONTEXT_FACTORY_DEPRECATED);
}
}
return null;
}

private static String jaxbProperties(Class<?>[] classesFromContextPath, String factoryId) throws JAXBException {
for (Class<?> c : classesFromContextPath) {
URL jaxbPropertiesUrl = getResourceUrl(c, "jaxb.properties");
if (jaxbPropertiesUrl != null) {
return classNameFromPackageProperties(jaxbPropertiesUrl, factoryId, JAXB_CONTEXT_FACTORY_DEPRECATED);
}
}
return null;
}

}
95 changes: 3 additions & 92 deletions jaxb-api/src/main/java/jakarta/xml/bind/JAXBContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
import org.w3c.dom.Node;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;

/**
* The {@code JAXBContext} class provides the client's entry point to the
Expand Down Expand Up @@ -44,21 +42,7 @@
* </ul>
*
* <p><i>
* The following JAXB 1.0 requirement is only required for schema to
* java interface/implementation binding. It does not apply to Jakarta XML Binding annotated
* classes. Jakarta XML Binding Providers must generate a {@code jaxb.properties} file in
* each package containing schema derived classes. The property file must
* contain a property named {@code jakarta.xml.bind.context.factory} whose
* value is the name of the class that implements the {@code createContext}
* APIs.</i>
*
* <p><i>
* The class supplied by the provider does not have to be assignable to
* {@code jakarta.xml.bind.JAXBContext}, it simply has to provide a class that
* implements the {@code createContext} APIs.</i>
*
* <p><i>
* In addition, the provider must call the
* The provider must call the
* {@link DatatypeConverter#setDatatypeConverter(DatatypeConverterInterface)
* DatatypeConverter.setDatatypeConverter} api prior to any client
* invocations of the marshal and unmarshal methods. This is necessary to
Expand Down Expand Up @@ -191,29 +175,8 @@
* <ol>
*
* <li>
* Packages/classes explicitly passed in to the {@link #newInstance} method are processed in the order they are
* specified, until {@code jaxb.properties} file is looked up in its package, by using the associated classloader &mdash;
* this is {@link Class#getClassLoader() the owner class loader} for a {@link Class} argument, and for a package
* the specified {@link ClassLoader}.
*
* <p>
* If such a resource is discovered, it is {@link Properties#load(InputStream) loaded} as a property file, and
* the value of the {@link #JAXB_CONTEXT_FACTORY} key will be assumed to be the provider factory class. If no value
* found, {@code "jakarta.xml.bind.context.factory"} is used as a key for backwards compatibility reasons. This class is
* then loaded by the associated class loader discussed above.
*
* <p>
* This phase of the look up allows some packages to force the use of a certain Jakarta XML Binding implementation.
* (For example, perhaps the schema compiler has generated some vendor extension in the code.)
*
* <p>
* This configuration method is deprecated.
*
* <li>
* If the system property {@link #JAXB_CONTEXT_FACTORY} exists, then its value is assumed to be the provider
* factory class. If no such property exists, properties {@code "jakarta.xml.bind.context.factory"} and
* {@code "jakarta.xml.bind.JAXBContext"} are checked too (in this order), for backwards compatibility reasons. This phase
* of the look up enables per-JVM override of the Jakarta XML Binding implementation.
* factory class. This phase of the look up enables per-JVM override of the Jakarta XML Binding implementation.
*
* <li>
* Provider of {@link jakarta.xml.bind.JAXBContextFactory} is loaded using the service-provider loading
Expand All @@ -228,57 +191,15 @@
* configuration error} a {@link jakarta.xml.bind.JAXBException} will be thrown.
*
* <li>
* Look for resource {@code /META-INF/services/jakarta.xml.bind.JAXBContext} using provided class loader.
* Methods without class loader parameter use {@code Thread.currentThread().getContextClassLoader()}.
* If such a resource exists, its content is assumed to be the provider factory class.
*
* This configuration method is deprecated.
*
* <li>
* Finally, if all the steps above fail, then the rest of the look up is unspecified. That said,
* the recommended behavior is to simply look for some hard-coded platform default Jakarta XML Binding implementation.
* This phase of the look up is so that Java SE can have its own JAXB implementation as the last resort.
* This phase of the look up is so that the environment can have its own Jakarta XML Binding implementation as the last resort.
* </ol>
*
* <p>
* Once the provider factory class is discovered, context creation is delegated to one of its
* {@code createContext(...)} methods.
*
* For backward compatibility reasons, there are two ways how to implement provider factory class:
* <ol>
* <li>the class is implementation of {@link jakarta.xml.bind.JAXBContextFactory}. It must also implement no-arg
* constructor. If discovered in other step then 3, new instance using no-arg constructor is created first.
* After that, appropriate instance method is invoked on this instance.
* <li>the class is not implementation of interface above and then it is mandated to implement the following
* static method signatures:
* <pre>
*
* public static JAXBContext createContext(
* String contextPath,
* ClassLoader classLoader,
* Map&lt;String,Object&gt; properties ) throws JAXBException
*
* public static JAXBContext createContext(
* Class[] classes,
* Map&lt;String,Object&gt; properties ) throws JAXBException
* </pre>
* In this scenario, appropriate static method is used instead of instance method. This approach is incompatible
* with {@link java.util.ServiceLoader} so it can't be used with step 3.
* </ol>
* <p>
* There is no difference in behavior of given method {@code createContext(...)} regardless of whether it uses approach
* 1 (JAXBContextFactory) or 2 (no interface, static methods).
*
* @apiNote
* Service discovery method using resource {@code /META-INF/services/jakarta.xml.bind.JAXBContext} (described in step 4)
* is supported only to allow backwards compatibility, it is strongly recommended to migrate to standard
* {@link java.util.ServiceLoader} mechanism (described in step 3). The difference here is the resource name, which
* doesn't match service's type name.
* <p>
* Also using providers implementing interface {@link JAXBContextFactory} is preferred over using ones defining
* static methods, same as {@link JAXBContext#JAXB_CONTEXT_FACTORY} property is preferred over property
* {@code "jakarta.xml.bind.context.factory"}
*
* @implNote
* Within the last step, if Glassfish AS environment detected, its specific service loader is used to find factory class.
*
Expand Down Expand Up @@ -381,16 +302,6 @@ public static JAXBContext newInstance( String contextPath )
* </ul>
*
* <p>
* To maintain compatibility with JAXB 1.0 schema to java
* interface/implementation binding, enabled by schema customization
* {@code <jaxb:globalBindings valueClass="false">},
* the Jakarta XML Binding provider will ensure that each package on the context path
* has a {@code jaxb.properties} file which contains a value for the
* {@code jakarta.xml.bind.context.factory} property and that all values
* resolve to the same provider. This requirement does not apply to
* Jakarta XML Binding annotated classes.
*
* <p>
* If there are any global XML element name collisions across the various
* packages listed on the {@code contextPath}, a {@code JAXBException}
* will be thrown.
Expand Down
Loading