< prev index next >

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

Print this page

        

@@ -26,12 +26,16 @@
 package java.net;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.PrivilegedAction;
 import java.util.Hashtable;
 import java.util.Date;
+import java.util.Iterator;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
 import java.util.StringTokenizer;
 import java.util.Collections;
 import java.util.Map;
 import java.util.List;
 import java.security.Permission;

@@ -105,11 +109,11 @@
  *   <li>{@code getContentEncoding}
  *   <li>{@code getContentLength}
  *   <li>{@code getContentType}
  *   <li>{@code getDate}
  *   <li>{@code getExpiration}
- *   <li>{@code getLastModifed}
+ *   <li>{@code getLastModified}
  * </ul>
  * <p>
  * provide convenient access to these fields. The
  * {@code getContentType} method is used by the
  * {@code getContent} method to determine the type of the remote

@@ -693,20 +697,34 @@
      * Retrieves the contents of this URL connection.
      * <p>
      * This method first determines the content type of the object by
      * calling the {@code getContentType} method. If this is
      * the first time that the application has seen that specific content
-     * type, a content handler for that content type is created:
+     * type, a content handler for that content type is created.
+     * <p> This is done as follows:
      * <ol>
      * <li>If the application has set up a content handler factory instance
      *     using the {@code setContentHandlerFactory} method, the
      *     {@code createContentHandler} method of that instance is called
      *     with the content type as an argument; the result is a content
      *     handler for that content type.
-     * <li>If no content handler factory has yet been set up, or if the
-     *     factory's {@code createContentHandler} method returns
-     *     {@code null}, then this method tries to load a content handler
+     * <li>If no {@code ContentHandlerFactory} has yet been set up,
+     *     or if the factory's {@code createContentHandler} method
+     *     returns {@code null}, then the {@linkplain java.util.ServiceLoader
+     *     ServiceLoader} mechanism is used to locate {@linkplain
+     *     java.net.ContentHandlerFactory ContentHandlerFactory}
+     *     implementations using the system class
+     *     loader. The order that factories are located is implementation
+     *     specific, and an implementation is free to cache the located
+     *     factories. A {@linkplain java.util.ServiceConfigurationError
+     *     ServiceConfigurationError}, {@code Error} or {@code RuntimeException}
+     *     thrown from the {@code createContentHandler}, if encountered, will
+     *     be propagated to the calling thread. The {@code
+     *     createContentHandler} method of each factory, if instantiated, is
+     *     invoked, with the content type, until a factory returns non-null,
+     *     or all factories have been exhausted.
+     * <li>Failing that, this method tries to load a content handler
      *     class as defined by {@link java.net.ContentHandler ContentHandler}.
      *     If the class does not exist, or is not a subclass of {@code
      *     ContentHandler}, then an {@code UnknownServiceException} is thrown.
      * </ol>
      *

@@ -853,12 +871,11 @@
      * @throws IllegalStateException if already connected
      * @see     java.net.URLConnection#doInput
      * @see #getDoInput()
      */
     public void setDoInput(boolean doinput) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
         doInput = doinput;
     }
 
     /**
      * Returns the value of this {@code URLConnection}'s

@@ -883,12 +900,11 @@
      * @param   dooutput   the new value.
      * @throws IllegalStateException if already connected
      * @see #getDoOutput()
      */
     public void setDoOutput(boolean dooutput) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
         doOutput = dooutput;
     }
 
     /**
      * Returns the value of this {@code URLConnection}'s

@@ -909,12 +925,11 @@
      * @param   allowuserinteraction   the new value.
      * @throws IllegalStateException if already connected
      * @see     #getAllowUserInteraction()
      */
     public void setAllowUserInteraction(boolean allowuserinteraction) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
         allowUserInteraction = allowuserinteraction;
     }
 
     /**
      * Returns the value of the {@code allowUserInteraction} field for

@@ -972,12 +987,11 @@
      * or not to allow caching
      * @throws IllegalStateException if already connected
      * @see #getUseCaches()
      */
     public void setUseCaches(boolean usecaches) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
         useCaches = usecaches;
     }
 
     /**
      * Returns the value of this {@code URLConnection}'s

@@ -998,12 +1012,11 @@
      * @param   ifmodifiedsince   the new value.
      * @throws IllegalStateException if already connected
      * @see     #getIfModifiedSince()
      */
     public void setIfModifiedSince(long ifmodifiedsince) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
         ifModifiedSince = ifmodifiedsince;
     }
 
     /**
      * Returns the value of this object's {@code ifModifiedSince} field.

@@ -1053,16 +1066,15 @@
      *
      * @param   key     the keyword by which the request is known
      *                  (e.g., "{@code Accept}").
      * @param   value   the value associated with it.
      * @throws IllegalStateException if already connected
-     * @throws NullPointerException if key is <CODE>null</CODE>
+     * @throws NullPointerException if key is {@code null}
      * @see #getRequestProperty(java.lang.String)
      */
     public void setRequestProperty(String key, String value) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
         if (key == null)
             throw new NullPointerException ("key is null");
 
         if (requests == null)
             requests = new MessageHeader();

