Skip to content

Commit

Permalink
#194: Simplify implementation lookup
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas Jungmann <lukas.jungmann@oracle.com>
  • Loading branch information
lukasj committed Jul 14, 2021
1 parent 734832b commit 2fc2446
Show file tree
Hide file tree
Showing 4 changed files with 14 additions and 287 deletions.
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

0 comments on commit 2fc2446

Please sign in to comment.