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>
- * <<i>package</i>>.<<i>protocol</i>>.Handler
- * </pre></blockquote>
- * where <<i>package</i>> is replaced by the name of the package
- * and <<i>protocol</i>> 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,