001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2009-2010 Pentaho and others 008// All Rights Reserved. 009*/ 010package mondrian.util; 011 012import org.apache.commons.logging.Log; 013import org.apache.commons.logging.LogFactory; 014 015import java.io.*; 016import java.net.URL; 017import java.util.*; 018 019/** 020 * Utility functions to discover Java services. 021 * 022 * <p>Java services are described in 023 * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider"> 024 * the JAR File Specification</a>. 025 * 026 * <p>Based on the suggested file format, this class reads the service 027 * entries in a JAR file and discovers implementors of an interface. 028 * 029 * @author Marc Batchelor 030 */ 031public class ServiceDiscovery<T> { 032 033 private static final Log logger = LogFactory.getLog(ServiceDiscovery.class); 034 035 private final Class<T> theInterface; 036 037 /** 038 * Creates a ServiceDiscovery. 039 * 040 * @param theInterface Interface for service 041 * 042 * @return ServiceDiscovery for finding instances of the given interface 043 */ 044 public static <T> ServiceDiscovery<T> forClass(Class<T> theInterface) { 045 return new ServiceDiscovery<T>(theInterface); 046 } 047 048 /** 049 * Creates a ServiceDiscovery. 050 * 051 * @param theInterface Interface for service 052 */ 053 private ServiceDiscovery(Class<T> theInterface) { 054 assert theInterface != null; 055 this.theInterface = theInterface; 056 } 057 058 /** 059 * Returns a list of classes that implement the service. 060 * 061 * @return List of classes that implement the service 062 */ 063 public List<Class<T>> getImplementor() { 064 // Use linked hash set to eliminate duplicates but still return results 065 // in the order they were added. 066 Set<Class<T>> uniqueClasses = new LinkedHashSet<Class<T>>(); 067 068 ClassLoader cLoader = Thread.currentThread().getContextClassLoader(); 069 if (cLoader == null) { 070 cLoader = this.getClass().getClassLoader(); 071 } 072 try { 073 // Enumerate the files because I may have more than one .jar file 074 // that contains an implementation for the interface, and therefore, 075 // more than one list of entries. 076 String lookupName = "META-INF/services/" + theInterface.getName(); 077 Enumeration<URL> urlEnum = cLoader.getResources(lookupName); 078 while (urlEnum.hasMoreElements()) { 079 URL resourceURL = urlEnum.nextElement(); 080 InputStream is = null; 081 try { 082 is = resourceURL.openStream(); 083 BufferedReader reader = 084 new BufferedReader(new InputStreamReader(is)); 085 086 // read each class and parse it 087 String clazz; 088 while ((clazz = reader.readLine()) != null) { 089 parseImplementor(clazz, cLoader, uniqueClasses); 090 } 091 } catch (IOException e) { 092 logger.warn( 093 "Error while finding service file " + resourceURL 094 + " for " + theInterface, 095 e); 096 } finally { 097 if (is != null) { 098 is.close(); 099 } 100 } 101 } 102 } catch (IOException e) { 103 logger.warn( 104 "Error while finding service files for " + theInterface, 105 e); 106 } 107 List<Class<T>> rtn = new ArrayList<Class<T>>(); 108 rtn.addAll(uniqueClasses); 109 return rtn; 110 } 111 112 /** 113 * Parses a list of classes that implement a service. 114 * 115 * @param clazz Class name (or list of class names) 116 * @param cLoader Class loader 117 * @param uniqueClasses Set of classes (output) 118 */ 119 protected void parseImplementor( 120 String clazz, 121 ClassLoader cLoader, 122 Set<Class<T>> uniqueClasses) 123 { 124 // Split should leave me with a class name in the first string 125 // which will nicely ignore comments in the line. I checked 126 // and found that it also doesn't choke if: 127 // a- There are no spaces on the line - you end up with one entry 128 // b- A line begins with a whitespace character (the trim() fixes that) 129 // c- Multiples of the same interface are filtered out 130 assert clazz != null; 131 132 String[] classList = clazz.trim().split("#"); 133 String theClass = classList[0].trim(); // maybe overkill, maybe not. :-D 134 if (theClass.length() == 0) { 135 // Ignore lines that are empty after removing comments & trailing 136 // spaces. 137 return; 138 } 139 try { 140 // I want to look up the class but not cause the static 141 // initializer to execute. 142 Class interfaceImplementor = 143 Class.forName(theClass, false, cLoader); 144 if (theInterface.isAssignableFrom(interfaceImplementor)) { 145 //noinspection unchecked 146 uniqueClasses.add((Class<T>) interfaceImplementor); 147 } else { 148 logger.error( 149 "Class " + interfaceImplementor 150 + " cannot be assigned to interface " 151 + theInterface); 152 } 153 } catch (ClassNotFoundException ignored) { 154 ignored.printStackTrace(); 155 } catch (LinkageError ignored) { 156 // including ExceptionInInitializerError 157 ignored.printStackTrace(); 158 } 159 } 160} 161 162// End ServiceDiscovery.java