8152912: SAX XMLReaderFactory needs to be ServiceLoader compliant
authorjoehw
Mon, 09 May 2016 14:58:55 -0700
changeset 37927 fe4019feb049
parent 37858 7c04fcb12bd4
child 37928 3aba2c70c997
8152912: SAX XMLReaderFactory needs to be ServiceLoader compliant Reviewed-by: dfuchs
jaxp/src/java.xml/share/classes/module-info.java
jaxp/src/java.xml/share/classes/org/xml/sax/AttributeList.java
jaxp/src/java.xml/share/classes/org/xml/sax/DocumentHandler.java
jaxp/src/java.xml/share/classes/org/xml/sax/Parser.java
jaxp/src/java.xml/share/classes/org/xml/sax/helpers/NewInstance.java
jaxp/src/java.xml/share/classes/org/xml/sax/helpers/ParserFactory.java
jaxp/src/java.xml/share/classes/org/xml/sax/helpers/SecuritySupport.java
jaxp/src/java.xml/share/classes/org/xml/sax/helpers/XMLReaderFactory.java
--- a/jaxp/src/java.xml/share/classes/module-info.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/module-info.java	Mon May 09 14:58:55 2016 -0700
@@ -86,5 +86,6 @@
     uses javax.xml.transform.TransformerFactory;
     uses javax.xml.validation.SchemaFactory;
     uses javax.xml.xpath.XPathFactory;
+    uses org.xml.sax.XMLReader;
 }
 
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/AttributeList.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/AttributeList.java	Mon May 09 14:58:55 2016 -0700
@@ -93,6 +93,7 @@
  * @see org.xml.sax.DocumentHandler#startElement startElement
  * @see org.xml.sax.helpers.AttributeListImpl AttributeListImpl
  */
