1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.ws.policy.privateutil;
  27 
  28 import java.io.BufferedReader;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.InputStreamReader;
  32 import java.lang.reflect.Array;
  33 import java.net.URL;
  34 import java.util.ArrayList;
  35 import java.util.Enumeration;
  36 import java.util.Iterator;
  37 import java.util.List;
  38 import java.util.NoSuchElementException;
  39 import java.util.Set;
  40 import java.util.TreeSet;
  41 
  42 
  43 /**
  44  *
  45  * A simple service-provider lookup mechanism.  A <i>service</i> is a
  46  * well-known set of interfaces and (usually abstract) classes.  A <i>service
  47  * provider</i> is a specific implementation of a service.  The classes in a
  48  * provider typically implement the interfaces and subclass the classes defined
  49  * in the service itself.  Service providers may be installed in an
  50  * implementation of the Java platform in the form of extensions, that is, jar
  51  * files placed into any of the usual extension directories.  Providers may
  52  * also be made available by adding them to the applet or application class
  53  * path or by some other platform-specific means.
  54  * <p/>
  55  * <p> In this lookup mechanism a service is represented by an interface or an
  56  * abstract class.  (A concrete class may be used, but this is not
  57  * recommended.)  A provider of a given service contains one or more concrete
  58  * classes that extend this <i>service class</i> with data and code specific to
  59  * the provider.  This <i>provider class</i> will typically not be the entire
  60  * provider itself but rather a proxy that contains enough information to
  61  * decide whether the provider is able to satisfy a particular request together
  62  * with code that can create the actual provider on demand.  The details of
  63  * provider classes tend to be highly service-specific; no single class or
  64  * interface could possibly unify them, so no such class has been defined.  The
  65  * only requirement enforced here is that provider classes must have a
  66  * zero-argument constructor so that they may be instantiated during lookup.
  67  * <p/>
  68  * <p> A service provider identifies itself by placing a provider-configuration
  69  * file in the resource directory {@code META-INF/services}.  The file's name
  70  * should consist of the fully-qualified name of the abstract service class.
  71  * The file should contain a list of fully-qualified concrete provider-class
  72  * names, one per line.  Space and tab characters surrounding each name, as
  73  * well as blank lines, are ignored.  The comment character is {@code '#'}
  74  * ({@code 0x23}); on each line all characters following the first comment
  75  * character are ignored.  The file must be encoded in UTF-8.
  76  * <p/>
  77  * <p> If a particular concrete provider class is named in more than one
  78  * configuration file, or is named in the same configuration file more than
  79  * once, then the duplicates will be ignored.  The configuration file naming a
  80  * particular provider need not be in the same jar file or other distribution
  81  * unit as the provider itself.  The provider must be accessible from the same
  82  * class loader that was initially queried to locate the configuration file;
  83  * note that this is not necessarily the class loader that found the file.
  84  * <p/>
  85  * <p> <b>Example:</b> Suppose we have a service class named
  86  * {@code java.io.spi.CharCodec}.  It has two abstract methods:
  87  * <p/>
  88  * <pre>
  89  *   public abstract CharEncoder getEncoder(String encodingName);
  90  *   public abstract CharDecoder getDecoder(String encodingName);
  91  * </pre>
  92  * <p/>
  93  * Each method returns an appropriate object or {@code null} if it cannot
  94  * translate the given encoding.  Typical {@code CharCodec} providers will
  95  * support more than one encoding.
  96  * <p/>
  97  * <p> If {@code sun.io.StandardCodec} is a provider of the {@code CharCodec}
  98  * service then its jar file would contain the file
  99  * {@code META-INF/services/java.io.spi.CharCodec}.  This file would contain
 100  * the single line:
 101  * <p/>
 102  * <pre>
 103  *   sun.io.StandardCodec    # Standard codecs for the platform
 104  * </pre>
 105  * <p/>
 106  * To locate an encoder for a given encoding name, the internal I/O code would
 107  * do something like this:
 108  * <p/>
 109  * <pre>
 110  *   CharEncoder getEncoder(String encodingName) {
 111  *       for( CharCodec cc : ServiceFinder.find(CharCodec.class) ) {
 112  *           CharEncoder ce = cc.getEncoder(encodingName);
 113  *           if (ce != null)
 114  *               return ce;
 115  *       }
 116  *       return null;
 117  *   }
 118  * </pre>
 119  * <p/>
 120  * The provider-lookup mechanism always executes in the security context of the
 121  * caller.  Trusted system code should typically invoke the methods in this
 122  * class from within a privileged security context.
 123  *
 124  * @author Mark Reinhold
 125  * @version 1.11, 03/12/19
 126  * @since 1.3
 127  */
 128 final class ServiceFinder<T> implements Iterable<T> {
 129     private static final PolicyLogger LOGGER = PolicyLogger.getLogger(ServiceFinder.class);
 130 
 131     private static final String prefix = "META-INF/services/";
 132 
 133     private final Class<T> serviceClass;
 134     private final ClassLoader classLoader;
 135 
 136     /**
 137      * Locates and incrementally instantiates the available providers of a
 138      * given service using the given class loader.
 139      * <p/>
 140      * <p> This method transforms the name of the given service class into a
 141      * provider-configuration filename as described above and then uses the
 142      * {@code getResources} method of the given class loader to find all
 143      * available files with that name.  These files are then read and parsed to
 144      * produce a list of provider-class names.  The iterator that is returned
 145      * uses the given class loader to lookup and then instantiate each element
 146      * of the list.
 147      * <p/>
 148      * <p> Because it is possible for extensions to be installed into a running
 149      * Java virtual machine, this method may return different results each time
 150      * it is invoked. <p>
 151      *
 152      * @param service The service's abstract service class
 153      * @param loader  The class loader to be used to load provider-configuration files
 154      *                and instantiate provider classes, or {@code null} if the system
 155      *                class loader (or, failing that the bootstrap class loader) is to
 156      *                be used
 157      * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
 158      *                                   or names a provider class that cannot be found and instantiated
 159      * @see #find(Class)
 160      */
 161     static <T> ServiceFinder<T> find(final Class<T> service, final ClassLoader loader) {
 162         if (null==service) {
 163             throw LOGGER.logSevereException(new NullPointerException(LocalizationMessages.WSP_0032_SERVICE_CAN_NOT_BE_NULL()));
 164         }
 165         return new ServiceFinder<T>(service,loader);
 166     }
 167 
 168     /**
 169      * Locates and incrementally instantiates the available providers of a
 170      * given service using the context class loader.  This convenience method
 171      * is equivalent to
 172      * <p/>
 173      * <pre>
 174      *   ClassLoader cl = Thread.currentThread().getContextClassLoader();
 175      *   return Service.providers(service, cl);
 176      * </pre>
 177      *
 178      * @param service The service's abstract service class
 179      *
 180      * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
 181      *                                   or names a provider class that cannot be found and instantiated
 182      * @see #find(Class, ClassLoader)
 183      */
 184     public static <T> ServiceFinder<T> find(final Class<T> service) {
 185         return find(service,Thread.currentThread().getContextClassLoader());
 186     }
 187 
 188     private ServiceFinder(Class<T> service, ClassLoader loader) {
 189         this.serviceClass = service;
 190         this.classLoader = loader;
 191     }
 192 
 193     /**
 194      * Returns discovered objects incrementally.
 195      *
 196      * @return An {@code Iterator} that yields provider objects for the given
 197      *         service, in some arbitrary order.  The iterator will throw a
 198      *         {@code ServiceConfigurationError} if a provider-configuration
 199      *         file violates the specified format or if a provider class cannot
 200      *         be found and instantiated.
 201      */
 202     public Iterator<T> iterator() {
 203         return new LazyIterator<T>(serviceClass,classLoader);
 204     }
 205 
 206     /**
 207      * Returns discovered objects all at once.
 208      *
 209      * @return
 210      *      can be empty but never null.
 211      *
 212      * @throws ServiceConfigurationError
 213      */
 214     @SuppressWarnings({"unchecked"})
 215     public T[] toArray() {
 216         List<T> result = new ArrayList<T>();
 217         for (T t : this) {
 218             result.add(t);
 219         }
 220         return result.toArray((T[])Array.newInstance(serviceClass,result.size()));
 221     }
 222 
 223     private static void fail(final Class service, final String msg, final Throwable cause)
 224             throws ServiceConfigurationError {
 225         final ServiceConfigurationError sce
 226                 = new ServiceConfigurationError(LocalizationMessages.WSP_0025_SPI_FAIL_SERVICE_MSG(service.getName(), msg));
 227         if (null != cause) {
 228             sce.initCause(cause);
 229         }
 230 
 231         throw LOGGER.logSevereException(sce);
 232     }
 233 
 234 /*    private static void fail(Class service, String msg)
 235         throws ServiceConfigurationError {
 236         throw new ServiceConfigurationError(LocalizationMessages.WSP_0025_SPI_FAIL_SERVICE_MSG(service.getName(), msg));
 237     }*/
 238 
 239     private static void fail(final Class service, final URL u, final int line, final String msg, final Throwable cause)
 240             throws ServiceConfigurationError {
 241         fail(service, LocalizationMessages.WSP_0024_SPI_FAIL_SERVICE_URL_LINE_MSG(u , line, msg), cause);
 242     }
 243 
 244     /**
 245      * Parse a single line from the given configuration file, adding the name
 246      * on the line to both the names list and the returned set iff the name is
 247      * not already a member of the returned set.
 248      */
 249     private static int parseLine(final Class service, final URL u, final BufferedReader r, final int lc,
 250                                  final List<String> names, final Set<String> returned)
 251             throws IOException, ServiceConfigurationError {
 252         String ln = r.readLine();
 253         if (ln == null) {
 254             return -1;
 255         }
 256         final int ci = ln.indexOf('#');
 257         if (ci >= 0) ln = ln.substring(0, ci);
 258         ln = ln.trim();
 259         final int n = ln.length();
 260         if (n != 0) {
 261             if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
 262                 fail(service, u, lc, LocalizationMessages.WSP_0067_ILLEGAL_CFG_FILE_SYNTAX(), null);
 263             int cp = ln.codePointAt(0);
 264             if (!Character.isJavaIdentifierStart(cp))
 265                 fail(service, u, lc, LocalizationMessages.WSP_0066_ILLEGAL_PROVIDER_CLASSNAME(ln), null);
 266             for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
 267                 cp = ln.codePointAt(i);
 268                 if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
 269                     fail(service, u, lc, LocalizationMessages.WSP_0066_ILLEGAL_PROVIDER_CLASSNAME(ln), null);
 270             }
 271             if (!returned.contains(ln)) {
 272                 names.add(ln);
 273                 returned.add(ln);
 274             }
 275         }
 276         return lc + 1;
 277     }
 278 
 279     /**
 280      * Parse the content of the given URL as a provider-configuration file.
 281      *
 282      * @param service  The service class for which providers are being sought;
 283      *                 used to construct error detail strings
 284      * @param u        The URL naming the configuration file to be parsed
 285      * @param returned A Set containing the names of provider classes that have already
 286      *                 been returned.  This set will be updated to contain the names
 287      *                 that will be yielded from the returned {@code Iterator}.
 288      * @return A (possibly empty) {@code Iterator} that will yield the
 289      *         provider-class names in the given configuration file that are
 290      *         not yet members of the returned set
 291      * @throws ServiceConfigurationError If an I/O error occurs while reading from the given URL, or
 292      *                                   if a configuration-file format error is detected
 293      */
 294     @SuppressWarnings({"StatementWithEmptyBody"})
 295     private static Iterator<String> parse(Class service, URL u, Set<String> returned)
 296             throws ServiceConfigurationError {
 297         InputStream in = null;
 298         BufferedReader r = null;
 299         ArrayList<String> names = new ArrayList<String>();
 300         try {
 301             in = u.openStream();
 302             r = new BufferedReader(new InputStreamReader(in, "utf-8"));
 303             int lc = 1;
 304             while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0) ;
 305         } catch (IOException x) {
 306             fail(service, ": " + x, x);
 307         } finally {
 308             try {
 309                 if (r != null) r.close();
 310                 if (in != null) in.close();
 311             } catch (IOException y) {
 312                 fail(service, ": " + y, y);
 313             }
 314         }
 315         return names.iterator();
 316     }
 317 
 318 
 319     /**
 320      * Private inner class implementing fully-lazy provider lookup
 321      */
 322     private static class LazyIterator<T> implements Iterator<T> {
 323         Class<T> service;
 324         ClassLoader loader;
 325         Enumeration<URL> configs = null;
 326         Iterator<String> pending = null;
 327         Set<String> returned = new TreeSet<String>();
 328         String nextName = null;
 329 
 330         private LazyIterator(Class<T> service, ClassLoader loader) {
 331             this.service = service;
 332             this.loader = loader;
 333         }
 334 
 335         public boolean hasNext() throws ServiceConfigurationError {
 336             if (nextName != null) {
 337                 return true;
 338             }
 339             if (configs == null) {
 340                 try {
 341                     final String fullName = prefix + service.getName();
 342                     if (loader == null)
 343                         configs = ClassLoader.getSystemResources(fullName);
 344                     else
 345                         configs = loader.getResources(fullName);
 346                 } catch (IOException x) {
 347                     fail(service, ": " + x, x);
 348                 }
 349             }
 350             while ((pending == null) || !pending.hasNext()) {
 351                 if (!configs.hasMoreElements()) {
 352                     return false;
 353                 }
 354                 pending = parse(service, configs.nextElement(), returned);
 355             }
 356             nextName = pending.next();
 357             return true;
 358         }
 359 
 360         public T next() throws ServiceConfigurationError {
 361             if (!hasNext()) {
 362                 throw new NoSuchElementException();
 363             }
 364             final String cn = nextName;
 365             nextName = null;
 366             try {
 367                 return service.cast(Class.forName(cn, true, loader).newInstance());
 368             } catch (ClassNotFoundException x) {
 369                 fail(service, LocalizationMessages.WSP_0027_SERVICE_PROVIDER_NOT_FOUND(cn), x);
 370             } catch (Exception x) {
 371                 fail(service, LocalizationMessages.WSP_0028_SERVICE_PROVIDER_COULD_NOT_BE_INSTANTIATED(cn), x);
 372             }
 373             return null;    /* This cannot happen */
 374         }
 375 
 376         public void remove() {
 377             throw new UnsupportedOperationException();
 378         }
 379     }
 380 }