001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.liquibase; 018 019import java.io.IOException; 020 021import liquibase.servicelocator.DefaultPackageScanClassResolver; 022import liquibase.servicelocator.PackageScanClassResolver; 023import org.apache.commons.logging.Log; 024 025import org.springframework.core.io.Resource; 026import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 027import org.springframework.core.io.support.ResourcePatternResolver; 028import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 029import org.springframework.core.type.classreading.MetadataReader; 030import org.springframework.core.type.classreading.MetadataReaderFactory; 031import org.springframework.util.ClassUtils; 032 033/** 034 * Liquibase {@link PackageScanClassResolver} implementation that uses Spring's resource 035 * scanning to locate classes. This variant is safe to use with Spring Boot packaged 036 * executable JARs. 037 * 038 * @author Phillip Webb 039 */ 040public class SpringPackageScanClassResolver extends DefaultPackageScanClassResolver { 041 042 private final Log logger; 043 044 public SpringPackageScanClassResolver(Log logger) { 045 this.logger = logger; 046 } 047 048 @Override 049 protected void findAllClasses(String packageName, ClassLoader loader) { 050 MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory( 051 loader); 052 try { 053 Resource[] resources = scan(loader, packageName); 054 for (Resource resource : resources) { 055 Class<?> clazz = loadClass(loader, metadataReaderFactory, resource); 056 if (clazz != null) { 057 addFoundClass(clazz); 058 } 059 } 060 } 061 catch (IOException ex) { 062 throw new IllegalStateException(ex); 063 } 064 } 065 066 private Resource[] scan(ClassLoader loader, String packageName) throws IOException { 067 ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver( 068 loader); 069 String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX 070 + ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class"; 071 return resolver.getResources(pattern); 072 } 073 074 private Class<?> loadClass(ClassLoader loader, MetadataReaderFactory readerFactory, 075 Resource resource) { 076 try { 077 MetadataReader reader = readerFactory.getMetadataReader(resource); 078 return ClassUtils.forName(reader.getClassMetadata().getClassName(), loader); 079 } 080 catch (ClassNotFoundException | LinkageError ex) { 081 handleFailure(resource, ex); 082 return null; 083 } 084 catch (Throwable ex) { 085 if (this.logger.isWarnEnabled()) { 086 this.logger.warn( 087 "Unexpected failure when loading class resource " + resource, ex); 088 } 089 return null; 090 } 091 } 092 093 private void handleFailure(Resource resource, Throwable ex) { 094 if (this.logger.isDebugEnabled()) { 095 this.logger.debug( 096 "Ignoring candidate class resource " + resource + " due to " + ex); 097 } 098 } 099 100}