src/java.base/share/classes/java/net/URL.java

Print this page

        

@@ -25,12 +25,22 @@
 
 package java.net;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Hashtable;
-import java.util.StringTokenizer;
+import java.util.List;
+import java.util.Objects;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
 import sun.security.util.SecurityConstants;
 
 /**
  * Class {@code URL} represents a Uniform Resource
  * Locator, a pointer to a "resource" on the World

@@ -240,33 +250,36 @@
      * If this is the first URL object being created with the specified
      * protocol, a <i>stream protocol handler</i> object, an instance of
      * class {@code URLStreamHandler}, is created for that protocol:
      * <ol>
      * <li>If the application has previously set up an instance of
-     *     {@code URLStreamHandlerFactory} as the stream handler factory,
+     *     {@code URLStreamHandlerFactory}, through {@linkplain
+     *     #setURLStreamHandlerFactory(URLStreamHandlerFactory)
+     *     setURLStreamHandlerFactory}, as the stream handler factory,
      *     then the {@code createURLStreamHandler} method of that instance
      *     is called with the protocol string as an argument to create the
      *     stream protocol handler.
      * <li>If no {@code URLStreamHandlerFactory} has yet been set up,
      *     or if the factory's {@code createURLStreamHandler} method
-     *     returns {@code null}, then the constructor finds the
-     *     value of the system property:
-     *     <blockquote><pre>
-     *         java.protocol.handler.pkgs
-     *     </pre></blockquote>
-     *     If the value of that system property is not {@code null},
-     *     it is interpreted as a list of packages separated by a vertical
-     *     slash character '{@code |}'. The constructor tries to load
-     *     the class named:
-     *     <blockquote><pre>
-     *         &lt;<i>package</i>&gt;.&lt;<i>protocol</i>&gt;.Handler
-     *     </pre></blockquote>
-     *     where &lt;<i>package</i>&gt; is replaced by the name of the package
-     *     and &lt;<i>protocol</i>&gt; is replaced by the name of the protocol.
-     *     If this class does not exist, or if the class exists but it is not
-     *     a subclass of {@code URLStreamHandler}, then the next package
-     *     in the list is tried.
+     *     returns {@code null}, then the list of factories, set through
+     *     {@linkplain #addURLStreamHandlerFactory(URLStreamHandlerFactory)
+     *     addURLStreamHandlerFactory} is consulted. The {@code
+     *     createURLStreamHandler} method of each factory is invoked, in
+     *     registration order, with the protocol string, until a factory returns
+     *     non-null, or all the factories in the list have been exhausted.
+     * <li>If the previous step fails to find a protocol handler, then the
+     *     {@linkplain java.util.ServiceLoader ServiceLoader} mechanism is used
+     *     to locate a {@code URLStreamHandlerFactory} provider using the system
+     *     class loader. The ordering that providers are located is
+     *     implementation specific, and an implementation is free to cache the
+     *     located providers. A {@linkplain java.util.ServiceConfigurationError
+     *     ServiceConfigurationError}, {@code Error} or {@code RuntimeException}
+     *     thrown from the {@code createURLStreamHandler}, if encountered, will
+     *     be propagated to the calling thread. The {@code
+     *     createURLStreamHandler} method of each provider, if instantiated, is
+     *     invoked, with the protocol string, until a provider returns non-null,
+     *     or all providers have been exhausted.
      * <li>If the previous step fails to find a protocol handler, then the
      *     constructor tries to load a built-in protocol handler.
      *     If this class does not exist, or if the class exists but it is not a
      *     subclass of {@code URLStreamHandler}, then a
      *     {@code MalformedURLException} is thrown.

@@ -275,12 +288,13 @@
      * <p>Protocol handlers for the following protocols are guaranteed
      * to exist on the search path :-
      * <blockquote><pre>
      *     http, https, file, and jar
      * </pre></blockquote>
-     * Protocol handlers for additional protocols may also be
-     * available.
+     * Protocol handlers for additional protocols may also be  available. Some
+     * protocols, that are fundamental to the platform, may have restrictions
+     * around when, or if, their built-in handlers can be overridden.
      *
      * <p>No validation of the inputs is performed by this constructor.
      *
      * @param      protocol   the name of the protocol to use.
      * @param      host       the name of the host.

@@ -1067,14 +1081,18 @@
     throws java.io.IOException {
         return openConnection().getContent(classes);
     }
 
     /**
-     * The URLStreamHandler factory.
+     * The Application URLStreamHandler factory.
      */
     private static volatile URLStreamHandlerFactory factory;
 
