1 /*
   2  * Copyright (c) 1997, 2013, 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.util;
  27 
  28 import com.sun.istack.internal.NotNull;
  29 import com.sun.istack.internal.Nullable;
  30 import com.sun.xml.internal.ws.api.Component;
  31 import com.sun.xml.internal.ws.api.ComponentEx;
  32 import com.sun.xml.internal.ws.api.server.ContainerResolver;
  33 
  34 import java.io.BufferedReader;
  35 import java.io.IOException;
  36 import java.io.InputStream;
  37 import java.io.InputStreamReader;
  38 import java.lang.reflect.Array;
  39 import java.net.URL;
  40 import java.util.ArrayList;
  41 import java.util.Arrays;
  42 import java.util.Collection;
  43 import java.util.Collections;
  44 import java.util.Enumeration;
  45 import java.util.Iterator;
  46 import java.util.List;
  47 import java.util.NoSuchElementException;
  48 import java.util.Set;
  49 import java.util.TreeSet;
  50 import java.util.WeakHashMap;
  51 import java.util.concurrent.ConcurrentHashMap;
  52 
  53 
  54 /**
  55  * A simple service-provider lookup mechanism.  A <i>service</i> is a
  56  * well-known set of interfaces and (usually abstract) classes.  A <i>service
  57  * provider</i> is a specific implementation of a service.  The classes in a
  58  * provider typically implement the interfaces and subclass the classes defined
  59  * in the service itself.  Service providers may be installed in an
  60  * implementation of the Java platform in the form of extensions, that is, jar
  61  * files placed into any of the usual extension directories.  Providers may
  62  * also be made available by adding them to the applet or application class
  63  * path or by some other platform-specific means.
  64  * <p/>
  65  * <p> In this lookup mechanism a service is represented by an interface or an
  66  * abstract class.  (A concrete class may be used, but this is not
  67  * recommended.)  A provider of a given service contains one or more concrete
  68  * classes that extend this <i>service class</i> with data and code specific to
  69  * the provider.  This <i>provider class</i> will typically not be the entire
  70  * provider itself but rather a proxy that contains enough information to
  71  * decide whether the provider is able to satisfy a particular request together
  72  * with code that can create the actual provider on demand.  The details of
  73  * provider classes tend to be highly service-specific; no single class or
  74  * interface could possibly unify them, so no such class has been defined.  The
  75  * only requirement enforced here is that provider classes must have a
  76  * zero-argument constructor so that they may be instantiated during lookup.
  77  * <p/>
  78  * <p> A service provider identifies itself by placing a provider-configuration
  79  * file in the resource directory {@code META-INF/services}.  The file's name
  80  * should consist of the fully-qualified name of the abstract service class.
  81  * The file should contain a list of fully-qualified concrete provider-class
  82  * names, one per line.  Space and tab characters surrounding each name, as
  83  * well as blank lines, are ignored.  The comment character is {@code '#'}
  84  * ({@code 0x23}); on each line all characters following the first comment
  85  * character are ignored.  The file must be encoded in UTF-8.
  86  * <p/>
  87  * <p> If a particular concrete provider class is named in more than one
  88  * configuration file, or is named in the same configuration file more than
  89  * once, then the duplicates will be ignored.  The configuration file naming a
  90  * particular provider need not be in the same jar file or other distribution
  91  * unit as the provider itself.  The provider must be accessible from the same
  92  * class loader that was initially queried to locate the configuration file;
  93  * note that this is not necessarily the class loader that found the file.
  94  * <p/>
  95  * <p> <b>Example:</b> Suppose we have a service class named
  96  * {@code java.io.spi.CharCodec}.  It has two abstract methods:
  97  * <p/>
  98  * <pre>
  99  *   public abstract CharEncoder getEncoder(String encodingName);
 100  *   public abstract CharDecoder getDecoder(String encodingName);
 101  * </pre>
 102  * <p/>
 103  * Each method returns an appropriate object or {@code null} if it cannot
 104  * translate the given encoding.  Typical {@code CharCodec} providers will
 105  * support more than one encoding.
 106  * <p/>
 107  * <p> If {@code sun.io.StandardCodec} is a provider of the {@code CharCodec}
 108  * service then its jar file would contain the file
 109  * {@code META-INF/services/java.io.spi.CharCodec}.  This file would contain
 110  * the single line:
 111  * <p/>
 112  * <pre>
 113  *   sun.io.StandardCodec    # Standard codecs for the platform
 114  * </pre>
 115  * <p/>
 116  * To locate an codec for a given encoding name, the internal I/O code would
 117  * do something like this:
 118  * <p/>
 119  * <pre>
 120  *   CharEncoder getEncoder(String encodingName) {
 121  *       for( CharCodec cc : ServiceFinder.find(CharCodec.class) ) {
 122  *           CharEncoder ce = cc.getEncoder(encodingName);
 123  *           if (ce != null)
 124  *               return ce;
 125  *       }
 126  *       return null;
 127  *   }
 128  * </pre>
 129  * <p/>
 130  * The provider-lookup mechanism always executes in the security context of the
 131  * caller.  Trusted system code should typically invoke the methods in this
 132  * class from within a privileged security context.
 133  *
 134  * @author Mark Reinhold
 135  * @version 1.11, 03/12/19
 136  * @since 1.3
 137  */
 138 public final class ServiceFinder<T> implements Iterable<T> {
 139 
 140     private static final String prefix = "META-INF/services/";
 141 
 142     private static WeakHashMap<ClassLoader, ConcurrentHashMap<String, ServiceName[]>> serviceNameCache
 143              = new WeakHashMap<ClassLoader, ConcurrentHashMap<String, ServiceName[]>>();
 144 
 145     private final Class<T> serviceClass;
 146     private final @Nullable ClassLoader classLoader;
 147     private final @Nullable ComponentEx component;
 148 
 149     private static class ServiceName {
 150         final String className;
 151         final URL config;
 152         public ServiceName(String className, URL config) {
 153             this.className = className;
 154             this.config = config;
 155         }
 156     }
 157 
 158     public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @Nullable ClassLoader loader, Component component) {
 159         return new ServiceFinder<T>(service, loader, component);
 160     }
 161 
 162     public static <T> ServiceFinder<T> find(@NotNull Class<T> service, Component component) {
 163         return find(service,Thread.currentThread().getContextClassLoader(),component);
 164     }
 165 
 166     /**
 167      * Locates and incrementally instantiates the available providers of a
 168      * given service using the given class loader.
 169      * <p/>
 170      * <p> This method transforms the name of the given service class into a
 171      * provider-configuration filename as described above and then uses the
 172      * {@code getResources} method of the given class loader to find all
 173      * available files with that name.  These files are then read and parsed to
 174      * produce a list of provider-class names.  The iterator that is returned
 175      * uses the given class loader to lookup and then instantiate each element
 176      * of the list.
 177      * <p/>
 178      * <p> Because it is possible for extensions to be installed into a running
 179      * Java virtual machine, this method may return different results each time
 180      * it is invoked. <p>
 181      *
 182      * @param service The service's abstract service class
 183      * @param loader  The class loader to be used to load provider-configuration files
 184      *                and instantiate provider classes, or {@code null} if the system
 185      *                class loader (or, failing that the bootstrap class loader) is to
 186      *                be used
 187      * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
 188      *                                   or names a provider class that cannot be found and instantiated
 189      * @see #find(Class)
 190      */
 191     public static <T> ServiceFinder<T> find(@NotNull Class<T> service, @Nullable ClassLoader loader) {
 192         return find(service, loader, ContainerResolver.getInstance().getContainer());
 193     }
 194 
 195     /**
 196      * Locates and incrementally instantiates the available providers of a
 197      * given service using the context class loader.  This convenience method
 198      * is equivalent to
 199      * <p/>
 200      * <pre>
 201      *   ClassLoader cl = Thread.currentThread().getContextClassLoader();
 202      *   return Service.providers(service, cl);
 203      * </pre>
 204      *
 205      * @param service The service's abstract service class
 206      *
 207      * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
 208      *                                   or names a provider class that cannot be found and instantiated
 209      * @see #find(Class, ClassLoader)
 210      */
 211     public static <T> ServiceFinder<T> find(Class<T> service) {
 212         return find(service,Thread.currentThread().getContextClassLoader());
 213     }
 214 
 215     private ServiceFinder(Class<T> service, ClassLoader loader, Component component) {
 216         this.serviceClass = service;
 217         this.classLoader = loader;
 218         this.component = getComponentEx(component);
 219     }
 220 
 221     private static ServiceName[] serviceClassNames(Class serviceClass, ClassLoader classLoader) {
 222         ArrayList<ServiceName> l = new ArrayList<ServiceName>();
 223         for (Iterator<ServiceName> it = new ServiceNameIterator(serviceClass,classLoader);it.hasNext();) l.add(it.next());
 224         return l.toArray(new ServiceName[l.size()]);
 225     }
 226 
 227     /**
 228      * Returns discovered objects incrementally.
 229      *
 230      * @return An {@code Iterator} that yields provider objects for the given
 231      *         service, in some arbitrary order.  The iterator will throw a
 232      *         {@code ServiceConfigurationError} if a provider-configuration
 233      *         file violates the specified format or if a provider class cannot
 234      *         be found and instantiated.
 235      */
 236     @SuppressWarnings("unchecked")
 237         public Iterator<T> iterator() {
 238         Iterator<T> it = new LazyIterator<T>(serviceClass,classLoader);
 239         return component != null ?
 240                         new CompositeIterator<T>(
 241                                         component.getIterableSPI(serviceClass).iterator(),it) :
 242                         it;
 243     }
 244 
 245     /**
 246      * Returns discovered objects all at once.
 247      *
 248      * @return
 249      *      can be empty but never null.
 250      *
 251      * @throws ServiceConfigurationError
 252      */
 253     public T[] toArray() {
 254         List<T> result = new ArrayList<T>();
 255         for (T t : this) {
 256             result.add(t);
 257         }
 258         return result.toArray((T[])Array.newInstance(serviceClass,result.size()));
 259     }
 260 
 261     private static void fail(Class service, String msg, Throwable cause)
 262         throws ServiceConfigurationError {
 263         ServiceConfigurationError sce
 264             = new ServiceConfigurationError(service.getName() + ": " + msg);
 265         sce.initCause(cause);
 266         throw sce;
 267     }
 268 
 269     private static void fail(Class service, String msg)
 270         throws ServiceConfigurationError {
 271         throw new ServiceConfigurationError(service.getName() + ": " + msg);
 272     }
 273 
 274     private static void fail(Class service, URL u, int line, String msg)
 275         throws ServiceConfigurationError {
 276         fail(service, u + ":" + line + ": " + msg);
 277     }
 278 
 279     /**
 280      * Parse a single line from the given configuration file, adding the name
 281      * on the line to both the names list and the returned set iff the name is
 282      * not already a member of the returned set.
 283      */
 284     private static int parseLine(Class service, URL u, BufferedReader r, int lc,
 285                                  List<String> names, Set<String> returned)
 286         throws IOException, ServiceConfigurationError {
 287         String ln = r.readLine();
 288         if (ln == null) {
 289             return -1;
 290         }
 291         int ci = ln.indexOf('#');
 292         if (ci >= 0) ln = ln.substring(0, ci);
 293         ln = ln.trim();
 294         int n = ln.length();
 295         if (n != 0) {
 296             if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
 297                 fail(service, u, lc, "Illegal configuration-file syntax");
 298             int cp = ln.codePointAt(0);
 299             if (!Character.isJavaIdentifierStart(cp))
 300                 fail(service, u, lc, "Illegal provider-class name: " + ln);
 301             for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
 302                 cp = ln.codePointAt(i);
 303                 if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
 304                     fail(service, u, lc, "Illegal provider-class name: " + ln);
 305             }
 306             if (!returned.contains(ln)) {
 307                 names.add(ln);
 308                 returned.add(ln);
 309             }
 310         }
 311         return lc + 1;
 312     }
 313 
 314     /**
 315      * Parse the content of the given URL as a provider-configuration file.
 316      *
 317      * @param service  The service class for which providers are being sought;
 318      *                 used to construct error detail strings
 319      * @param u        The URL naming the configuration file to be parsed
 320      * @param returned A Set containing the names of provider classes that have already
 321      *                 been returned.  This set will be updated to contain the names
 322      *                 that will be yielded from the returned {@code Iterator}.
 323      * @return A (possibly empty) {@code Iterator} that will yield the
 324      *         provider-class names in the given configuration file that are
 325      *         not yet members of the returned set
 326      * @throws ServiceConfigurationError If an I/O error occurs while reading from the given URL, or
 327      *                                   if a configuration-file format error is detected
 328      */
 329     @SuppressWarnings({"StatementWithEmptyBody"})
 330     private static Iterator<String> parse(Class service, URL u, Set<String> returned)
 331         throws ServiceConfigurationError {
 332         InputStream in = null;
 333         BufferedReader r = null;
 334         ArrayList<String> names = new ArrayList<String>();
 335         try {
 336             in = u.openStream();
 337             r = new BufferedReader(new InputStreamReader(in, "utf-8"));
 338             int lc = 1;
 339             while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0) ;
 340         } catch (IOException x) {
 341             fail(service, ": " + x);
 342         } finally {
 343             try {
 344                 if (r != null) r.close();
 345                 if (in != null) in.close();
 346             } catch (IOException y) {
 347                 fail(service, ": " + y);
 348             }
 349         }
 350         return names.iterator();
 351     }
 352 
 353     private static ComponentEx getComponentEx(Component component) {
 354         if (component instanceof ComponentEx)
 355                 return (ComponentEx) component;
 356 
 357         return component != null ? new ComponentExWrapper(component) : null;
 358     }
 359 
 360     private static class ComponentExWrapper implements ComponentEx {
 361         private final Component component;
 362 
 363         public ComponentExWrapper(Component component) {
 364                 this.component = component;
 365         }
 366 
 367                 public <S> S getSPI(Class<S> spiType) {
 368                         return component.getSPI(spiType);
 369                 }
 370 
 371                 public <S> Iterable<S> getIterableSPI(Class<S> spiType) {
 372                 S item = getSPI(spiType);
 373                 if (item != null) {
 374                         Collection<S> c = Collections.singletonList(item);
 375                         return c;
 376                 }
 377                 return Collections.emptySet();
 378                 }
 379     }
 380 
 381     private static class CompositeIterator<T> implements Iterator<T> {
 382         private final Iterator<Iterator<T>> it;
 383         private Iterator<T> current = null;
 384 
 385         public CompositeIterator(Iterator<T>... iterators) {
 386                 it = Arrays.asList(iterators).iterator();
 387         }
 388 
 389                 public boolean hasNext() {
 390                         if (current != null && current.hasNext())
 391                                 return true;
 392 
 393                         while (it.hasNext()) {
 394                                 current = it.next();
 395                                 if (current.hasNext())
 396                                         return true;
 397 
 398                         }
 399 
 400                         return false;
 401                 }
 402 
 403                 public T next() {
 404                         if (!hasNext())
 405                                 throw new NoSuchElementException();
 406 
 407                         return current.next();
 408                 }
 409 
 410                 public void remove() {
 411             throw new UnsupportedOperationException();
 412                 }
 413     }
 414 
 415     /**
 416      * Private inner class implementing fully-lazy provider lookup
 417      */
 418     private static class ServiceNameIterator implements Iterator<ServiceName> {
 419         Class service;
 420         @Nullable ClassLoader loader;
 421         Enumeration<URL> configs = null;
 422         Iterator<String> pending = null;
 423         Set<String> returned = new TreeSet<String>();
 424         String nextName = null;
 425         URL currentConfig = null;
 426 
 427         private ServiceNameIterator(Class service, ClassLoader loader) {
 428             this.service = service;
 429             this.loader = loader;
 430         }
 431 
 432         public boolean hasNext() throws ServiceConfigurationError {
 433             if (nextName != null) {
 434                 return true;
 435             }
 436             if (configs == null) {
 437                 try {
 438                     String fullName = prefix + service.getName();
 439                     if (loader == null)
 440                         configs = ClassLoader.getSystemResources(fullName);
 441                     else
 442                         configs = loader.getResources(fullName);
 443                 } catch (IOException x) {
 444                     fail(service, ": " + x);
 445                 }
 446             }
 447             while ((pending == null) || !pending.hasNext()) {
 448                 if (!configs.hasMoreElements()) {
 449                     return false;
 450                 }
 451                 currentConfig = configs.nextElement();
 452                 pending = parse(service, currentConfig, returned);
 453             }
 454             nextName = pending.next();
 455             return true;
 456         }
 457 
 458         public ServiceName next() throws ServiceConfigurationError {
 459             if (!hasNext()) {
 460                 throw new NoSuchElementException();
 461             }
 462             String cn = nextName;
 463             nextName = null;
 464             return new ServiceName(cn, currentConfig);
 465         }
 466 
 467         public void remove() {
 468             throw new UnsupportedOperationException();
 469         }
 470     }
 471 
 472     private static class LazyIterator<T> implements Iterator<T> {
 473         Class<T> service;
 474         @Nullable ClassLoader loader;
 475         ServiceName[] names;
 476         int index;
 477 
 478         private LazyIterator(Class<T> service, ClassLoader loader) {
 479             this.service = service;
 480             this.loader = loader;
 481             this.names = null;
 482             index = 0;
 483         }
 484 
 485         @Override
 486         public boolean hasNext() {
 487             if (names == null) {
 488                 ConcurrentHashMap<String, ServiceName[]> nameMap = null;
 489                 synchronized(serviceNameCache){ nameMap = serviceNameCache.get(loader); }
 490                 names = (nameMap != null)? nameMap.get(service.getName()) : null;
 491                 if (names == null) {
 492                     names = serviceClassNames(service, loader);
 493                     if (nameMap == null) nameMap = new ConcurrentHashMap<String, ServiceName[]>();
 494                     nameMap.put(service.getName(), names);
 495                     synchronized(serviceNameCache){ serviceNameCache.put(loader,nameMap); }
 496                 }
 497             }
 498             return (index < names.length);
 499         }
 500 
 501         @Override
 502         public T next() {
 503             if (!hasNext()) throw new NoSuchElementException();
 504             ServiceName sn = names[index++];
 505             String cn = sn.className;
 506             URL currentConfig = sn.config;
 507             try {
 508                 return service.cast(Class.forName(cn, true, loader).newInstance());
 509             } catch (ClassNotFoundException x) {
 510                 fail(service, "Provider " + cn + " is specified in "+currentConfig+" but not found");
 511             } catch (Exception x) {
 512               fail(service, "Provider " + cn + " is specified in "+currentConfig+"but could not be instantiated: " + x, x);
 513             }
 514             return null;    /* This cannot happen */
 515         }
 516 
 517         @Override
 518         public void remove() {
 519             throw new UnsupportedOperationException();
 520         }
 521 
 522     }
 523 }