
org.ops4j.spi.SafeServiceLoader Maven / Gradle / Ivy
/*
* 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.spi;
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.
*
* @param classLoader
*/
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.
*
* 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
* @throws NoServiceProviderException if a resource cannot be read or if a loaded class cannot
* be instantiated
*/
public List load( String serviceType )
{
List services = new ArrayList();
String resourceName = "/META-INF/services/" + serviceType;
try
{
Enumeration resources = classLoader.getResources( resourceName );
while( resources.hasMoreElements() )
{
URL url = resources.nextElement();
List classNames = parse( url );
for( String className : classNames )
{
Class klass = loadClassIfVisible( className, classLoader );
if( klass != null )
{
T service = klass.newInstance();
services.add( service );
}
}
}
}
catch ( IOException exc )
{
throw new NoServiceProviderException( exc );
}
catch ( InstantiationException exc )
{
throw new NoServiceProviderException( exc );
}
catch ( IllegalAccessException exc )
{
throw new NoServiceProviderException( 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 Class loadClassIfVisible( String className, ClassLoader classLoader )
{
try
{
Class klass = (Class) classLoader.loadClass( className );
return klass;
}
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 parse( URL url )
{
InputStream is = null;
BufferedReader reader = null;
List names = new ArrayList();
try
{
is = url.openStream();
reader = new BufferedReader( new InputStreamReader( is, "UTF-8" ) );
String line = null;
while( ( line = reader.readLine() ) != null )
{
parseLine( names, line );
}
}
catch ( IOException exc )
{
throw new NoServiceProviderException( exc );
}
finally
{
closeSilently( reader );
}
return names;
}
/**
* Closes the given reader, silently ignoring any exception.
*
* @param reader
*/
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 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 );
}
}
}