package liquibase.servicelocator;
import liquibase.exception.ServiceNotFoundException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.ResourceAccessor;
import org.osgi.framework.Bundle;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class ServiceLocator {
private static ServiceLocator instance;
public static void initialize(Bundle bundle) {
instance = new ServiceLocator(bundle);
}
public static void clean() {
instance = null;
}
private ResourceAccessor resourceAccessor;
private Map<Class, List<Class>> classesBySuperclass;
private List<String> packagesToScan;
private Logger logger = Logger.getLogger(getClass().getName());
private PackageScanClassResolver classResolver;
private ServiceLocator(final Bundle bundle) {
classResolver = defaultClassLoader(bundle);
setResourceAccessor(new ClassLoaderResourceAccessor(getClass().getClassLoader()));
}
public static ServiceLocator getInstance() {
return instance;
}
private PackageScanClassResolver defaultClassLoader(final Bundle bundle) {
return bundle == null ? new DefaultPackageScanClassResolver() : new BundlePackageScanClassResolver(bundle);
}
public void setResourceAccessor(ResourceAccessor resourceAccessor) {
this.resourceAccessor = resourceAccessor;
this.classesBySuperclass = new HashMap<Class, List<Class>>();
this.classResolver.setClassLoaders(new HashSet<ClassLoader>(Arrays.asList(new ClassLoader[]{resourceAccessor.toClassLoader()})));
packagesToScan = new ArrayList<String>();
addPackageToScan("liquibase.change");
addPackageToScan("liquibase.database");
addPackageToScan("liquibase.parser");
addPackageToScan("liquibase.precondition");
addPackageToScan("liquibase.serializer");
addPackageToScan("liquibase.sqlgenerator");
addPackageToScan("liquibase.executor");
addPackageToScan("liquibase.snapshot");
}
public void addPackageToScan(String packageName) {
packagesToScan.add(packageName);
}
public Class findClass(Class requiredInterface) throws ServiceNotFoundException {
Class[] classes = findClasses(requiredInterface);
if (PrioritizedService.class.isAssignableFrom(requiredInterface)) {
PrioritizedService returnObject = null;
for (Class clazz : classes) {
PrioritizedService newInstance;
try {
newInstance = (PrioritizedService) clazz.newInstance();
} catch (Exception e) {
throw new UnexpectedLiquibaseException(e);
}
if (returnObject == null || newInstance.getPriority() > returnObject.getPriority()) {
returnObject = newInstance;
}
}
if (returnObject == null) {
throw new ServiceNotFoundException("Could not find implementation of " + requiredInterface.getName());
}
return returnObject.getClass();
}
if (classes.length != 1) {
throw new ServiceNotFoundException("Could not find unique implementation of " + requiredInterface.getName() + ". Found " + classes.length + " implementations");
}
return classes[0];
}
public Class[] findClasses(Class requiredInterface) throws ServiceNotFoundException {
logger.fine("ServiceLocator.findClasses for " + requiredInterface.getName());
try {
Class.forName(requiredInterface.getName());
if (!classesBySuperclass.containsKey(requiredInterface)) {
classesBySuperclass.put(requiredInterface, findClassesImpl(requiredInterface));
}
} catch (Exception e) {
throw new ServiceNotFoundException(e);
}
List<Class> classes = classesBySuperclass.get(requiredInterface);
HashSet<Class> uniqueClasses = new HashSet<Class>(classes);
return uniqueClasses.toArray(new Class[uniqueClasses.size()]);
}
public Object newInstance(Class requiredInterface) throws ServiceNotFoundException {
try {
return findClass(requiredInterface).newInstance();
} catch (Exception e) {
throw new ServiceNotFoundException(e);
}
}
private List<Class> findClassesImpl(Class requiredInterface) throws Exception {
logger.fine("ServiceLocator finding classes matching interface " + requiredInterface.getName());
List<Class> classes = new ArrayList<Class>();
classResolver.addClassLoader(resourceAccessor.toClassLoader());
for (Class<?> clazz : classResolver.findImplementations(requiredInterface, packagesToScan.toArray(new String[packagesToScan.size()]))) {
if (clazz.getAnnotation(LiquibaseService.class) != null && clazz.getAnnotation(LiquibaseService.class).skip()) {
continue;
}
if (!Modifier.isAbstract(clazz.getModifiers()) && !Modifier.isInterface(clazz.getModifiers()) && Modifier.isPublic(clazz.getModifiers())) {
try {
clazz.getConstructor();
logger.fine(clazz.getName() + " matches " + requiredInterface.getName());
classes.add(clazz);
} catch (NoSuchMethodException e) {
logger.info("Can not use " + clazz + " as a Liquibase service because it does not have a no-argument constructor");
}
}
}
return classes;
}
protected Logger getLogger() {
return logger;
}
}