@@ -1082,12 +1094,11 @@
      * @throws NullPointerException if key is null
      * @see #getRequestProperties()
      * @since 1.4
      */
     public void addRequestProperty(String key, String value) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
         if (key == null)
             throw new NullPointerException ("key is null");
 
         if (requests == null)
             requests = new MessageHeader();

@@ -1105,12 +1116,11 @@
      *           connection. If key is null, then null is returned.
      * @throws IllegalStateException if already connected
      * @see #setRequestProperty(java.lang.String, java.lang.String)
      */
     public String getRequestProperty(String key) {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
 
         if (requests == null)
             return null;
 
         return requests.findValue(key);

@@ -1127,12 +1137,11 @@
      * @return  a Map of the general request properties for this connection.
      * @throws IllegalStateException if already connected
      * @since 1.4
      */
     public Map<String,List<String>> getRequestProperties() {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+        checkConnected();
 
         if (requests == null)
             return Collections.emptyMap();
 
         return requests.getHeaders(null);

@@ -1181,11 +1190,11 @@
     }
 
     /**
      * The ContentHandler factory.
      */
-    static ContentHandlerFactory factory;
+    private static volatile ContentHandlerFactory factory;
 
     /**
      * Sets the {@code ContentHandlerFactory} of an
      * application. It can be called at most once by an application.
      * <p>

@@ -1214,41 +1223,49 @@
             security.checkSetFactory();
         }
         factory = fac;
     }
 
-    private static Hashtable<String, ContentHandler> handlers = new Hashtable<>();
+    private static final Hashtable<String, ContentHandler> handlers = new Hashtable<>();
 
     /**
      * Gets the Content Handler appropriate for this connection.
      */
-    synchronized ContentHandler getContentHandler()
-        throws UnknownServiceException
-    {
+    private ContentHandler getContentHandler() throws UnknownServiceException {
         String contentType = stripOffParameters(getContentType());
-        ContentHandler handler = null;
-        if (contentType == null)
+        if (contentType == null) {
             throw new UnknownServiceException("no content-type");
-        try {
-            handler = handlers.get(contentType);
+        }
+
+        ContentHandler handler = handlers.get(contentType);
             if (handler != null)
                 return handler;
-        } catch(Exception e) {
-        }
 
-        if (factory != null)
+        if (factory != null) {
             handler = factory.createContentHandler(contentType);
-        if (handler == null) {
+            if (handler != null)
+                return handler;
+        }
+
+        handler = lookupContentHandlerViaProvider(contentType);
+
+        if (handler != null) {
+            ContentHandler h = handlers.putIfAbsent(contentType, handler);
+            return h != null ? h : handler;
+        }
+
             try {
                 handler = lookupContentHandlerClassFor(contentType);
-            } catch(Exception e) {
+        } catch (Exception e) {
                 e.printStackTrace();
                 handler = UnknownContentHandler.INSTANCE;
             }
-            handlers.put(contentType, handler);
-        }
-        return handler;
+
+        assert handler != null;
+
+        ContentHandler h = handlers.putIfAbsent(contentType, handler);
+        return h != null ? h : handler;
     }
 
     /*
      * Media types are in the format: type/subtype*(; parameter).
      * For looking up the content handler, we should ignore those

@@ -1268,26 +1285,25 @@
 
     private static final String contentClassPrefix = "sun.net.www.content";
     private static final String contentPathProp = "java.content.handler.pkgs";
 
     /**
-     * Looks for a content handler in a user-defineable set of places.
-     * By default it looks in sun.net.www.content, but users can define a
-     * vertical-bar delimited set of class prefixes to search through in
-     * addition by defining the java.content.handler.pkgs property.
+     * Looks for a content handler in a user-definable set of places.
+     * By default it looks in {@value #contentClassPrefix}, but users can define
+     * a vertical-bar delimited set of class prefixes to search through in
+     * addition by defining the {@value #contentPathProp} property.
      * The class name must be of the form:
      * <pre>
      *     {package-prefix}.{major}.{minor}
      * e.g.
      *     YoyoDyne.experimental.text.plain
      * </pre>
      */
-    private ContentHandler lookupContentHandlerClassFor(String contentType)
-        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+    private ContentHandler lookupContentHandlerClassFor(String contentType) {
         String contentHandlerClassName = typeToPackageName(contentType);
 
-        String contentHandlerPkgPrefixes =getContentHandlerPkgPrefixes();
+        String contentHandlerPkgPrefixes = getContentHandlerPkgPrefixes();
 
         StringTokenizer packagePrefixIter =
             new StringTokenizer(contentHandlerPkgPrefixes, "|");
 
         while (packagePrefixIter.hasMoreTokens()) {

@@ -1303,21 +1319,50 @@
                     if (cl != null) {
                         cls = cl.loadClass(clsName);
                     }
                 }
                 if (cls != null) {
-                    ContentHandler handler =
-                        (ContentHandler)cls.newInstance();
-                    return handler;
-                }
-            } catch(Exception e) {
+                    return (ContentHandler) cls.newInstance();
             }
+            } catch(Exception ignored) { }
         }
 
         return UnknownContentHandler.INSTANCE;
     }
 
