Shuyi Chen | d7955ce | 2013-05-22 14:51:55 -0700 | [diff] [blame] | 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 | |
| 17 | package com.kenai.jbosh; |
| 18 | |
| 19 | import java.io.BufferedReader; |
| 20 | import java.io.Closeable; |
| 21 | import java.io.IOException; |
| 22 | import java.io.InputStream; |
| 23 | import java.io.InputStreamReader; |
| 24 | import java.net.URL; |
| 25 | import java.util.ArrayList; |
| 26 | import java.util.List; |
| 27 | import java.util.logging.Level; |
| 28 | import 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 | */ |
| 35 | final 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 | } |