package liquibase.servicelocator; import liquibase.exception.ServiceNotFoundException; import liquibase.exception.UnexpectedLiquibaseException; import liquibase.logging.LogFactory; import liquibase.logging.Logger; import liquibase.logging.core.DefaultLogger; import liquibase.osgi.OSGiPackageScanClassResolver; import liquibase.osgi.OSGiResourceAccessor; import liquibase.osgi.OSGiUtil; import liquibase.resource.ClassLoaderResourceAccessor; import liquibase.resource.ResourceAccessor; import liquibase.util.StringUtils; import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Modifier; import java.util.*; import java.util.jar.Manifest; /** * Entry point to the Liquibase specific ServiceLocator framework. * * Services (concrete instances of interfaces) are located by scanning nominated * packages on the classpath for implementations of the interface. */ public class ServiceLocator { private static ServiceLocator instance; static { try { Class<?> scanner = Class.forName("Liquibase.ServiceLocator.ClrServiceLocator, Liquibase"); instance = (ServiceLocator) scanner.newInstance(); } catch (Exception e) { try { if (OSGiUtil.isLiquibaseLoadedAsOSGiBundle()) { Bundle liquibaseBundle = FrameworkUtil.getBundle(ServiceLocator.class); instance = new ServiceLocator(new OSGiPackageScanClassResolver(liquibaseBundle), new OSGiResourceAccessor(liquibaseBundle)); } else { instance = new ServiceLocator(); } } catch (Throwable e1) { LogFactory.getInstance().getLog().severe("Cannot build ServiceLocator", e1); } } } private ResourceAccessor resourceAccessor; private Map<Class, List<Class>> classesBySuperclass; private List<String> packagesToScan; private Logger logger = new DefaultLogger(); //cannot look up regular logger because you get a stackoverflow since we are in the servicelocator private PackageScanClassResolver classResolver; protected ServiceLocator() { this.classResolver = defaultClassLoader(); setResourceAccessor(new ClassLoaderResourceAccessor()); } protected ServiceLocator(ResourceAccessor accessor) { this.classResolver = defaultClassLoader(); setResourceAccessor(accessor); } protected ServiceLocator(PackageScanClassResolver classResolver) { this.classResolver = classResolver; setResourceAccessor(new ClassLoaderResourceAccessor()); } protected ServiceLocator(PackageScanClassResolver classResolver, ResourceAccessor accessor) { this.classResolver = classResolver; setResourceAccessor(accessor); } public static ServiceLocator getInstance() { return instance; } public static synchronized void setInstance(ServiceLocator newInstance) { instance = newInstance; } protected PackageScanClassResolver defaultClassLoader(){ if (WebSpherePackageScanClassResolver.isWebSphereClassLoader(this.getClass().getClassLoader())) { logger.debug("Using WebSphere Specific Class Resolver"); return new WebSpherePackageScanClassResolver("liquibase/parser/core/xml/dbchangelog-2.0.xsd"); } else { return new DefaultPackageScanClassResolver(); } } 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()}))); if (packagesToScan == null) { packagesToScan = new ArrayList<String>(); String packagesToScanSystemProp = System.getProperty("liquibase.scan.packages"); if ((packagesToScanSystemProp != null) && ((packagesToScanSystemProp = StringUtils.trimToNull(packagesToScanSystemProp)) != null)) { for (String value : packagesToScanSystemProp.split(",")) { addPackageToScan(value); } } else { Set<InputStream> manifests; try { manifests = resourceAccessor.getResourcesAsStream("META-INF/MANIFEST.MF"); if (manifests != null) { for (InputStream is : manifests) { Manifest manifest = new Manifest(is); String attributes = StringUtils.trimToNull(manifest.getMainAttributes().getValue("Liquibase-Package")); if (attributes != null) { for (Object value : attributes.split(",")) { addPackageToScan(value.toString()); } } is.close(); } } } catch (IOException e) { throw new UnexpectedLiquibaseException(e); } if (packagesToScan.size() == 0) { addPackageToScan("liquibase.change"); addPackageToScan("liquibase.changelog"); addPackageToScan("liquibase.database"); addPackageToScan("liquibase.parser"); addPackageToScan("liquibase.precondition"); addPackageToScan("liquibase.datatype"); addPackageToScan("liquibase.serializer"); addPackageToScan("liquibase.sqlgenerator"); addPackageToScan("liquibase.executor"); addPackageToScan("liquibase.snapshot"); addPackageToScan("liquibase.logging"); addPackageToScan("liquibase.diff"); addPackageToScan("liquibase.structure"); addPackageToScan("liquibase.structurecompare"); addPackageToScan("liquibase.lockservice"); addPackageToScan("liquibase.sdk.database"); addPackageToScan("liquibase.ext"); } } } } public void addPackageToScan(String packageName) { packagesToScan.add(packageName); } public List<String> getPackages() { return packagesToScan; } 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 <T> Class<? extends T>[] findClasses(Class<T> requiredInterface) throws ServiceNotFoundException { logger.debug("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.debug("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.debug(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" ); } catch (NoClassDefFoundError e) { String message = "Can not use " + clazz + " as a Liquibase service because " + e.getMessage().replace("/", ".") + " is not in the classpath"; if (e.getMessage().startsWith("org/yaml/snakeyaml")) { logger.info(message); } else { logger.warning(message); } } } } return classes; } public static synchronized void reset() { instance = new ServiceLocator(); } protected Logger getLogger() { return logger; } }