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 }