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