diff options
Diffstat (limited to 'src/com/kenai/jbosh/ServiceLib.java')
-rw-r--r-- | src/com/kenai/jbosh/ServiceLib.java | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/src/com/kenai/jbosh/ServiceLib.java b/src/com/kenai/jbosh/ServiceLib.java new file mode 100644 index 0000000..07d0556 --- /dev/null +++ b/src/com/kenai/jbosh/ServiceLib.java @@ -0,0 +1,195 @@ +/* + * 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> T loadService(Class<T> ofType) { + List<String> 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<String> loadServicesImplementations( + final Class ofClass) { + List<String> result = new ArrayList<String>(); + + // 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> T attemptLoad( + final Class<T> 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); + } + } + } + +} |