/* * Copyright 2009 Mike Cumings * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.kenai.jbosh; import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Utility library for use in loading services using the Jar Service * Provider Interface (Jar SPI). This can be replaced once the minimum * java rev moves beyond Java 5. */ final class ServiceLib { /** * Logger. */ private static final Logger LOG = Logger.getLogger(ServiceLib.class.getName()); /////////////////////////////////////////////////////////////////////////// // Package-private methods: /** * Prevent construction. */ private ServiceLib() { // Empty } /////////////////////////////////////////////////////////////////////////// // Package-private methods: /** * Probe for and select an implementation of the specified service * type by using the a modified Jar SPI mechanism. Modified in that * the system properties will be checked to see if there is a value * set for the naem of the class to be loaded. If so, that value is * treated as the class name of the first implementation class to be * attempted to be loaded. This provides a (unsupported) mechanism * to insert other implementations. Note that the supported mechanism * is by properly ordering the classpath. * * @return service instance * @throws IllegalStateException is no service implementations could be * instantiated */ static T loadService(Class ofType) { List implClasses = loadServicesImplementations(ofType); for (String implClass : implClasses) { T result = attemptLoad(ofType, implClass); if (result != null) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Selected " + ofType.getSimpleName() + " implementation: " + result.getClass().getName()); } return result; } } throw(new IllegalStateException( "Could not load " + ofType.getName() + " implementation")); } /////////////////////////////////////////////////////////////////////////// // Private methods: /** * Generates a list of implementation class names by using * the Jar SPI technique. The order in which the class names occur * in the service manifest is significant. * * @return list of all declared implementation class names */ private static List loadServicesImplementations( final Class ofClass) { List result = new ArrayList(); // Allow a sysprop to specify the first candidate String override = System.getProperty(ofClass.getName()); if (override != null) { result.add(override); } ClassLoader loader = ServiceLib.class.getClassLoader(); URL url = loader.getResource("META-INF/services/" + ofClass.getName()); InputStream inStream = null; InputStreamReader reader = null; BufferedReader bReader = null; try { inStream = url.openStream(); reader = new InputStreamReader(inStream); bReader = new BufferedReader(reader); String line; while ((line = bReader.readLine()) != null) { if (!line.matches("\\s*(#.*)?")) { // not a comment or blank line result.add(line.trim()); } } } catch (IOException iox) { LOG.log(Level.WARNING, "Could not load services descriptor: " + url.toString(), iox); } finally { finalClose(bReader); finalClose(reader); finalClose(inStream); } return result; } /** * Attempts to load the specified implementation class. * Attempts will fail if - for example - the implementation depends * on a class not found on the classpath. * * @param className implementation class to attempt to load * @return service instance, or {@code null} if the instance could not be * loaded */ private static T attemptLoad( final Class ofClass, final String className) { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("Attempting service load: " + className); } Level level; Exception thrown; try { Class clazz = Class.forName(className); if (!ofClass.isAssignableFrom(clazz)) { if (LOG.isLoggable(Level.WARNING)) { LOG.warning(clazz.getName() + " is not assignable to " + ofClass.getName()); } return null; } return ofClass.cast(clazz.newInstance()); } catch (ClassNotFoundException ex) { level = Level.FINEST; thrown = ex; } catch (InstantiationException ex) { level = Level.WARNING; thrown = ex; } catch (IllegalAccessException ex) { level = Level.WARNING; thrown = ex; } LOG.log(level, "Could not load " + ofClass.getSimpleName() + " instance: " + className, thrown); return null; } /** * Check and close a closeable object, trapping and ignoring any * exception that might result. * * @param closeMe the thing to close */ private static void finalClose(final Closeable closeMe) { if (closeMe != null) { try { closeMe.close(); } catch (IOException iox) { LOG.log(Level.FINEST, "Could not close: " + closeMe, iox); } } } }