Browse Source
Create LiquibaseServiceLocatorInitializer to replace the standard liquibase classpath scanning logic with SpringPackageScanClassResolver which will work correctly in Spring Boot packaged executable JARs. Issue: #55580628pull/50/head
7 changed files with 246 additions and 1 deletions
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
package org.springframework.boot.liquibase; |
||||
|
||||
import liquibase.servicelocator.CustomResolverServiceLocator; |
||||
import liquibase.servicelocator.ServiceLocator; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.SpringApplicationInitializer; |
||||
import org.springframework.context.ApplicationContextInitializer; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* {@link SpringApplicationInitializer} that replaces the liquibase {@link ServiceLocator} |
||||
* with a version that works with Spring Boot executable archives. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public class LiquibaseServiceLocatorInitializer implements |
||||
ApplicationContextInitializer<ConfigurableApplicationContext>, |
||||
SpringApplicationInitializer { |
||||
|
||||
static final Log logger = LogFactory |
||||
.getLog(LiquibaseServiceLocatorInitializer.class); |
||||
|
||||
@Override |
||||
public void initialize(SpringApplication springApplication, String[] args) { |
||||
if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) { |
||||
new LiquibasePresent().replaceServiceLocator(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void initialize(ConfigurableApplicationContext applicationContext) { |
||||
} |
||||
|
||||
/** |
||||
* Inner class to prevent class not found issues |
||||
*/ |
||||
private static class LiquibasePresent { |
||||
|
||||
public void replaceServiceLocator() { |
||||
ServiceLocator.setInstance(new CustomResolverServiceLocator( |
||||
new SpringPackageScanClassResolver())); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
/* |
||||
* Copyright 2012-2013 the original author or authors. |
||||
* |
||||
* 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.springframework.boot.liquibase; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.Set; |
||||
|
||||
import liquibase.servicelocator.DefaultPackageScanClassResolver; |
||||
import liquibase.servicelocator.PackageScanClassResolver; |
||||
import liquibase.servicelocator.PackageScanFilter; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; |
||||
import org.springframework.core.io.support.ResourcePatternResolver; |
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory; |
||||
import org.springframework.core.type.classreading.MetadataReader; |
||||
import org.springframework.core.type.classreading.MetadataReaderFactory; |
||||
import org.springframework.util.ClassUtils; |
||||
|
||||
/** |
||||
* Liquibase {@link PackageScanClassResolver} implementation that uses Spring's resource |
||||
* scanning to locate classes. This variant is safe to use with Spring Boot packaged |
||||
* executable JARs. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public class SpringPackageScanClassResolver extends DefaultPackageScanClassResolver { |
||||
|
||||
@Override |
||||
protected void find(PackageScanFilter test, String packageName, ClassLoader loader, |
||||
Set<Class<?>> classes) { |
||||
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory( |
||||
loader); |
||||
try { |
||||
Resource[] resources = scan(loader, packageName); |
||||
for (Resource resource : resources) { |
||||
Class<?> candidate = loadClass(loader, metadataReaderFactory, resource); |
||||
if (candidate != null && test.matches(candidate)) { |
||||
classes.add(candidate); |
||||
} |
||||
} |
||||
} |
||||
catch (IOException ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
} |
||||
|
||||
private Resource[] scan(ClassLoader loader, String packageName) throws IOException { |
||||
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(loader); |
||||
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX |
||||
+ ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class"; |
||||
Resource[] resources = resolver.getResources(pattern); |
||||
return resources; |
||||
} |
||||
|
||||
private Class<?> loadClass(ClassLoader loader, MetadataReaderFactory readerFactory, |
||||
Resource resource) { |
||||
try { |
||||
MetadataReader reader = readerFactory.getMetadataReader(resource); |
||||
return ClassUtils.forName(reader.getClassMetadata().getClassName(), loader); |
||||
} |
||||
catch (Exception ex) { |
||||
if (LiquibaseServiceLocatorInitializer.logger.isWarnEnabled()) { |
||||
LiquibaseServiceLocatorInitializer.logger.warn( |
||||
"Ignoring cadidate class resource " + resource, ex); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
/* |
||||
* Copyright 2012-2013 the original author or authors. |
||||
* |
||||
* 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.springframework.boot.liquibase; |
||||
|
||||
import java.lang.reflect.Field; |
||||
|
||||
import liquibase.servicelocator.ServiceLocator; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.hamcrest.Matchers.instanceOf; |
||||
import static org.junit.Assert.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link LiquibaseServiceLocatorInitializer}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public class LiquibaseServiceLocatorInitializerTests { |
||||
|
||||
@Test |
||||
public void replacesServiceLocator() throws Exception { |
||||
SpringApplication application = new SpringApplication(Conf.class); |
||||
application.setWebEnvironment(false); |
||||
application.run(); |
||||
ServiceLocator instance = ServiceLocator.getInstance(); |
||||
Field field = ReflectionUtils.findField(ServiceLocator.class, "classResolver"); |
||||
field.setAccessible(true); |
||||
Object resolver = field.get(instance); |
||||
assertThat(resolver, instanceOf(SpringPackageScanClassResolver.class)); |
||||
} |
||||
|
||||
@Configuration |
||||
public static class Conf { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
/* |
||||
* Copyright 2012-2013 the original author or authors. |
||||
* |
||||
* 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.springframework.boot.liquibase; |
||||
|
||||
import java.util.Set; |
||||
|
||||
import liquibase.logging.Logger; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
import static org.hamcrest.Matchers.greaterThan; |
||||
import static org.junit.Assert.assertThat; |
||||
|
||||
/** |
||||
* Tests for SpringPackageScanClassResolver. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
public class SpringPackageScanClassResolverTests { |
||||
|
||||
@Test |
||||
public void testScan() { |
||||
SpringPackageScanClassResolver resolver = new SpringPackageScanClassResolver(); |
||||
resolver.addClassLoader(getClass().getClassLoader()); |
||||
Set<Class<?>> implementations = resolver.findImplementations(Logger.class, |
||||
"liquibase.logging.core"); |
||||
assertThat(implementations.size(), greaterThan(0)); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue