/* * Copyright 2012 Harald Wellmann. * * 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 org.ops4j.pax.cdi.openwebbeans.impl; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * Loads service provider instances from {@code META-INF/services} using a given class loader, * avoiding the system class loader used by {@code java.util.ServiceLoader}. * * @author Harald Wellmann */ public class SafeServiceLoader { private ClassLoader classLoader; /** * Constructs a service loader using the given class loader. */ public SafeServiceLoader( ClassLoader classLoader ) { this.classLoader = classLoader; } /** * Returns a list of service instances for the given a service type, finding all * META-INF/services resources for the given type and loading and instantiating all classes * listed in these resources. * <p> * A class that cannot be loaded by the class loader passed to the constructor of this class is * silently ignored. * * @param serviceType fully qualified service class name * @return list of services matching the given service type */ public <T> List<T> load( String serviceType ) { List<T> services = new ArrayList<T>(); String resourceName = "/META-INF/services/" + serviceType; try { Enumeration<URL> resources = classLoader.getResources( resourceName ); while( resources.hasMoreElements() ) { URL url = resources.nextElement(); List<String> classNames = parse( url ); for( String className : classNames ) { Class<T> klass = loadClassIfVisible( className, classLoader ); if( klass != null ) { T service = klass.newInstance(); services.add( service ); } } } } catch ( Exception exc ) { throw new IllegalStateException( exc ); } return services; } /** * Loads a class with the given name from the given class loader. * * @param className fully qualified class name * @param classLoader class loader * @return class with given name, or null */ @SuppressWarnings( "unchecked" ) private <T> Class<T> loadClassIfVisible( String className, ClassLoader classLoader ) { try { return (Class<T>) classLoader.loadClass( className ); } catch ( ClassNotFoundException e ) { return null; } } /** * Parses a META-INF/services resource and returns the list of service provider class names * defined in that resource. * * @param url a URL of a META-INF/services resource * @return list of service class names (not null, but possibly empty) */ private List<String> parse( URL url ) throws IOException { InputStream is; BufferedReader reader = null; List<String> names = new ArrayList<String>(); try { is = url.openStream(); reader = new BufferedReader( new InputStreamReader( is, "UTF-8" ) ); String line; while( ( line = reader.readLine() ) != null ) { parseLine( names, line ); } } finally { closeSilently( reader ); } return names; } /** * Closes the given reader, silently ignoring any exception. */ private void closeSilently( BufferedReader reader ) { try { if( reader != null ) { reader.close(); } } catch ( IOException exc ) { // ignore } } /** * Parses a single line of a META-INF/services resources. If the line contains a class name, the * name is added to the given list. * * @param names list of class names * @param line line to be parsed */ private void parseLine( List<String> names, String line ) { int commentPos = line.indexOf( '#' ); if( commentPos >= 0 ) { line = line.substring( 0, commentPos ); } line = line.trim(); if( !line.isEmpty() && !names.contains( line ) ) { names.add( line ); } } }