+    /** The list of factories. */
+    private static final CopyOnWriteArrayList<URLStreamHandlerFactory> factoryList =
+            new CopyOnWriteArrayList<>();
+
     /**
      * Sets an application's {@code URLStreamHandlerFactory}.
      * This method can be called at most once in a given Java Virtual
      * Machine.
      *

@@ -1105,88 +1123,198 @@
             if (security != null) {
                 security.checkSetFactory();
             }
             handlers.clear();
 
-            // ensure the core protocol handlers are loaded before setting
-            // a custom URLStreamHandlerFactory
-            ensureHandlersLoaded("jrt", "jar", "file");
-
             // safe publication of URLStreamHandlerFactory with volatile write
             factory = fac;
         }
     }
 
     /**
-     * A table of protocol handlers.
+     * Adds a {@linkplain URLStreamHandlerFactory}. This method can be called
+     * multiple times in order to create an effective list of factories.
+     *
+     * <p> Factories, added through a call to this method, will have their
+     * {@code createURLStreamHandler} method called, in the order in which they
+     * were added, to construct a stream protocol handler from a protocol name.
+     * A factory that cannot construct a stream protocol handler for a
+     * particular protocol name should return {@code null}. The next factory in
+     * the list, if there is one, will then be consulted.
+     *
+     * <p> If there is a security manager, this method first calls
+     * the security manager's {@code checkSetFactory} method
+     * to ensure the operation is allowed.
+     * This could result in a SecurityException.
+     *
+     * @apiNote
+     * This method is intended to be used by long running applications, to
+     * support adding additional system-wide protocol handlers, beyond that of
+     * the built-in handlers. It can be used in some situations to override
+     * built-in handlers, that may not be possible to locate using services,
+     * like {@code jar} for example.
+     *
+     * @param  factory the factory
+     * @throws SecurityException  if a security manager exists and its {@link
+     *         SecurityManager#checkSetFactory} method doesn't allow the operation
+     * @throws NullPointerException if {@code factory} is null
+     * @since 1.9
      */
