1/*
2 * Copyright 2009 Mike Cumings
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.kenai.jbosh;
18
19import java.io.BufferedReader;
20import java.io.Closeable;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.InputStreamReader;
24import java.net.URL;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.logging.Level;
28import java.util.logging.Logger;
29
30/**
31 * Utility library for use in loading services using the Jar Service
32 * Provider Interface (Jar SPI).  This can be replaced once the minimum
33 * java rev moves beyond Java 5.
34 */
35final class ServiceLib {
36
37    /**
38     * Logger.
39     */
40    private static final Logger LOG =
41            Logger.getLogger(ServiceLib.class.getName());
42
43    ///////////////////////////////////////////////////////////////////////////
44    // Package-private methods:
45
46    /**
47     * Prevent construction.
48     */
49    private ServiceLib() {
50        // Empty
51    }
52
53    ///////////////////////////////////////////////////////////////////////////
54    // Package-private methods:
55
56    /**
57     * Probe for and select an implementation of the specified service
58     * type by using the a modified Jar SPI mechanism.  Modified in that
59     * the system properties will be checked to see if there is a value
60     * set for the naem of the class to be loaded.  If so, that value is
61     * treated as the class name of the first implementation class to be
62     * attempted to be loaded.  This provides a (unsupported) mechanism
63     * to insert other implementations.  Note that the supported mechanism
64     * is by properly ordering the classpath.
65     *
66     * @return service instance
67     * @throws IllegalStateException is no service implementations could be
68     *  instantiated
69     */
70    static <T> T loadService(Class<T> ofType) {
71        List<String> implClasses = loadServicesImplementations(ofType);
72        for (String implClass : implClasses) {
73            T result = attemptLoad(ofType, implClass);
74            if (result != null) {
75                if (LOG.isLoggable(Level.FINEST)) {
76                    LOG.finest("Selected " + ofType.getSimpleName()
77                            + " implementation: "
78                            + result.getClass().getName());
79                }
80                return result;
81            }
82        }
83        throw(new IllegalStateException(
84                "Could not load " + ofType.getName() + " implementation"));
85    }
86
87    ///////////////////////////////////////////////////////////////////////////
88    // Private methods:
89
90    /**
91     * Generates a list of implementation class names by using
92     * the Jar SPI technique.  The order in which the class names occur
93     * in the service manifest is significant.
94     *
95     * @return list of all declared implementation class names
96     */
97    private static List<String> loadServicesImplementations(
98            final Class ofClass) {
99        List<String> result = new ArrayList<String>();
100
101        // Allow a sysprop to specify the first candidate
102        String override = System.getProperty(ofClass.getName());
103        if (override != null) {
104            result.add(override);
105        }
106
107        ClassLoader loader = ServiceLib.class.getClassLoader();
108        URL url = loader.getResource("META-INF/services/" + ofClass.getName());
109        InputStream inStream = null;
110        InputStreamReader reader = null;
111        BufferedReader bReader = null;
112        try {
113            inStream = url.openStream();
114            reader = new InputStreamReader(inStream);
115            bReader = new BufferedReader(reader);
116            String line;
117            while ((line = bReader.readLine()) != null) {
118                if (!line.matches("\\s*(#.*)?")) {
119                    // not a comment or blank line
120                    result.add(line.trim());
121                }
122            }
123        } catch (IOException iox) {
124            LOG.log(Level.WARNING,
125                    "Could not load services descriptor: " + url.toString(),
126                    iox);
127        } finally {
128            finalClose(bReader);
129            finalClose(reader);
130            finalClose(inStream);
131        }
132        return result;
133    }
134
135    /**
136     * Attempts to load the specified implementation class.
137     * Attempts will fail if - for example - the implementation depends
138     * on a class not found on the classpath.
139     *
140     * @param className implementation class to attempt to load
141     * @return service instance, or {@code null} if the instance could not be
142     *  loaded
143     */
144    private static <T> T attemptLoad(
145            final Class<T> ofClass,
146            final String className) {
147        if (LOG.isLoggable(Level.FINEST)) {
148            LOG.finest("Attempting service load: " + className);
149        }
150        Level level;
151        Exception thrown;
152        try {
153            Class clazz = Class.forName(className);
154            if (!ofClass.isAssignableFrom(clazz)) {
155                if (LOG.isLoggable(Level.WARNING)) {
156                    LOG.warning(clazz.getName() + " is not assignable to "
157                            + ofClass.getName());
158                }
159                return null;
160            }
161            return ofClass.cast(clazz.newInstance());
162        } catch (ClassNotFoundException ex) {
163            level = Level.FINEST;
164            thrown = ex;
165        } catch (InstantiationException ex) {
166            level = Level.WARNING;
167            thrown = ex;
168        } catch (IllegalAccessException ex) {
169            level = Level.WARNING;
170            thrown = ex;
171        }
172        LOG.log(level,
173                "Could not load " + ofClass.getSimpleName()
174                + " instance: " + className,
175                thrown);
176        return null;
177    }
178
179    /**
180     * Check and close a closeable object, trapping and ignoring any
181     * exception that might result.
182     *
183     * @param closeMe the thing to close
184     */
185    private static void finalClose(final Closeable closeMe) {
186        if (closeMe != null) {
187            try {
188                closeMe.close();
189            } catch (IOException iox) {
190                LOG.log(Level.FINEST, "Could not close: " + closeMe, iox);
191            }
192        }
193    }
194
195}
196