1. /*
  2. * @(#)Service.java 1.5 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.jmx.remote.util;
  8. import java.io.BufferedReader;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.io.InputStreamReader;
  12. import java.net.URL;
  13. import java.util.ArrayList;
  14. import java.util.Enumeration;
  15. import java.util.Iterator;
  16. import java.util.List;
  17. import java.util.NoSuchElementException;
  18. import java.util.Set;
  19. import java.util.TreeSet;
  20. import java.security.AccessController;
  21. import java.security.PrivilegedAction;
  22. /**
  23. * EXTRACTED FROM sun.misc.Service
  24. * A simple service-provider lookup mechanism. A <i>service</i> is a
  25. * well-known set of intjavax.management.remoteerfaces and (usually abstract) classes. A <i>service
  26. * provider</i> is a specific implementation of a service. The classes in a
  27. * provider typically implement the interfaces and subclass the classes defined
  28. * in the service itself. Service providers may be installed in an
  29. * implementation of the Java platform in the form of extensions, that is, jar
  30. * files placed into any of the usual extension directories. Providers may
  31. * also be made available by adding them to the applet or application class
  32. * path or by some other platform-specific means.
  33. *
  34. * <p> In this lookup mechanism a service is represented by an interface or an
  35. * abstract class. (A concrete class may be used, but this is not
  36. * recommended.) A provider of a given service contains one or more concrete
  37. * classes that extend this <i>service class</i> with data and code specific to
  38. * the provider. This <i>provider class</i> will typically not be the entire
  39. * provider itself but rather a proxy that contains enough information to
  40. * decide whether the provider is able to satisfy a particular request together
  41. * with code that can create the actual provider on demand. The details of
  42. * provider classes tend to be highly service-specific; no single class or
  43. * interface could possibly unify them, so no such class has been defined. The
  44. * only requirement enforced here is that provider classes must have a
  45. * zero-argument constructor so that they may be instantiated during lookup.
  46. *
  47. * <p> A service provider identifies itself by placing a provider-configuration
  48. * file in the resource directory <tt>META-INF/services</tt>. The file's name
  49. * should consist of the fully-qualified name of the abstract service class.
  50. * The file should contain a list of fully-qualified concrete provider-class
  51. * names, one per line. Space and tab characters surrounding each name, as
  52. * well as blank lines, are ignored. The comment character is <tt>'#'</tt>
  53. * (<tt>0x23</tt>); on each line all characters following the first comment
  54. * character are ignored. The file must be encoded in UTF-8.
  55. *
  56. * <p> If a particular concrete provider class is named in more than one
  57. * configuration file, or is named in the same configuration file more than
  58. * once, then the duplicates will be ignored. The configuration file naming a
  59. * particular provider need not be in the same jar file or other distribution
  60. * unit as the provider itself. The provider must be accessible from the same
  61. * class loader that was initially queried to locate the configuration file;
  62. * note that this is not necessarily the class loader that found the file.
  63. *
  64. * <p> <b>Example:</b> Suppose we have a service class named
  65. * <tt>java.io.spi.CharCodec</tt>. It has two abstract methods:
  66. *
  67. * <pre>
  68. * public abstract CharEncoder getEncoder(String encodingName);
  69. * public abstract CharDecoder getDecoder(String encodingName);
  70. * </pre>
  71. *
  72. * Each method returns an appropriate object or <tt>null</tt> if it cannot
  73. * translate the given encoding. Typical <tt>CharCodec</tt> providers will
  74. * support more than one encoding.
  75. *
  76. * <p> If <tt>sun.io.StandardCodec</tt> is a provider of the <tt>CharCodec</tt>
  77. * service then its jar file would contain the file
  78. * <tt>META-INF/services/java.io.spi.CharCodec</tt>. This file would contain
  79. * the single line:
  80. *
  81. * <pre>
  82. * sun.io.StandardCodec # Standard codecs for the platform
  83. * </pre>
  84. *
  85. * To locate an encoder for a given encoding name, the internal I/O code would
  86. * do something like this:
  87. *
  88. * <pre>
  89. * CharEncoder getEncoder(String encodingName) {
  90. * Iterator ps = Service.providers(CharCodec.class);
  91. * while (ps.hasNext()) {
  92. * CharCodec cc = (CharCodec)ps.next();
  93. * CharEncoder ce = cc.getEncoder(encodingName);
  94. * if (ce != null)
  95. * return ce;
  96. * }
  97. * return null;
  98. * }
  99. * </pre>
  100. *
  101. * The provider-lookup mechanism always executes in the security context of the
  102. * caller. Trusted system code should typically invoke the methods in this
  103. * class from within a privileged security context.
  104. *
  105. */
  106. public final class Service {
  107. private static final String prefix = "META-INF/services/";
  108. private Service() { }
  109. private static void fail(Class service, String msg, Throwable cause)
  110. throws IllegalArgumentException
  111. {
  112. IllegalArgumentException sce
  113. = new IllegalArgumentException(service.getName() + ": " + msg);
  114. throw (IllegalArgumentException) EnvHelp.initCause(sce, cause);
  115. }
  116. private static void fail(Class service, String msg)
  117. throws IllegalArgumentException
  118. {
  119. throw new IllegalArgumentException(service.getName() + ": " + msg);
  120. }
  121. private static void fail(Class service, URL u, int line, String msg)
  122. throws IllegalArgumentException
  123. {
  124. fail(service, u + ":" + line + ": " + msg);
  125. }
  126. /**
  127. * Parse a single line from the given configuration file, adding the name
  128. * on the line to both the names list and the returned set iff the name is
  129. * not already a member of the returned set.
  130. */
  131. private static int parseLine(Class service, URL u, BufferedReader r, int lc,
  132. List names, Set returned)
  133. throws IOException, IllegalArgumentException
  134. {
  135. String ln = r.readLine();
  136. if (ln == null) {
  137. return -1;
  138. }
  139. int ci = ln.indexOf('#');
  140. if (ci >= 0) ln = ln.substring(0, ci);
  141. ln = ln.trim();
  142. int n = ln.length();
  143. if (n != 0) {
  144. if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
  145. fail(service, u, lc, "Illegal configuration-file syntax");
  146. if (!Character.isJavaIdentifierStart(ln.charAt(0)))
  147. fail(service, u, lc, "Illegal provider-class name: " + ln);
  148. for (int i = 1; i < n; i++) {
  149. char c = ln.charAt(i);
  150. if (!Character.isJavaIdentifierPart(c) && (c != '.'))
  151. fail(service, u, lc, "Illegal provider-class name: " + ln);
  152. }
  153. if (!returned.contains(ln)) {
  154. names.add(ln);
  155. returned.add(ln);
  156. }
  157. }
  158. return lc + 1;
  159. }
  160. /**
  161. * Parse the content of the given URL as a provider-configuration file.
  162. *
  163. * @param service
  164. * The service class for which providers are being sought;
  165. * used to construct error detail strings
  166. *
  167. * @param url
  168. * The URL naming the configuration file to be parsed
  169. *
  170. * @param returned
  171. * A Set containing the names of provider classes that have already
  172. * been returned. This set will be updated to contain the names
  173. * that will be yielded from the returned <tt>Iterator</tt>.
  174. *
  175. * @return A (possibly empty) <tt>Iterator</tt> that will yield the
  176. * provider-class names in the given configuration file that are
  177. * not yet members of the returned set
  178. *
  179. * @throws IllegalArgumentException
  180. * If an I/O error occurs while reading from the given URL, or
  181. * if a configuration-file format error is detected
  182. */
  183. private static Iterator parse(Class service, URL u, Set returned)
  184. throws IllegalArgumentException
  185. {
  186. InputStream in = null;
  187. BufferedReader r = null;
  188. ArrayList names = new ArrayList();
  189. try {
  190. in = u.openStream();
  191. r = new BufferedReader(new InputStreamReader(in, "utf-8"));
  192. int lc = 1;
  193. while ((lc = parseLine(service, u, r, lc, names, returned)) >= 0);
  194. } catch (IOException x) {
  195. fail(service, ": " + x);
  196. } finally {
  197. try {
  198. if (r != null) r.close();
  199. if (in != null) in.close();
  200. } catch (IOException y) {
  201. fail(service, ": " + y);
  202. }
  203. }
  204. return names.iterator();
  205. }
  206. /**
  207. * Private inner class implementing fully-lazy provider lookup
  208. */
  209. private static class LazyIterator implements Iterator {
  210. Class service;
  211. ClassLoader loader;
  212. Enumeration configs = null;
  213. Iterator pending = null;
  214. Set returned = new TreeSet();
  215. String nextName = null;
  216. private LazyIterator(Class service, ClassLoader loader) {
  217. this.service = service;
  218. this.loader = loader;
  219. }
  220. public boolean hasNext() throws IllegalArgumentException {
  221. if (nextName != null) {
  222. return true;
  223. }
  224. if (configs == null) {
  225. try {
  226. String fullName = prefix + service.getName();
  227. if (loader == null)
  228. configs = ClassLoader.getSystemResources(fullName);
  229. else
  230. configs = loader.getResources(fullName);
  231. } catch (IOException x) {
  232. fail(service, ": " + x);
  233. }
  234. }
  235. while ((pending == null) || !pending.hasNext()) {
  236. if (!configs.hasMoreElements()) {
  237. return false;
  238. }
  239. pending = parse(service, (URL)configs.nextElement(), returned);
  240. }
  241. nextName = (String)pending.next();
  242. return true;
  243. }
  244. public Object next() throws IllegalArgumentException {
  245. if (!hasNext()) {
  246. throw new NoSuchElementException();
  247. }
  248. String cn = nextName;
  249. nextName = null;
  250. try {
  251. return Class.forName(cn, true, loader).newInstance();
  252. } catch (ClassNotFoundException x) {
  253. fail(service,
  254. "Provider " + cn + " not found");
  255. } catch (Exception x) {
  256. fail(service,
  257. "Provider " + cn + " could not be instantiated: " + x,
  258. x);
  259. }
  260. return null; /* This cannot happen */
  261. }
  262. public void remove() {
  263. throw new UnsupportedOperationException();
  264. }
  265. }
  266. /**
  267. * Locates and incrementally instantiates the available providers of a
  268. * given service using the given class loader.
  269. *
  270. * <p> This method transforms the name of the given service class into a
  271. * provider-configuration filename as described above and then uses the
  272. * <tt>getResources</tt> method of the given class loader to find all
  273. * available files with that name. These files are then read and parsed to
  274. * produce a list of provider-class names. The iterator that is returned
  275. * uses the given class loader to lookup and then instantiate each element
  276. * of the list.
  277. *
  278. * <p> Because it is possible for extensions to be installed into a running
  279. * Java virtual machine, this method may return different results each time
  280. * it is invoked. <p>
  281. *
  282. * @param service
  283. * The service's abstract service class
  284. *
  285. * @param loader
  286. * The class loader to be used to load provider-configuration files
  287. * and instantiate provider classes, or <tt>null</tt> if the system
  288. * class loader (or, failing that the bootstrap class loader) is to
  289. * be used
  290. *
  291. * @return An <tt>Iterator</tt> that yields provider objects for the given
  292. * service, in some arbitrary order. The iterator will throw a
  293. * <tt>IllegalArgumentException</tt> if a provider-configuration
  294. * file violates the specified format or if a provider class cannot
  295. * be found and instantiated.
  296. *
  297. * @throws IllegalArgumentException
  298. * If a provider-configuration file violates the specified format
  299. * or names a provider class that cannot be found and instantiated
  300. *
  301. */
  302. public static Iterator providers(Class service, ClassLoader loader)
  303. throws IllegalArgumentException
  304. {
  305. return new LazyIterator(service, loader);
  306. }
  307. }