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}