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.policy.privateutil;
038:
039: import java.io.BufferedReader;
040: import java.io.IOException;
041: import java.io.InputStream;
042: import java.io.InputStreamReader;
043: import java.lang.reflect.Array;
044: import java.net.URL;
045: import java.util.ArrayList;
046: import java.util.Enumeration;
047: import java.util.Iterator;
048: import java.util.List;
049: import java.util.NoSuchElementException;
050: import java.util.Set;
051: import java.util.TreeSet;
052:
053: /**
054: *
055: * A simple service-provider lookup mechanism. A <i>service</i> is a
056: * well-known set of interfaces and (usually abstract) classes. A <i>service
057: * provider</i> is a specific implementation of a service. The classes in a
058: * provider typically implement the interfaces and subclass the classes defined
059: * in the service itself. Service providers may be installed in an
060: * implementation of the Java platform in the form of extensions, that is, jar
061: * files placed into any of the usual extension directories. Providers may
062: * also be made available by adding them to the applet or application class
063: * path or by some other platform-specific means.
064: * <p/>
065: * <p> In this lookup mechanism a service is represented by an interface or an
066: * abstract class. (A concrete class may be used, but this is not
067: * recommended.) A provider of a given service contains one or more concrete
068: * classes that extend this <i>service class</i> with data and code specific to
069: * the provider. This <i>provider class</i> will typically not be the entire
070: * provider itself but rather a proxy that contains enough information to
071: * decide whether the provider is able to satisfy a particular request together
072: * with code that can create the actual provider on demand. The details of
073: * provider classes tend to be highly service-specific; no single class or
074: * interface could possibly unify them, so no such class has been defined. The
075: * only requirement enforced here is that provider classes must have a
076: * zero-argument constructor so that they may be instantiated during lookup.
077: * <p/>
078: * <p> A service provider identifies itself by placing a provider-configuration
079: * file in the resource directory <tt>META-INF/services</tt>. The file's name
080: * should consist of the fully-qualified name of the abstract service class.
081: * The file should contain a list of fully-qualified concrete provider-class
082: * names, one per line. Space and tab characters surrounding each name, as
083: * well as blank lines, are ignored. The comment character is <tt>'#'</tt>
084: * (<tt>0x23</tt>); on each line all characters following the first comment
085: * character are ignored. The file must be encoded in UTF-8.
086: * <p/>
087: * <p> If a particular concrete provider class is named in more than one
088: * configuration file, or is named in the same configuration file more than
089: * once, then the duplicates will be ignored. The configuration file naming a
090: * particular provider need not be in the same jar file or other distribution
091: * unit as the provider itself. The provider must be accessible from the same
092: * class loader that was initially queried to locate the configuration file;
093: * note that this is not necessarily the class loader that found the file.
094: * <p/>
095: * <p> <b>Example:</b> Suppose we have a service class named
096: * <tt>java.io.spi.CharCodec</tt>. It has two abstract methods:
097: * <p/>
098: * <pre>
099: * 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 <tt>null</tt> if it cannot
104: * translate the given encoding. Typical <tt>CharCodec</tt> providers will
105: * support more than one encoding.
106: * <p/>
107: * <p> If <tt>sun.io.StandardCodec</tt> is a provider of the <tt>CharCodec</tt>
108: * service then its jar file would contain the file
109: * <tt>META-INF/services/java.io.spi.CharCodec</tt>. 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 encoder 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: final class ServiceFinder<T> implements Iterable<T> {
139: private static final PolicyLogger LOGGER = PolicyLogger
140: .getLogger(ServiceFinder.class);
141:
142: private static final String prefix = "META-INF/services/";
143:
144: private final Class<T> serviceClass;
145: private final ClassLoader classLoader;
146:
147: /**
148: * Locates and incrementally instantiates the available providers of a
149: * given service using the given class loader.
150: * <p/>
151: * <p> This method transforms the name of the given service class into a
152: * provider-configuration filename as described above and then uses the
153: * <tt>getResources</tt> method of the given class loader to find all
154: * available files with that name. These files are then read and parsed to
155: * produce a list of provider-class names. The iterator that is returned
156: * uses the given class loader to lookup and then instantiate each element
157: * of the list.
158: * <p/>
159: * <p> Because it is possible for extensions to be installed into a running
160: * Java virtual machine, this method may return different results each time
161: * it is invoked. <p>
162: *
163: * @param service The service's abstract service class
164: * @param loader The class loader to be used to load provider-configuration files
165: * and instantiate provider classes, or <tt>null</tt> if the system
166: * class loader (or, failing that the bootstrap class loader) is to
167: * be used
168: * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
169: * or names a provider class that cannot be found and instantiated
170: * @see #find(Class)
171: */
172: static <T> ServiceFinder<T> find(final Class<T> service,
173: final ClassLoader loader) {
174: if (null == service) {
175: throw LOGGER.logSevereException(new NullPointerException(
176: LocalizationMessages
177: .WSP_0032_SERVICE_CAN_NOT_BE_NULL()));
178: }
179: return new ServiceFinder<T>(service, loader);
180: }
181:
182: /**
183: * Locates and incrementally instantiates the available providers of a
184: * given service using the context class loader. This convenience method
185: * is equivalent to
186: * <p/>
187: * <pre>
188: * ClassLoader cl = Thread.currentThread().getContextClassLoader();
189: * return Service.providers(service, cl);
190: * </pre>
191: *
192: * @param service The service's abstract service class
193: *
194: * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
195: * or names a provider class that cannot be found and instantiated
196: * @see #find(Class, ClassLoader)
197: */
198: public static <T> ServiceFinder<T> find(final Class<T> service) {
199: return find(service, Thread.currentThread()
200: .getContextClassLoader());
201: }
202:
203: private ServiceFinder(Class<T> service, ClassLoader loader) {
204: this .serviceClass = service;
205: this .classLoader = loader;
206: }
207:
208: /**
209: * Returns discovered objects incrementally.
210: *
211: * @return An <tt>Iterator</tt> that yields provider objects for the given
212: * service, in some arbitrary order. The iterator will throw a
213: * <tt>ServiceConfigurationError</tt> if a provider-configuration
214: * file violates the specified format or if a provider class cannot
215: * be found and instantiated.
216: */
217: public Iterator<T> iterator() {
218: return new LazyIterator<T>(serviceClass, classLoader);
219: }
220:
221: /**
222: * Returns discovered objects all at once.
223: *
224: * @return
225: * can be empty but never null.
226: *
227: * @throws ServiceConfigurationError
228: */
229: @SuppressWarnings({"unchecked"})
230: public T[] toArray() {
231: List<T> result = new ArrayList<T>();
232: for (T t : this ) {
233: result.add(t);
234: }
235: return result.toArray((T[]) Array.newInstance(serviceClass,
236: result.size()));
237: }
238:
239: private static void fail(final Class service, final String msg,
240: final Throwable cause) throws ServiceConfigurationError {
241: final ServiceConfigurationError sce = new ServiceConfigurationError(
242: LocalizationMessages.WSP_0025_SPI_FAIL_SERVICE_MSG(
243: service.getName(), msg));
244: if (null != cause) {
245: sce.initCause(cause);
246: }
247:
248: throw LOGGER.logSevereException(sce);
249: }
250:
251: /* private static void fail(Class service, String msg)
252: throws ServiceConfigurationError {
253: throw new ServiceConfigurationError(LocalizationMessages.WSP_0025_SPI_FAIL_SERVICE_MSG(service.getName(), msg));
254: }*/
255:
256: private static void fail(final Class service, final URL u,
257: final int line, final String msg, final Throwable cause)
258: throws ServiceConfigurationError {
259: fail(service, LocalizationMessages
260: .WSP_0024_SPI_FAIL_SERVICE_URL_LINE_MSG(u, line, msg),
261: cause);
262: }
263:
264: /**
265: * Parse a single line from the given configuration file, adding the name
266: * on the line to both the names list and the returned set iff the name is
267: * not already a member of the returned set.
268: */
269: private static int parseLine(final Class service, final URL u,
270: final BufferedReader r, final int lc,
271: final List<String> names, final Set<String> returned)
272: throws IOException, ServiceConfigurationError {
273: String ln = r.readLine();
274: if (ln == null) {
275: return -1;
276: }
277: final int ci = ln.indexOf('#');
278: if (ci >= 0)
279: ln = ln.substring(0, ci);
280: ln = ln.trim();
281: final int n = ln.length();
282: if (n != 0) {
283: if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
284: fail(service, u, lc, LocalizationMessages
285: .WSP_0067_ILLEGAL_CFG_FILE_SYNTAX(), null);
286: int cp = ln.codePointAt(0);
287: if (!Character.isJavaIdentifierStart(cp))
288: fail(service, u, lc, LocalizationMessages
289: .WSP_0066_ILLEGAL_PROVIDER_CLASSNAME(ln), null);
290: for (int i = Character.charCount(cp); i < n; i += Character
291: .charCount(cp)) {
292: cp = ln.codePointAt(i);
293: if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
294: fail(service, u, lc, LocalizationMessages
295: .WSP_0066_ILLEGAL_PROVIDER_CLASSNAME(ln),
296: null);
297: }
298: if (!returned.contains(ln)) {
299: names.add(ln);
300: returned.add(ln);
301: }
302: }
303: return lc + 1;
304: }
305:
306: /**
307: * Parse the content of the given URL as a provider-configuration file.
308: *
309: * @param service The service class for which providers are being sought;
310: * used to construct error detail strings
311: * @param u The URL naming the configuration file to be parsed
312: * @param returned A Set containing the names of provider classes that have already
313: * been returned. This set will be updated to contain the names
314: * that will be yielded from the returned <tt>Iterator</tt>.
315: * @return A (possibly empty) <tt>Iterator</tt> that will yield the
316: * provider-class names in the given configuration file that are
317: * not yet members of the returned set
318: * @throws ServiceConfigurationError If an I/O error occurs while reading from the given URL, or
319: * if a configuration-file format error is detected
320: */
321: @SuppressWarnings({"StatementWithEmptyBody"})
322: private static Iterator<String> parse(Class service, URL u,
323: Set<String> returned) throws ServiceConfigurationError {
324: InputStream in = null;
325: BufferedReader r = null;
326: ArrayList<String> names = new ArrayList<String>();
327: try {
328: in = u.openStream();
329: r = new BufferedReader(new InputStreamReader(in, "utf-8"));
330: int lc = 1;
331: while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0)
332: ;
333: } catch (IOException x) {
334: fail(service, ": " + x, x);
335: } finally {
336: try {
337: if (r != null)
338: r.close();
339: if (in != null)
340: in.close();
341: } catch (IOException y) {
342: fail(service, ": " + y, y);
343: }
344: }
345: return names.iterator();
346: }
347:
348: /**
349: * Private inner class implementing fully-lazy provider lookup
350: */
351: private static class LazyIterator<T> implements Iterator<T> {
352: Class<T> service;
353: ClassLoader loader;
354: Enumeration<URL> configs = null;
355: Iterator<String> pending = null;
356: Set<String> returned = new TreeSet<String>();
357: String nextName = null;
358:
359: private LazyIterator(Class<T> service, ClassLoader loader) {
360: this .service = service;
361: this .loader = loader;
362: }
363:
364: public boolean hasNext() throws ServiceConfigurationError {
365: if (nextName != null) {
366: return true;
367: }
368: if (configs == null) {
369: try {
370: final String fullName = prefix + service.getName();
371: if (loader == null)
372: configs = ClassLoader
373: .getSystemResources(fullName);
374: else
375: configs = loader.getResources(fullName);
376: } catch (IOException x) {
377: fail(service, ": " + x, x);
378: }
379: }
380: while ((pending == null) || !pending.hasNext()) {
381: if (!configs.hasMoreElements()) {
382: return false;
383: }
384: pending = parse(service, configs.nextElement(),
385: returned);
386: }
387: nextName = pending.next();
388: return true;
389: }
390:
391: public T next() throws ServiceConfigurationError {
392: if (!hasNext()) {
393: throw new NoSuchElementException();
394: }
395: final String cn = nextName;
396: nextName = null;
397: try {
398: return service.cast(Class.forName(cn, true, loader)
399: .newInstance());
400: } catch (ClassNotFoundException x) {
401: fail(service, LocalizationMessages
402: .WSP_0027_SERVICE_PROVIDER_NOT_FOUND(cn), x);
403: } catch (Exception x) {
404: fail(
405: service,
406: LocalizationMessages
407: .WSP_0028_SERVICE_PROVIDER_COULD_NOT_BE_INSTANTIATED(cn),
408: x);
409: }
410: return null; /* This cannot happen */
411: }
412:
413: public void remove() {
414: throw new UnsupportedOperationException();
415: }
416: }
417: }
|