001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common Development
008: * and Distribution License("CDDL") (collectively, the "License"). You
009: * may not use this file except in compliance with the License. You can obtain
010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
012: * language governing permissions and limitations under the License.
013: *
014: * When distributing the software, include this License Header Notice in each
015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
016: * Sun designates this particular file as subject to the "Classpath" exception
017: * as provided by Sun in the GPL Version 2 section of the License file that
018: * accompanied this code. If applicable, add the following below the License
019: * Header, with the fields enclosed by brackets [] replaced by your own
020: * identifying information: "Portions Copyrighted [year]
021: * [name of copyright owner]"
022: *
023: * Contributor(s):
024: *
025: * If you wish your version of this file to be governed by only the CDDL or
026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
027: * elects to include this software in this distribution under the [CDDL or GPL
028: * Version 2] license." If you don't indicate a single choice of license, a
029: * recipient has the option to distribute your version of this file under
030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
031: * its licensees as provided above. However, if you add GPL Version 2 code
032: * and therefore, elected the GPL Version 2 license, then the option applies
033: * only if the new code is made subject to such option by the copyright
034: * holder.
035: */
036:
037: package com.sun.xml.ws.util;
038:
039: import com.sun.istack.NotNull;
040: import com.sun.istack.Nullable;
041:
042: import java.io.BufferedReader;
043: import java.io.IOException;
044: import java.io.InputStream;
045: import java.io.InputStreamReader;
046: import java.lang.reflect.Array;
047: import java.net.URL;
048: import java.util.ArrayList;
049: import java.util.Enumeration;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.NoSuchElementException;
053: import java.util.Set;
054: import java.util.TreeSet;
055:
056: /**
057: * A simple service-provider lookup mechanism. A <i>service</i> is a
058: * well-known set of interfaces and (usually abstract) classes. A <i>service
059: * provider</i> is a specific implementation of a service. The classes in a
060: * provider typically implement the interfaces and subclass the classes defined
061: * in the service itself. Service providers may be installed in an
062: * implementation of the Java platform in the form of extensions, that is, jar
063: * files placed into any of the usual extension directories. Providers may
064: * also be made available by adding them to the applet or application class
065: * path or by some other platform-specific means.
066: * <p/>
067: * <p> In this lookup mechanism a service is represented by an interface or an
068: * abstract class. (A concrete class may be used, but this is not
069: * recommended.) A provider of a given service contains one or more concrete
070: * classes that extend this <i>service class</i> with data and code specific to
071: * the provider. This <i>provider class</i> will typically not be the entire
072: * provider itself but rather a proxy that contains enough information to
073: * decide whether the provider is able to satisfy a particular request together
074: * with code that can create the actual provider on demand. The details of
075: * provider classes tend to be highly service-specific; no single class or
076: * interface could possibly unify them, so no such class has been defined. The
077: * only requirement enforced here is that provider classes must have a
078: * zero-argument constructor so that they may be instantiated during lookup.
079: * <p/>
080: * <p> A service provider identifies itself by placing a provider-configuration
081: * file in the resource directory <tt>META-INF/services</tt>. The file's name
082: * should consist of the fully-qualified name of the abstract service class.
083: * The file should contain a list of fully-qualified concrete provider-class
084: * names, one per line. Space and tab characters surrounding each name, as
085: * well as blank lines, are ignored. The comment character is <tt>'#'</tt>
086: * (<tt>0x23</tt>); on each line all characters following the first comment
087: * character are ignored. The file must be encoded in UTF-8.
088: * <p/>
089: * <p> If a particular concrete provider class is named in more than one
090: * configuration file, or is named in the same configuration file more than
091: * once, then the duplicates will be ignored. The configuration file naming a
092: * particular provider need not be in the same jar file or other distribution
093: * unit as the provider itself. The provider must be accessible from the same
094: * class loader that was initially queried to locate the configuration file;
095: * note that this is not necessarily the class loader that found the file.
096: * <p/>
097: * <p> <b>Example:</b> Suppose we have a service class named
098: * <tt>java.io.spi.CharCodec</tt>. It has two abstract methods:
099: * <p/>
100: * <pre>
101: * public abstract CharEncoder getEncoder(String encodingName);
102: * public abstract CharDecoder getDecoder(String encodingName);
103: * </pre>
104: * <p/>
105: * Each method returns an appropriate object or <tt>null</tt> if it cannot
106: * translate the given encoding. Typical <tt>CharCodec</tt> providers will
107: * support more than one encoding.
108: * <p/>
109: * <p> If <tt>sun.io.StandardCodec</tt> is a provider of the <tt>CharCodec</tt>
110: * service then its jar file would contain the file
111: * <tt>META-INF/services/java.io.spi.CharCodec</tt>. This file would contain
112: * the single line:
113: * <p/>
114: * <pre>
115: * sun.io.StandardCodec # Standard codecs for the platform
116: * </pre>
117: * <p/>
118: * To locate an codec for a given encoding name, the internal I/O code would
119: * do something like this:
120: * <p/>
121: * <pre>
122: * CharEncoder getEncoder(String encodingName) {
123: * for( CharCodec cc : ServiceFinder.find(CharCodec.class) ) {
124: * CharEncoder ce = cc.getEncoder(encodingName);
125: * if (ce != null)
126: * return ce;
127: * }
128: * return null;
129: * }
130: * </pre>
131: * <p/>
132: * The provider-lookup mechanism always executes in the security context of the
133: * caller. Trusted system code should typically invoke the methods in this
134: * class from within a privileged security context.
135: *
136: * @author Mark Reinhold
137: * @version 1.11, 03/12/19
138: * @since 1.3
139: */
140: public final class ServiceFinder<T> implements Iterable<T> {
141:
142: private static final String prefix = "META-INF/services/";
143:
144: private final Class<T> serviceClass;
145: private final @Nullable
146: ClassLoader classLoader;
147:
148: /**
149: * Locates and incrementally instantiates the available providers of a
150: * given service using the given class loader.
151: * <p/>
152: * <p> This method transforms the name of the given service class into a
153: * provider-configuration filename as described above and then uses the
154: * <tt>getResources</tt> method of the given class loader to find all
155: * available files with that name. These files are then read and parsed to
156: * produce a list of provider-class names. The iterator that is returned
157: * uses the given class loader to lookup and then instantiate each element
158: * of the list.
159: * <p/>
160: * <p> Because it is possible for extensions to be installed into a running
161: * Java virtual machine, this method may return different results each time
162: * it is invoked. <p>
163: *
164: * @param service The service's abstract service class
165: * @param loader The class loader to be used to load provider-configuration files
166: * and instantiate provider classes, or <tt>null</tt> if the system
167: * class loader (or, failing that the bootstrap class loader) is to
168: * be used
169: * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
170: * or names a provider class that cannot be found and instantiated
171: * @see #find(Class)
172: */
173: public static <T> ServiceFinder<T> find(@NotNull
174: Class<T> service, @Nullable
175: ClassLoader loader) {
176: return new ServiceFinder<T>(service, loader);
177: }
178:
179: /**
180: * Locates and incrementally instantiates the available providers of a
181: * given service using the context class loader. This convenience method
182: * is equivalent to
183: * <p/>
184: * <pre>
185: * ClassLoader cl = Thread.currentThread().getContextClassLoader();
186: * return Service.providers(service, cl);
187: * </pre>
188: *
189: * @param service The service's abstract service class
190: *
191: * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
192: * or names a provider class that cannot be found and instantiated
193: * @see #find(Class, ClassLoader)
194: */
195: public static <T> ServiceFinder<T> find(Class<T> service) {
196: return find(service, Thread.currentThread()
197: .getContextClassLoader());
198: }
199:
200: private ServiceFinder(Class<T> service, ClassLoader loader) {
201: this .serviceClass = service;
202: this .classLoader = loader;
203: }
204:
205: /**
206: * Returns discovered objects incrementally.
207: *
208: * @return An <tt>Iterator</tt> that yields provider objects for the given
209: * service, in some arbitrary order. The iterator will throw a
210: * <tt>ServiceConfigurationError</tt> if a provider-configuration
211: * file violates the specified format or if a provider class cannot
212: * be found and instantiated.
213: */
214: public Iterator<T> iterator() {
215: return new LazyIterator<T>(serviceClass, classLoader);
216: }
217:
218: /**
219: * Returns discovered objects all at once.
220: *
221: * @return
222: * can be empty but never null.
223: *
224: * @throws ServiceConfigurationError
225: */
226: public T[] toArray() {
227: List<T> result = new ArrayList<T>();
228: for (T t : this ) {
229: result.add(t);
230: }
231: return result.toArray((T[]) Array.newInstance(serviceClass,
232: result.size()));
233: }
234:
235: private static void fail(Class service, String msg, Throwable cause)
236: throws ServiceConfigurationError {
237: ServiceConfigurationError sce = new ServiceConfigurationError(
238: service.getName() + ": " + msg);
239: sce.initCause(cause);
240: throw sce;
241: }
242:
243: private static void fail(Class service, String msg)
244: throws ServiceConfigurationError {
245: throw new ServiceConfigurationError(service.getName() + ": "
246: + msg);
247: }
248:
249: private static void fail(Class service, URL u, int line, String msg)
250: throws ServiceConfigurationError {
251: fail(service, u + ":" + line + ": " + msg);
252: }
253:
254: /**
255: * Parse a single line from the given configuration file, adding the name
256: * on the line to both the names list and the returned set iff the name is
257: * not already a member of the returned set.
258: */
259: private static int parseLine(Class service, URL u,
260: BufferedReader r, int lc, List<String> names,
261: Set<String> returned) throws IOException,
262: ServiceConfigurationError {
263: String ln = r.readLine();
264: if (ln == null) {
265: return -1;
266: }
267: int ci = ln.indexOf('#');
268: if (ci >= 0)
269: ln = ln.substring(0, ci);
270: ln = ln.trim();
271: int n = ln.length();
272: if (n != 0) {
273: if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
274: fail(service, u, lc,
275: "Illegal configuration-file syntax");
276: int cp = ln.codePointAt(0);
277: if (!Character.isJavaIdentifierStart(cp))
278: fail(service, u, lc, "Illegal provider-class name: "
279: + ln);
280: for (int i = Character.charCount(cp); i < n; i += Character
281: .charCount(cp)) {
282: cp = ln.codePointAt(i);
283: if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
284: fail(service, u, lc,
285: "Illegal provider-class name: " + ln);
286: }
287: if (!returned.contains(ln)) {
288: names.add(ln);
289: returned.add(ln);
290: }
291: }
292: return lc + 1;
293: }
294:
295: /**
296: * Parse the content of the given URL as a provider-configuration file.
297: *
298: * @param service The service class for which providers are being sought;
299: * used to construct error detail strings
300: * @param u The URL naming the configuration file to be parsed
301: * @param returned A Set containing the names of provider classes that have already
302: * been returned. This set will be updated to contain the names
303: * that will be yielded from the returned <tt>Iterator</tt>.
304: * @return A (possibly empty) <tt>Iterator</tt> that will yield the
305: * provider-class names in the given configuration file that are
306: * not yet members of the returned set
307: * @throws ServiceConfigurationError If an I/O error occurs while reading from the given URL, or
308: * if a configuration-file format error is detected
309: */
310: @SuppressWarnings({"StatementWithEmptyBody"})
311: private static Iterator<String> parse(Class service, URL u,
312: Set<String> returned) throws ServiceConfigurationError {
313: InputStream in = null;
314: BufferedReader r = null;
315: ArrayList<String> names = new ArrayList<String>();
316: try {
317: in = u.openStream();
318: r = new BufferedReader(new InputStreamReader(in, "utf-8"));
319: int lc = 1;
320: while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0)
321: ;
322: } catch (IOException x) {
323: fail(service, ": " + x);
324: } finally {
325: try {
326: if (r != null)
327: r.close();
328: if (in != null)
329: in.close();
330: } catch (IOException y) {
331: fail(service, ": " + y);
332: }
333: }
334: return names.iterator();
335: }
336:
337: /**
338: * Private inner class implementing fully-lazy provider lookup
339: */
340: private static class LazyIterator<T> implements Iterator<T> {
341: Class<T> service;
342: @Nullable
343: ClassLoader loader;
344: Enumeration<URL> configs = null;
345: Iterator<String> pending = null;
346: Set<String> returned = new TreeSet<String>();
347: String nextName = null;
348: URL currentConfig = null;
349:
350: private LazyIterator(Class<T> service, ClassLoader loader) {
351: this .service = service;
352: this .loader = loader;
353: }
354:
355: public boolean hasNext() throws ServiceConfigurationError {
356: if (nextName != null) {
357: return true;
358: }
359: if (configs == null) {
360: try {
361: String fullName = prefix + service.getName();
362: if (loader == null)
363: configs = ClassLoader
364: .getSystemResources(fullName);
365: else
366: configs = loader.getResources(fullName);
367: } catch (IOException x) {
368: fail(service, ": " + x);
369: }
370: }
371: while ((pending == null) || !pending.hasNext()) {
372: if (!configs.hasMoreElements()) {
373: return false;
374: }
375: currentConfig = configs.nextElement();
376: pending = parse(service, currentConfig, returned);
377: }
378: nextName = pending.next();
379: return true;
380: }
381:
382: public T next() throws ServiceConfigurationError {
383: if (!hasNext()) {
384: throw new NoSuchElementException();
385: }
386: String cn = nextName;
387: nextName = null;
388: try {
389: return service.cast(Class.forName(cn, true, loader)
390: .newInstance());
391: } catch (ClassNotFoundException x) {
392: fail(service, "Provider " + cn + " is specified in "
393: + currentConfig + " but not found");
394: } catch (Exception x) {
395: fail(service, "Provider " + cn + " is specified in "
396: + currentConfig
397: + "but could not be instantiated: " + x, x);
398: }
399: return null; /* This cannot happen */
400: }
401:
402: public void remove() {
403: throw new UnsupportedOperationException();
404: }
405: }
406: }
|