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