+@Deprecated(since="5")
 public interface AttributeList {
 
 
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/DocumentHandler.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/DocumentHandler.java	Mon May 09 14:58:55 2016 -0700
@@ -68,6 +68,7 @@
  * @see org.xml.sax.Locator
  * @see org.xml.sax.HandlerBase
  */
+@Deprecated(since="5")
 public interface DocumentHandler {
 
 
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/Parser.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/Parser.java	Mon May 09 14:58:55 2016 -0700
@@ -73,6 +73,7 @@
  * @see org.xml.sax.HandlerBase
  * @see org.xml.sax.InputSource
  */
+@Deprecated(since="5")
 public interface Parser
 {
 
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/NewInstance.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/NewInstance.java	Mon May 09 14:58:55 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 2016, 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
@@ -32,8 +32,7 @@
 
 package org.xml.sax.helpers;
 
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
 
 /**
  * Create a new instance of a class by name.
@@ -57,31 +56,26 @@
  * @version 2.0.1 (sax2r2)
  */
 class NewInstance {
+
     private static final String DEFAULT_PACKAGE = "com.sun.org.apache.xerces.internal";
     /**
      * Creates a new instance of the specified class name
      *
      * Package private so this code is not exposed at the API level.
      */
-    static Object newInstance (ClassLoader classLoader, String className)
+    static <T> T newInstance (Class<T> type, ClassLoader loader, String clsName)
         throws ClassNotFoundException, IllegalAccessException,
             InstantiationException
     {
-        // make sure we have access to restricted packages
-        boolean internal = false;
-        if (System.getSecurityManager() != null) {
-            if (className != null && className.startsWith(DEFAULT_PACKAGE)) {
-                internal = true;
-            }
+        ClassLoader classLoader = Objects.requireNonNull(loader);
+        String className = Objects.requireNonNull(clsName);
+
+        if (className.startsWith(DEFAULT_PACKAGE)) {
+            return type.cast(new com.sun.org.apache.xerces.internal.parsers.SAXParser());
         }
 
-        Class driverClass;
-        if (classLoader == null || internal) {
-            driverClass = Class.forName(className);
-        } else {
-            driverClass = classLoader.loadClass(className);
-        }
-        return driverClass.newInstance();
+        Class<?> driverClass = classLoader.loadClass(className);
+        return type.cast(driverClass.newInstance());
     }
 
 }
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/ParserFactory.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/ParserFactory.java	Mon May 09 14:58:55 2016 -0700
@@ -30,8 +30,6 @@
 
 package org.xml.sax.helpers;
 
-import org.xml.sax.Parser;
-
 
 /**
  * Java-specific class for dynamically loading SAX parsers.
@@ -65,6 +63,8 @@
  * @author David Megginson
  * @version 2.0.1 (sax2r2)
  */
+@SuppressWarnings( "deprecation" )
+@Deprecated(since="5")
 public class ParserFactory {
     private static SecuritySupport ss = new SecuritySupport();
 
@@ -97,7 +97,7 @@
      * @see #makeParser(java.lang.String)
      * @see org.xml.sax.Parser
      */
-    public static Parser makeParser ()
+    public static org.xml.sax.Parser makeParser ()
         throws ClassNotFoundException,
         IllegalAccessException,
         InstantiationException,
@@ -134,14 +134,13 @@
      * @see #makeParser()
      * @see org.xml.sax.Parser
      */
-    public static Parser makeParser (String className)
+    public static org.xml.sax.Parser makeParser (String className)
         throws ClassNotFoundException,
         IllegalAccessException,
         InstantiationException,
         ClassCastException
     {
-        return (Parser) NewInstance.newInstance (
-                ss.getContextClassLoader(), className);
+        return NewInstance.newInstance (org.xml.sax.Parser.class, ss.getClassLoader(), className);
     }
 
 }
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/SecuritySupport.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/SecuritySupport.java	Mon May 09 14:58:55 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2016, 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
@@ -37,72 +37,56 @@
  */
 class SecuritySupport  {
 
+    /**
+     * Returns the current thread's context class loader, or the system class loader
+     * if the context class loader is null.
+     * @return the current thread's context class loader, or the system class loader
+     * @throws SecurityException
+     */
+    ClassLoader getClassLoader() throws SecurityException{
+        return AccessController.doPrivileged((PrivilegedAction<ClassLoader>)() -> {
+            ClassLoader cl = Thread.currentThread().getContextClassLoader();
+            if (cl == null) {
+                cl = ClassLoader.getSystemClassLoader();
+            }
 
-    ClassLoader getContextClassLoader() throws SecurityException{
-        return (ClassLoader)
-                AccessController.doPrivileged(new PrivilegedAction() {
-            public Object run() {
-                ClassLoader cl = null;
-                //try {
-                cl = Thread.currentThread().getContextClassLoader();
-                //} catch (SecurityException ex) { }
-
-                if (cl == null)
-                    cl = ClassLoader.getSystemClassLoader();
-
-                return cl;
-            }
+            return cl;
         });
     }
 
     String getSystemProperty(final String propName) {
-        return (String)
-            AccessController.doPrivileged(new PrivilegedAction() {
-                public Object run() {
-                    return System.getProperty(propName);
-                }
-            });
+        return AccessController.doPrivileged((PrivilegedAction<String>)()
+                -> System.getProperty(propName));
     }
 
     FileInputStream getFileInputStream(final File file)
         throws FileNotFoundException
     {
         try {
-            return (FileInputStream)
-                AccessController.doPrivileged(new PrivilegedExceptionAction() {
-                    public Object run() throws FileNotFoundException {
-                        return new FileInputStream(file);
-                    }
-                });
+            return AccessController.doPrivileged((PrivilegedExceptionAction<FileInputStream>)() ->
+                    new FileInputStream(file));
         } catch (PrivilegedActionException e) {
             throw (FileNotFoundException)e.getException();
         }
     }
 
-    InputStream getResourceAsStream(final ClassLoader cl,
-                                           final String name)
+
+    InputStream getResourceAsStream(final ClassLoader cl, final String name)
     {
-        return (InputStream)
-            AccessController.doPrivileged(new PrivilegedAction() {
-                public Object run() {
-                    InputStream ris;
-                    if (cl == null) {
-                        ris = SecuritySupport.class.getResourceAsStream(name);
-                    } else {
-                        ris = cl.getResourceAsStream(name);
-                    }
-                    return ris;
-                }
-            });
+        return AccessController.doPrivileged((PrivilegedAction<InputStream>) () -> {
+            InputStream ris;
+            if (cl == null) {
+                ris = SecuritySupport.class.getResourceAsStream(name);
+            } else {
+                ris = cl.getResourceAsStream(name);
+            }
+            return ris;
+        });
     }
 
     boolean doesFileExist(final File f) {
-    return ((Boolean)
-            AccessController.doPrivileged(new PrivilegedAction() {
-                public Object run() {
-                    return new Boolean(f.exists());
-                }
-            })).booleanValue();
+        return (AccessController.doPrivileged((PrivilegedAction<Boolean>)() ->
+                new Boolean(f.exists())));
     }
 
 }
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/XMLReaderFactory.java	Wed Jul 05 21:41:01 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/helpers/XMLReaderFactory.java	Mon May 09 14:58:55 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, 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
@@ -32,10 +32,17 @@
 
 package org.xml.sax.helpers;
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import org.xml.sax.SAXException;
 import org.xml.sax.XMLReader;
-import org.xml.sax.SAXException;
 
 
 /**
@@ -70,7 +77,11 @@
  * @since 1.4, SAX 2.0
  * @author David Megginson, David Brownell
  * @version 2.0.1 (sax2r2)
+ *
+ * @deprecated It is recommended to use {@link javax.xml.parsers.SAXParserFactory}
+ * instead.
  */
+@Deprecated(since="9")
 final public class XMLReaderFactory
 {
     /**
@@ -83,47 +94,43 @@
     }
 
     private static final String property = "org.xml.sax.driver";
-    private static SecuritySupport ss = new SecuritySupport();
+    private static final SecuritySupport ss = new SecuritySupport();
 
-    private static String _clsFromJar = null;
-    private static boolean _jarread = false;
     /**
-     * Attempt to create an XMLReader from system defaults.
-     * In environments which can support it, the name of the XMLReader
-     * class is determined by trying each these options in order, and
-     * using the first one which succeeds:
-     * <ul>
-     *
+     * Obtains a new instance of a {@link org.xml.sax.XMLReader}.
+     * This method uses the following ordered lookup procedure to find and load
+     * the {@link org.xml.sax.XMLReader} implementation class:
+     * <p>
+     * <ol>
      * <li>If the system property {@code org.xml.sax.driver}
      * has a value, that is used as an XMLReader class name. </li>
-     *
-     * <li>The JAR "Services API" is used to look for a class name
-     * in the <em>META-INF/services/org.xml.sax.driver</em> file in
-     * jarfiles available to the runtime.</li>
-     *
-     * <li> SAX parser distributions are strongly encouraged to provide
-     * a default XMLReader class name that will take effect only when
-     * previous options (on this list) are not successful.</li>
+     * <li>
+     * Use the service-provider loading facility, defined by the
+     * {@link java.util.ServiceLoader} class, to attempt to locate and load an
+     * implementation of the service {@link org.xml.sax.XMLReader} by using the
+     * {@linkplain java.lang.Thread#getContextClassLoader() current thread's context class loader}.
+     * If the context class loader is null, the
+     * {@linkplain ClassLoader#getSystemClassLoader() system class loader} will
+     * be used.
+     * </li>
+     * <li>
+     * Deprecated. Look for a class name in the {@code META-INF/services/org.xml.sax.driver}
+     * file in a jar file available to the runtime.</li>
+     * <li>
+     * <p>
+     * Otherwise, the system-default implementation is returned.
+     * </li>
+     * </ol>
      *
-     * <li>Finally, if {@link ParserFactory#makeParser()} can
-     * return a system default SAX1 parser, that parser is wrapped in
-     * a {@link ParserAdapter}.  (This is a migration aid for SAX1
-     * environments, where the {@code org.xml.sax.parser} system
-     * property will often be usable.) </li>
-     * </ul>
+     * @apiNote
+     * The process that looks for a class name in the
+     * {@code META-INF/services/org.xml.sax.driver} file in a jar file does not
+     * conform to the specification of the service-provider loading facility
+     * as defined in {@link java.util.ServiceLoader} and therefore does not
+     * support modularization. It is deprecated as of Java SE 9 and subject to
+     * removal in a future release.
      *
-     * <p> In environments such as small embedded systems, which can not
-     * support that flexibility, other mechanisms to determine the default
-     * may be used.
-     *
-     * <p>Note that many Java environments allow system properties to be
-     * initialized on a command line.  This means that <em>in most cases</em>
-     * setting a good value for that property ensures that calls to this
-     * method will succeed, except when security policies intervene.
-     * This will also maximize application portability to older SAX
-     * environments, with less robust implementations of this method.
-     *
-     * @return A new XMLReader.
+     * @return a new XMLReader.
      * @exception org.xml.sax.SAXException If no default XMLReader class
      *            can be identified and instantiated.
      * @see #createXMLReader(java.lang.String)
@@ -132,7 +139,7 @@
         throws SAXException
     {
         String          className = null;
-        ClassLoader     cl = ss.getContextClassLoader();
+        ClassLoader     cl = ss.getClassLoader();
 
         // 1. try the JVM-instance-wide system property
         try {
@@ -140,62 +147,26 @@
         }
         catch (RuntimeException e) { /* continue searching */ }
 
-        // 2. if that fails, try META-INF/services/
+        // 2. try the ServiceLoader
         if (className == null) {
-            if (!_jarread) {
-                _jarread = true;
-                String      service = "META-INF/services/" + property;
-                InputStream in;
-                BufferedReader      reader;
-
-                try {
-                    if (cl != null) {
-                        in = ss.getResourceAsStream(cl, service);
-
-                        // If no provider found then try the current ClassLoader
-                        if (in == null) {
-                            cl = null;
-                            in = ss.getResourceAsStream(cl, service);
-                        }
-                    } else {
-                        // No Context ClassLoader, try the current ClassLoader
-                        in = ss.getResourceAsStream(cl, service);
-                    }
-
-                    if (in != null) {
-                        reader = new BufferedReader (new InputStreamReader (in, "UTF8"));
-                        _clsFromJar = reader.readLine ();
-                        in.close ();
-                    }
-                } catch (Exception e) {
-                }
+            final XMLReader provider = findServiceProvider(XMLReader.class, cl);
+            if (provider != null) {
+                return provider;
             }
-            className = _clsFromJar;
         }
 
-        // 3. Distro-specific fallback
+        // 3. try META-INF/services/org.xml.sax.driver. This old process allows
+        // legacy providers to be found
         if (className == null) {
-// BEGIN DISTRIBUTION-SPECIFIC
-
-            // EXAMPLE:
-            // className = "com.example.sax.XmlReader";
-            // or a $JAVA_HOME/jre/lib/*properties setting...
-            className = "com.sun.org.apache.xerces.internal.parsers.SAXParser";
-
-// END DISTRIBUTION-SPECIFIC
+            className = jarLookup(cl);
         }
 
-        // do we know the XMLReader implementation class yet?
-        if (className != null)
-            return loadClass (cl, className);
+        // 4. Distro-specific fallback
+        if (className == null) {
+            return new com.sun.org.apache.xerces.internal.parsers.SAXParser();
+        }
 
-        // 4. panic -- adapt any SAX1 parser
-        try {
-            return new ParserAdapter (ParserFactory.makeParser ());
-        } catch (Exception e) {
-            throw new SAXException ("Can't create default XMLReader; "
-                    + "is system property org.xml.sax.driver set?");
-        }
+        return loadClass (cl, className);
     }
 
 
@@ -217,14 +188,14 @@
     public static XMLReader createXMLReader (String className)
         throws SAXException
     {
-        return loadClass (ss.getContextClassLoader(), className);
+        return loadClass (ss.getClassLoader(), className);
     }
 
     private static XMLReader loadClass (ClassLoader loader, String className)
     throws SAXException
     {
         try {
-            return (XMLReader) NewInstance.newInstance (loader, className);
+            return NewInstance.newInstance (XMLReader.class, loader, className);
         } catch (ClassNotFoundException e1) {
             throw new SAXException("SAX2 driver class " + className +
                                    " not found", e1);
@@ -240,4 +211,64 @@
                                    " does not implement XMLReader", e4);
         }
     }
+
+    /**
+     * Locates a provider by directly reading the jar service file.
+     * @param loader the ClassLoader to be used to read the service file
+     * @return the name of the provider, or null if nothing is found
+     */
+    private static String jarLookup(final ClassLoader loader) {
+        final ClassLoader cl = Objects.requireNonNull(loader);
+        String clsFromJar = null;
+        String service = "META-INF/services/" + property;
+        InputStream in;
+        BufferedReader      reader;
+
+        try {
+            in = ss.getResourceAsStream(cl, service);
+
+            // If no provider found then try the current ClassLoader
+            if (in == null) {
+                in = ss.getResourceAsStream(null, service);
+            }
+
+            if (in != null) {
+                reader = new BufferedReader (new InputStreamReader (in, "UTF8"));
+                clsFromJar = reader.readLine ();
+                in.close ();
+            }
+        } catch (IOException e) {
+        }
+        return clsFromJar;
+    }
+
+    /*
+     * Try to find provider using the ServiceLoader API
+     *
+     * @param type Base class / Service interface of the factory to find.
+     *
+     * @return instance of provider class if found or null
+     */
+    private static <T> T findServiceProvider(final Class<T> type, final ClassLoader loader)
+            throws SAXException {
+        ClassLoader cl = Objects.requireNonNull(loader);
+        try {
+            return AccessController.doPrivileged((PrivilegedAction<T>) () -> {
+                final ServiceLoader<T> serviceLoader;
+                serviceLoader = ServiceLoader.load(type, cl);
+                final Iterator<T> iterator = serviceLoader.iterator();
+                if (iterator.hasNext()) {
+                    return iterator.next();
+                } else {
+                    return null;
+                }
+            });
+        } catch(ServiceConfigurationError e) {
+            final RuntimeException x = new RuntimeException(
+                    "Provider for " + type + " cannot be created", e);
+            throw new SAXException("Provider for " + type + " cannot be created", x);
+
+          }
+      }
+
 }