+    private ContentHandler lookupContentHandlerViaProvider(String contentType) {
+        return AccessController.doPrivileged(
+                new PrivilegedAction<>() {
+                    @Override
+                    public ContentHandler run() {
+                        ClassLoader cl = ClassLoader.getSystemClassLoader();
+                        ServiceLoader<ContentHandlerFactory> sl =
+                                ServiceLoader.load(ContentHandlerFactory.class, cl);
+
+                        Iterator<ContentHandlerFactory> iterator = sl.iterator();
+
+                        ContentHandler handler = null;
+                        while (iterator.hasNext()) {
+                            ContentHandlerFactory f;
+                            try {
+                                f = iterator.next();
+                            } catch (ServiceConfigurationError e) {
+                                if (e.getCause() instanceof SecurityException) {
+                                    continue;
+                                }
+                                throw e;
+                            }
+                            handler = f.createContentHandler(contentType);
+                            if (handler != null) {
+                                break;
+                            }
+                        }
+                        return handler;
+                    }
+                });
+    }
+
     /**
      * Utility function to map a MIME content type into an equivalent
      * pair of class name components.  For example: "text/html" would
      * be returned as "text.html"
      */

@@ -1343,12 +1388,12 @@
 
     /**
      * Returns a vertical bar separated list of package prefixes for potential
      * content handlers.  Tries to get the java.content.handler.pkgs property
      * to use as a set of package prefixes to search.  Whether or not
-     * that property has been defined, the sun.net.www.content is always
-     * the last one on the returned package list.
+     * that property has been defined, the {@value #contentClassPrefix}
+     * is always the last one on the returned package list.
      */
     private String getContentHandlerPkgPrefixes() {
         String packagePrefixList = AccessController.doPrivileged(
             new sun.security.action.GetPropertyAction(contentPathProp, ""));
 

@@ -1762,13 +1807,16 @@
             skipped += eachSkip;
         }
         return skipped;
     }
 
+    private void checkConnected() {
+        if (connected)
+            throw new IllegalStateException("Already connected");
+    }
 }
 
-
 class UnknownContentHandler extends ContentHandler {
     static final ContentHandler INSTANCE = new UnknownContentHandler();
 
     public Object getContent(URLConnection uc) throws IOException {
         return uc.getInputStream();
< prev index next >