-    static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();
-    private static Object streamHandlerLock = new Object();
+    public static void addURLStreamHandlerFactory(URLStreamHandlerFactory factory) {
+        Objects.requireNonNull(factory);
+        synchronized (streamHandlerLock) {
+            SecurityManager security = System.getSecurityManager();
+            if (security != null) {
+                security.checkSetFactory();
+            }
+            handlers.clear();
 
-    /**
-     * Returns the Stream Handler.
-     * @param protocol the protocol to use
-     */
-    static URLStreamHandler getURLStreamHandler(String protocol) {
+            factoryList.add(factory);
+        }
+    }
 
-        URLStreamHandler handler = handlers.get(protocol);
-        if (handler == null) {
+    private static final URLStreamHandlerFactory defaultFactory = new DefaultFactory();
 
-            boolean checkedWithFactory = false;
+    private static class DefaultFactory implements URLStreamHandlerFactory {
+        private static String PREFIX = "sun.net.www.protocol";
+
+        public URLStreamHandler createURLStreamHandler(String protocol) {
+            String name = PREFIX + "." + protocol + ".Handler";
+            try {
+                Class<?> c = Class.forName(name);
+                return (URLStreamHandler)c.newInstance();
+            } catch (ClassNotFoundException x) {
+                // ignore
+            } catch (Exception e) {
+                // For compatibility, all Exceptions are ignored.
+                // any number of exceptions can get thrown here
+            }
+            return null;
+        }
+    }
 
+    private static final URLStreamHandler NULL_HANDLER = new URLStreamHandler() {
+        public URLConnection openConnection(URL u) { return null; }
+        public URLConnection openConnection(URL u, Proxy p)  { return null; }
+    };
+
+    /** Returns a handler instance for the given protocol, null if the factories
+     *  could not create a handler, or NULL_HANDLER if there are no factories.
+     */
+    private static URLStreamHandler handlerFromSettableFactory(String protocol) {
+        URLStreamHandler handler = NULL_HANDLER;
             // Use the factory (if any). Volatile read makes
             // URLStreamHandlerFactory appear fully initialized to current thread.
             URLStreamHandlerFactory fac = factory;
-            if (fac != null) {
+        if (fac != null)
                 handler = fac.createURLStreamHandler(protocol);
-                checkedWithFactory = true;
+
+        if (handler == NULL_HANDLER || handler == null) {
+            for (URLStreamHandlerFactory f : factoryList) {
+                handler = f.createURLStreamHandler(protocol);
+                if (handler != null)
+                    return handler;
+            }
+        }
+        return handler;
             }
 
-            // Try java protocol handler
-            if (handler == null) {
-                String packagePrefixList = null;
+    // installed service providers
+    private static volatile List<URLStreamHandlerFactory> installedProviders;
+    private static final Object providersLock = new Object();
+    private static boolean loadingProviders  = false;
 
-                packagePrefixList
-                    = java.security.AccessController.doPrivileged(
-                    new sun.security.action.GetPropertyAction(
-                        protocolPathProp,""));
-                if (packagePrefixList != "") {
-                    packagePrefixList += "|";
+    private static List<URLStreamHandlerFactory> loadInstalledProviders() {
+        List<URLStreamHandlerFactory> list = new ArrayList<>();
+        ServiceLoader<URLStreamHandlerFactory> sl = ServiceLoader
+                .load(URLStreamHandlerFactory.class, ClassLoader.getSystemClassLoader());
+        // ServiceConfigurationError may be throw here
+        for (URLStreamHandlerFactory provider: sl)
+            list.add(provider);
+
+        return list;
+    }
+
+    private static List<URLStreamHandlerFactory> installedProviders() {
+        if (installedProviders == null) {
+            synchronized (providersLock) {
+                if (installedProviders == null) {
+                    if (loadingProviders) {
+                        throw new Error(
+                            "Circular loading of URL stream handler providers detected");
                 }
+                    loadingProviders = true;
 
-                // REMIND: decide whether to allow the "null" class prefix
-                // or not.
-                packagePrefixList += "sun.net.www.protocol";
+                    // do not use a lambda, or method ref here.
+                    List<URLStreamHandlerFactory> list = AccessController
+                        .doPrivileged(new PrivilegedAction<List<URLStreamHandlerFactory>>() {
+                            public List<URLStreamHandlerFactory> run() {
+                                return URL.loadInstalledProviders();
+                            }
+                        });
+
+                    installedProviders = Collections.unmodifiableList(list);
+                }
+            }
+        }
+        return installedProviders;
+    }
 
-                StringTokenizer packagePrefixIter =
-                    new StringTokenizer(packagePrefixList, "|");
+    private static final String SL_SKIP_PROTOCOL = "jar";
 
-                while (handler == null &&
-                       packagePrefixIter.hasMoreTokens()) {
+    private static URLStreamHandler handlerFromInstalledProviders(String protocol) {
+        if (protocol.equalsIgnoreCase(SL_SKIP_PROTOCOL))
+            return null;
 
-                    String packagePrefix =
-                      packagePrefixIter.nextToken().trim();
-                    try {
-                        String clsName = packagePrefix + "." + protocol +
-                          ".Handler";
-                        Class<?> cls = null;
-                        try {
-                            cls = Class.forName(clsName);
-                        } catch (ClassNotFoundException e) {
-                            ClassLoader cl = ClassLoader.getSystemClassLoader();
-                            if (cl != null) {
-                                cls = cl.loadClass(clsName);
+        URLStreamHandler handler = null;
+        for (URLStreamHandlerFactory f : installedProviders()) {
+            handler = f.createURLStreamHandler(protocol);
+            if (handler != null)
+                return handler;
                             }
+        return handler;
                         }
-                        if (cls != null) {
-                            handler  =
-                              (URLStreamHandler)cls.newInstance();
+
+    private static final String[] UNOVERRIDEABLE_PROTOCOLS = {"file", "jrt"};
+    private static boolean overrideable(String protocol) {
+        for (String p : UNOVERRIDEABLE_PROTOCOLS)
+            if (protocol.equalsIgnoreCase(p))
+                return false;
+        return true;
                         }
-                    } catch (Exception e) {
-                        // any number of exceptions can get thrown here
+
+    /**
+     * A table of protocol handlers.
+     */
+    static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();
+    private static final Object streamHandlerLock = new Object();
+
+    /**
+     * Returns the Stream Handler.
+     * @param protocol the protocol to use
+     */
+    static URLStreamHandler getURLStreamHandler(String protocol) {
+
+        URLStreamHandler handler = handlers.get(protocol);
+
+        if (handler != null)
+            return handler;
+
+        boolean checkedWithFactory = false;
+
+        if (overrideable(protocol)) {
+            handler = handlerFromSettableFactory(protocol);
+            if (handler != NULL_HANDLER)
+                checkedWithFactory = true;
+
+            if (handler == null || handler == NULL_HANDLER) {
+                handler = handlerFromInstalledProviders(protocol);
                     }
                 }
+        if (handler == null) {
+            // Try the built-in protocol handler
+            handler = defaultFactory.createURLStreamHandler(protocol);
             }
 
             synchronized (streamHandlerLock) {
 
                 URLStreamHandler handler2 = null;

@@ -1199,15 +1327,15 @@
                     return handler2;
                 }
 
                 // Check with factory if another thread set a
                 // factory since our last check
-                if (!checkedWithFactory && (fac = factory) != null) {
-                    handler2 = fac.createURLStreamHandler(protocol);
+            if (!checkedWithFactory) {
+                handler2 = handlerFromSettableFactory(protocol);
                 }
 
-                if (handler2 != null) {
+            if (!(handler2 == null || handler2 == NULL_HANDLER)) {
                     // The handler from the factory must be given more
                     // importance. Discard the default handler that
                     // this thread created.
                     handler = handler2;
                 }

@@ -1216,27 +1344,15 @@
                 if (handler != null) {
                     handlers.put(protocol, handler);
                 }
 
             }
-        }
 
         return handler;
-
     }
 
     /**
-     * Ensures that the given protocol handlers are loaded
-     */
-    private static void ensureHandlersLoaded(String... protocols) {
-        for (String protocol: protocols) {
-            getURLStreamHandler(protocol);
-        }
-    }
-
-
-    /**
      * WriteObject is called to save the state of the URL to an
      * ObjectOutputStream. The handler is not saved since it is
      * specific to this system.
      *
      * @serialData the default write object value. When read back in,