Merge branch '1.5.x'

pull/7300/head
Phillip Webb 8 years ago
commit 98a3ae9ac4

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.mvc;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -82,7 +83,8 @@ public class LogFileMvcEndpoint extends AbstractNamedMvcEndpoint {
} }
resource = null; resource = null;
} }
new Handler(resource).handleRequest(request, response); Handler handler = new Handler(resource, request.getServletContext());
handler.handleRequest(request, response);
} }
private Resource getLogFileResource() { private Resource getLogFileResource() {
@ -104,10 +106,11 @@ public class LogFileMvcEndpoint extends AbstractNamedMvcEndpoint {
private final Resource resource; private final Resource resource;
Handler(Resource resource) { Handler(Resource resource, ServletContext servletContext) {
this.resource = resource; this.resource = resource;
getLocations().add(resource); getLocations().add(resource);
try { try {
setServletContext(servletContext);
afterPropertiesSet(); afterPropertiesSet();
} }
catch (Exception ex) { catch (Exception ex) {

@ -27,8 +27,7 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter; import org.springframework.core.type.filter.TypeFilter;
/** /**
* A {@link TypeFilter} implementation that matches registered auto-configuration * A {@link TypeFilter} implementation that matches registered auto-configuration classes.
* classes.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 1.5.0 * @since 1.5.0
@ -37,7 +36,7 @@ public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoad
private ClassLoader beanClassLoader; private ClassLoader beanClassLoader;
private List<String> candidateAutoConfigurations; private volatile List<String> autoConfigurations;
@Override @Override
public void setBeanClassLoader(ClassLoader beanClassLoader) { public void setBeanClassLoader(ClassLoader beanClassLoader) {
@ -50,22 +49,22 @@ public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoad
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader); return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
} }
protected List<String> getCandidateAutoConfigurations() {
if (this.candidateAutoConfigurations == null) {
this.candidateAutoConfigurations = SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, this.beanClassLoader);
}
return this.candidateAutoConfigurations;
}
private boolean isConfiguration(MetadataReader metadataReader) { private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata() return metadataReader.getAnnotationMetadata()
.isAnnotated(Configuration.class.getName()); .isAnnotated(Configuration.class.getName());
} }
private boolean isAutoConfiguration(MetadataReader metadataReader) { private boolean isAutoConfiguration(MetadataReader metadataReader) {
return getCandidateAutoConfigurations().contains( return getAutoConfigurations()
metadataReader.getClassMetadata().getClassName()); .contains(metadataReader.getClassMetadata().getClassName());
}
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, this.beanClassLoader);
}
return this.autoConfigurations;
} }
} }

@ -16,16 +16,12 @@
package org.springframework.boot.autoconfigure.orm.jpa; package org.springframework.boot.autoconfigure.orm.jpa;
import java.net.URL;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
@ -53,7 +49,6 @@ import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@ -69,8 +64,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
@Import(DataSourceInitializedPublisher.Registrar.class) @Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware { public abstract class JpaBaseConfiguration implements BeanFactoryAware {
private static final Log logger = LogFactory.getLog(JpaBaseConfiguration.class);
private final DataSource dataSource; private final DataSource dataSource;
private final JpaProperties properties; private final JpaProperties properties;
@ -110,8 +103,7 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
ObjectProvider<PersistenceUnitManager> persistenceUnitManagerProvider) { ObjectProvider<PersistenceUnitManager> persistenceUnitManagerProvider) {
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder( EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(
jpaVendorAdapter, this.properties.getProperties(), jpaVendorAdapter, this.properties.getProperties(),
persistenceUnitManagerProvider.getIfAvailable(), persistenceUnitManagerProvider.getIfAvailable());
determinePersistenceUnitRootLocation());
builder.setCallback(getVendorCallback()); builder.setCallback(getVendorCallback());
return builder; return builder;
} }
@ -190,19 +182,6 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
} }
private URL determinePersistenceUnitRootLocation() {
Class<?> source = getClass();
try {
URL url = source.getProtectionDomain().getCodeSource().getLocation();
return ResourceUtils.extractJarFileURL(url);
}
catch (Exception ex) {
logger.info("Could not determine persistence " + "unit root location from "
+ source + " : " + ex);
}
return null;
}
@Configuration @Configuration
@ConditionalOnWebApplication @ConditionalOnWebApplication
@ConditionalOnClass(WebMvcConfigurerAdapter.class) @ConditionalOnClass(WebMvcConfigurerAdapter.class)

@ -675,9 +675,9 @@ public class ServerProperties
private int acceptCount = 0; private int acceptCount = 0;
/** /**
* Comma-separated list of additional patterns that match jars to ignore for * Comma-separated list of additional patterns that match jars to ignore for TLD
* TLD scanning. The special '?' and '*' characters can be used in the pattern * scanning. The special '?' and '*' characters can be used in the pattern to
* to match one and only one character and zero or more characters respectively. * match one and only one character and zero or more characters respectively.
*/ */
private List<String> additionalTldSkipPatterns = new ArrayList<String>(); private List<String> additionalTldSkipPatterns = new ArrayList<String>();

@ -41,6 +41,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class AutoConfigurationExcludeFilterTests { public class AutoConfigurationExcludeFilterTests {
private static final Class<?> FILTERED = ExampleFilteredAutoConfiguration.class;
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@ -59,10 +61,9 @@ public class AutoConfigurationExcludeFilterTests {
assertThat(this.context.getBeansOfType(String.class)).hasSize(1); assertThat(this.context.getBeansOfType(String.class)).hasSize(1);
assertThat(this.context.getBean(String.class)).isEqualTo("test"); assertThat(this.context.getBean(String.class)).isEqualTo("test");
this.thrown.expect(NoSuchBeanDefinitionException.class); this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(ExampleFilteredAutoConfiguration.class); this.context.getBean(FILTERED);
} }
@Configuration @Configuration
@ComponentScan(basePackageClasses = ExampleConfiguration.class, excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TestAutoConfigurationExcludeFilter.class)) @ComponentScan(basePackageClasses = ExampleConfiguration.class, excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TestAutoConfigurationExcludeFilter.class))
static class Config { static class Config {
@ -71,10 +72,12 @@ public class AutoConfigurationExcludeFilterTests {
static class TestAutoConfigurationExcludeFilter static class TestAutoConfigurationExcludeFilter
extends AutoConfigurationExcludeFilter { extends AutoConfigurationExcludeFilter {
@Override @Override
protected List<String> getCandidateAutoConfigurations() { protected List<String> getAutoConfigurations() {
return Collections.singletonList(ExampleFilteredAutoConfiguration.class.getName()); return Collections.singletonList(FILTERED.getName());
} }
} }
} }

@ -493,7 +493,6 @@ public class ServerPropertiesTests {
Map<String, String> map = new HashMap<String, String>(); Map<String, String> map = new HashMap<String, String>();
map.put("server.tomcat.additional-tld-skip-patterns", "foo.jar,bar.jar"); map.put("server.tomcat.additional-tld-skip-patterns", "foo.jar,bar.jar");
bindProperties(map); bindProperties(map);
testCustomTomcatTldSkip("foo.jar", "bar.jar"); testCustomTomcatTldSkip("foo.jar", "bar.jar");
} }
@ -503,7 +502,6 @@ public class ServerPropertiesTests {
map.put("server.tomcat.additional-tld-skip-patterns[0]", "biz.jar"); map.put("server.tomcat.additional-tld-skip-patterns[0]", "biz.jar");
map.put("server.tomcat.additional-tld-skip-patterns[1]", "bah.jar"); map.put("server.tomcat.additional-tld-skip-patterns[1]", "bah.jar");
bindProperties(map); bindProperties(map);
testCustomTomcatTldSkip("biz.jar", "bah.jar"); testCustomTomcatTldSkip("biz.jar", "bah.jar");
} }
@ -511,7 +509,8 @@ public class ServerPropertiesTests {
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory(); TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(container); this.properties.customize(container);
assertThat(container.getTldSkipPatterns()).contains(expectedJars); assertThat(container.getTldSkipPatterns()).contains(expectedJars);
assertThat(container.getTldSkipPatterns()).contains("junit-*.jar", "spring-boot-*.jar"); assertThat(container.getTldSkipPatterns()).contains("junit-*.jar",
"spring-boot-*.jar");
} }
@Test @Test

@ -21,10 +21,13 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
@ -59,7 +62,7 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
@Override @Override
public void postProcessEnvironment(ConfigurableEnvironment environment, public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) { SpringApplication application) {
if (isLocalApplication(environment)) { if (isLocalApplication(environment) && canAddProperties(environment)) {
PropertySource<?> propertySource = new MapPropertySource("refresh", PropertySource<?> propertySource = new MapPropertySource("refresh",
PROPERTIES); PROPERTIES);
environment.getPropertySources().addLast(propertySource); environment.getPropertySources().addLast(propertySource);
@ -70,4 +73,24 @@ public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostPro
return environment.getPropertySources().get("remoteUrl") == null; return environment.getPropertySources().get("remoteUrl") == null;
} }
private boolean canAddProperties(Environment environment) {
return isRestarterInitialized() || isRemoteRestartEnabled(environment);
}
private boolean isRestarterInitialized() {
try {
Restarter restarter = Restarter.getInstance();
return (restarter != null && restarter.getInitialUrls() != null);
}
catch (Exception ex) {
return false;
}
}
private boolean isRemoteRestartEnabled(Environment environment) {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,
"spring.devtools.remote.");
return resolver.containsProperty("secret");
}
} }

@ -16,11 +16,20 @@
package org.springframework.boot.devtools.env; package org.springframework.boot.devtools.env;
import java.net.URL;
import java.util.Collections;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.devtools.restart.RestartInitializer;
import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -32,13 +41,22 @@ import org.springframework.context.annotation.Configuration;
*/ */
public class DevToolPropertiesIntegrationTests { public class DevToolPropertiesIntegrationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private ConfigurableApplicationContext context; private ConfigurableApplicationContext context;
@Before
public void setup() {
Restarter.initialize(new String[] {}, false, new MockInitializer(), false);
}
@After @After
public void cleanup() { public void cleanup() {
if (this.context != null) { if (this.context != null) {
this.context.close(); this.context.close();
} }
Restarter.clearInstance();
} }
@Test @Test
@ -59,6 +77,33 @@ public class DevToolPropertiesIntegrationTests {
this.context.getBean(MyBean.class); this.context.getBean(MyBean.class);
} }
@Test
public void postProcessWhenRestarterDisabledAndRemoteSecretNotSetShouldNotAddPropertySource()
throws Exception {
Restarter.clearInstance();
Restarter.disable();
SpringApplication application = new SpringApplication(
BeanConditionConfiguration.class);
application.setWebEnvironment(false);
this.context = application.run();
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(MyBean.class);
}
@Test
public void postProcessWhenRestarterDisabledAndRemoteSecretSetShouldAddPropertySource()
throws Exception {
Restarter.clearInstance();
Restarter.disable();
SpringApplication application = new SpringApplication(
BeanConditionConfiguration.class);
application.setWebEnvironment(false);
application.setDefaultProperties(Collections.<String, Object>singletonMap(
"spring.devtools.remote.secret", "donttell"));
this.context = application.run();
this.context.getBean(MyBean.class);
}
@Configuration @Configuration
@ConditionalOnProperty("spring.h2.console.enabled") @ConditionalOnProperty("spring.h2.console.enabled")
static class ClassConditionConfiguration { static class ClassConditionConfiguration {
@ -79,4 +124,12 @@ public class DevToolPropertiesIntegrationTests {
} }
static class MockInitializer implements RestartInitializer {
@Override
public URL[] getInitialUrls(Thread thread) {
return new URL[] {};
}
}
} }

@ -493,7 +493,7 @@ The following configuration options are available:
[[build-tool-plugins-gradle-configuration-layouts]] [[build-tool-plugins-gradle-configuration-layouts]]
==== Available built-in layouts ==== Available layouts
The `layout` attribute configures the format of the archive and whether the bootstrap The `layout` attribute configures the format of the archive and whether the bootstrap
loader should be included or not. The following layouts are available: loader should be included or not. The following layouts are available:
@ -530,37 +530,6 @@ loader should be included or not. The following layouts are available:
[[build-tool-plugins-gradle-configuration-custom-layout]]
==== Using a custom layout
If you have custom requirements for how to arrange the dependencies and loader classes
inside the repackaged jar, you can use a custom layout in addition to the built-in values.
Any library which defines one or more `LayoutFactory` implementations and
lists them in `META-INF/spring.factories` can be added to the build script dependencies
and then the layout type becomes available in the `springBoot` configuration. For example
[source,groovy,indent=0,subs="verbatim,attributes"]
----
buildscript {
ext {
springBootVersion = '1.5.0.BUILD-SNAPSHOT'
customVersion = '0.0.1.BUILD-SNAPSHOT'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("com.example:custom-layout:${customVersion}")
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
springBoot {
layout = 'CUSTOM'
}
----
[[build-tool-plugins-understanding-the-gradle-plugin]] [[build-tool-plugins-understanding-the-gradle-plugin]]
=== Understanding how the Gradle plugin works === Understanding how the Gradle plugin works
When `spring-boot` is applied to your Gradle project a default task named `bootRepackage` When `spring-boot` is applied to your Gradle project a default task named `bootRepackage`

@ -101,15 +101,20 @@ public class TestDatabaseAutoConfiguration {
ConfigurableListableBeanFactory beanFactory) { ConfigurableListableBeanFactory beanFactory) {
BeanDefinitionHolder holder = getDataSourceBeanDefinition(beanFactory); BeanDefinitionHolder holder = getDataSourceBeanDefinition(beanFactory);
if (holder != null) { if (holder != null) {
logger.info("Replacing '" + holder.getBeanName() String beanName = holder.getBeanName();
+ "' DataSource bean with embedded version"); boolean primary = holder.getBeanDefinition().isPrimary();
registry.registerBeanDefinition(holder.getBeanName(), logger.info("Replacing '" + beanName + "' DataSource bean with "
createEmbeddedBeanDefinition()); + (primary ? "primary " : "") + "embedded version");
registry.registerBeanDefinition(beanName,
createEmbeddedBeanDefinition(primary));
} }
} }
private BeanDefinition createEmbeddedBeanDefinition() { private BeanDefinition createEmbeddedBeanDefinition(boolean primary) {
return new RootBeanDefinition(EmbeddedDataSourceFactoryBean.class); BeanDefinition beanDefinition = new RootBeanDefinition(
EmbeddedDataSourceFactoryBean.class);
beanDefinition.setPrimary(primary);
return beanDefinition;
} }
private BeanDefinitionHolder getDataSourceBeanDefinition( private BeanDefinitionHolder getDataSourceBeanDefinition(

@ -0,0 +1,92 @@
/*
* Copyright 2012-2016 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.test.autoconfigure.orm.jpa;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link AutoConfigureTestDatabase} when there are multiple
* datasources.
*
* @author Greg Potter
*/
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase
public class AutoConfigureTestDatabaseWithMultipleDatasourcesIntegrationTests {
@Autowired
private TestEntityManager entities;
@Autowired
private ExampleRepository repository;
@Autowired
private DataSource dataSource;
@Test
public void testRepository() throws Exception {
this.entities.persist(new ExampleEntity("boot", "124"));
this.entities.flush();
ExampleEntity found = this.repository.findByReference("124");
assertThat(found.getName()).isEqualTo("boot");
}
@Test
public void replacesDefinedDataSourceWithExplicit() throws Exception {
// Look that the datasource is replaced with an H2 DB.
String product = this.dataSource.getConnection().getMetaData()
.getDatabaseProductName();
assertThat(product).startsWith("H2");
}
@Configuration
@EnableAutoConfiguration
static class Config {
@Bean
@Primary
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL);
return builder.build();
}
@Bean
public DataSource secondaryDataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL);
return builder.build();
}
}
}

@ -96,8 +96,8 @@ class TypeUtils {
/** /**
* Return the qualified name of the specified element. * Return the qualified name of the specified element.
* @param element the element to handle * @param element the element to handle
* @return the fully qualified name of the element, suitable for a call * @return the fully qualified name of the element, suitable for a call to
* to {@link Class#forName(String)} * {@link Class#forName(String)}
*/ */
public String getQualifiedName(Element element) { public String getQualifiedName(Element element) {
if (element == null) { if (element == null) {
@ -106,13 +106,14 @@ class TypeUtils {
TypeElement enclosingElement = getEnclosingTypeElement(element.asType()); TypeElement enclosingElement = getEnclosingTypeElement(element.asType());
if (enclosingElement != null) { if (enclosingElement != null) {
return getQualifiedName(enclosingElement) + "$" return getQualifiedName(enclosingElement) + "$"
+ ((DeclaredType) element.asType()).asElement().getSimpleName().toString(); + ((DeclaredType) element.asType()).asElement().getSimpleName()
.toString();
} }
if (element instanceof TypeElement) { if (element instanceof TypeElement) {
return ((TypeElement) element).getQualifiedName().toString(); return ((TypeElement) element).getQualifiedName().toString();
} }
throw new IllegalStateException("Could not extract qualified name from " throw new IllegalStateException(
+ element); "Could not extract qualified name from " + element);
} }
/** /**

@ -404,8 +404,7 @@ public class ConfigurationMetadataAnnotationProcessorTests {
assertThat(metadata).has(Metadata.withGroup("generic.foo.bar.biz").ofType( assertThat(metadata).has(Metadata.withGroup("generic.foo.bar.biz").ofType(
"org.springframework.boot.configurationsample.specific.GenericConfig$Bar$Biz")); "org.springframework.boot.configurationsample.specific.GenericConfig$Bar$Biz"));
assertThat(metadata).has(Metadata.withProperty("generic.foo.name") assertThat(metadata).has(Metadata.withProperty("generic.foo.name")
.ofType(String.class) .ofType(String.class).fromSource(GenericConfig.Foo.class));
.fromSource(GenericConfig.Foo.class));
assertThat(metadata).has(Metadata.withProperty("generic.foo.string-to-bar") assertThat(metadata).has(Metadata.withProperty("generic.foo.string-to-bar")
.ofType("java.util.Map<java.lang.String,org.springframework.boot.configurationsample.specific.GenericConfig.Bar<java.lang.Integer>>") .ofType("java.util.Map<java.lang.String,org.springframework.boot.configurationsample.specific.GenericConfig.Bar<java.lang.Integer>>")
.fromSource(GenericConfig.Foo.class)); .fromSource(GenericConfig.Foo.class));
@ -413,11 +412,9 @@ public class ConfigurationMetadataAnnotationProcessorTests {
.ofType("java.util.Map<java.lang.String,java.lang.Integer>") .ofType("java.util.Map<java.lang.String,java.lang.Integer>")
.fromSource(GenericConfig.Foo.class)); .fromSource(GenericConfig.Foo.class));
assertThat(metadata).has(Metadata.withProperty("generic.foo.bar.name") assertThat(metadata).has(Metadata.withProperty("generic.foo.bar.name")
.ofType("java.lang.String") .ofType("java.lang.String").fromSource(GenericConfig.Bar.class));
.fromSource(GenericConfig.Bar.class));
assertThat(metadata).has(Metadata.withProperty("generic.foo.bar.biz.name") assertThat(metadata).has(Metadata.withProperty("generic.foo.bar.biz.name")
.ofType("java.lang.String") .ofType("java.lang.String").fromSource(GenericConfig.Bar.Biz.class));
.fromSource(GenericConfig.Bar.Biz.class));
assertThat(metadata.getItems()).hasSize(9); assertThat(metadata.getItems()).hasSize(9);
} }

@ -21,13 +21,12 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import groovy.lang.Closure; import groovy.lang.Closure;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.springframework.boot.gradle.buildinfo.BuildInfo; import org.springframework.boot.gradle.buildinfo.BuildInfo;
import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.LayoutType; import org.springframework.boot.loader.tools.Layouts;
/** /**
* Gradle DSL Extension for 'Spring Boot'. Most of the time Spring Boot can guess the * Gradle DSL Extension for 'Spring Boot'. Most of the time Spring Boot can guess the
@ -89,7 +88,7 @@ public class SpringBootPluginExtension {
* the MANIFEST.MF 'Main-Class' to be PropertiesLauncher. Gradle will coerce literal * the MANIFEST.MF 'Main-Class' to be PropertiesLauncher. Gradle will coerce literal
* String values to the correct type. * String values to the correct type.
*/ */
String layout; LayoutType layout;
/** /**
* Libraries that must be unpacked from fat jars in order to run. Use Strings in the * Libraries that must be unpacked from fat jars in order to run. Use Strings in the
@ -146,7 +145,7 @@ public class SpringBootPluginExtension {
* @return the Layout to use or null if not explicitly set * @return the Layout to use or null if not explicitly set
*/ */
public Layout convertLayout() { public Layout convertLayout() {
return (this.layout == null ? null : LayoutType.layout(this.layout)); return (this.layout == null ? null : this.layout.layout);
} }
public String getMainClass() { public String getMainClass() {
@ -189,11 +188,11 @@ public class SpringBootPluginExtension {
this.backupSource = backupSource; this.backupSource = backupSource;
} }
public String getLayout() { public LayoutType getLayout() {
return this.layout; return this.layout;
} }
public void setLayout(String layout) { public void setLayout(LayoutType layout) {
this.layout = layout; this.layout = layout;
} }
@ -277,4 +276,29 @@ public class SpringBootPluginExtension {
} }
} }
/**
* Layout Types.
*/
enum LayoutType {
JAR(new Layouts.Jar()),
WAR(new Layouts.War()),
ZIP(new Layouts.Expanded()),
DIR(new Layouts.Expanded()),
MODULE(new Layouts.Module()),
NONE(new Layouts.None());
Layout layout;
LayoutType(Layout layout) {
this.layout = layout;
}
}
} }

@ -51,6 +51,8 @@ import java.util.zip.ZipEntry;
*/ */
public class JarWriter { public class JarWriter {
private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar";
private static final int BUFFER_SIZE = 32 * 1024; private static final int BUFFER_SIZE = 32 * 1024;
private final JarOutputStream jarOutput; private final JarOutputStream jarOutput;
@ -201,20 +203,9 @@ public class JarWriter {
/** /**
* Write the required spring-boot-loader classes to the JAR. * Write the required spring-boot-loader classes to the JAR.
* @throws IOException if the classes cannot be written * @throws IOException if the classes cannot be written
* @deprecated us {@link #writeLoaderClasses(String)} instead
*/ */
@Deprecated
public void writeLoaderClasses() throws IOException { public void writeLoaderClasses() throws IOException {
writeLoaderClasses(Layouts.DEFAULT_LOADER_JAR); URL loaderJar = getClass().getClassLoader().getResource(NESTED_LOADER_JAR);
}
/**
* Write the required spring-boot-loader classes to the JAR.
* @param loaderJarPath the path to the loader jar (in the classpath)
* @throws IOException if the classes cannot be written
*/
public void writeLoaderClasses(String loaderJarPath) throws IOException {
URL loaderJar = getClass().getClassLoader().getResource(loaderJarPath);
JarInputStream inputStream = new JarInputStream( JarInputStream inputStream = new JarInputStream(
new BufferedInputStream(loaderJar.openStream())); new BufferedInputStream(loaderJar.openStream()));
JarEntry entry; JarEntry entry;

@ -40,25 +40,15 @@ public interface Layout {
String getLibraryDestination(String libraryName, LibraryScope scope); String getLibraryDestination(String libraryName, LibraryScope scope);
/** /**
* Returns the location of classes within the archive. Empty if the location is the * Returns the location of classes within the archive.
* root path, otherwise ends with a slash ('/').
* @return the classes location * @return the classes location
*/ */
String getClassesLocation(); String getClassesLocation();
/** /**
* Returns if loader classes should be included to make the archive executable. If * Returns if loader classes should be included to make the archive executable.
* true, then {@link #getLoaderJarPath()} should point to a valid jar file that
* contains the loader classes.
* @return if the layout is executable * @return if the layout is executable
*/ */
boolean isExecutable(); boolean isExecutable();
/**
* Returns the path to a nested jar that contains the loader, and which will be
* unpacked into the root of the repackaged jar.
* @return the path to a nested jar that contains the loader
*/
String getLoaderJarPath();
} }

@ -1,31 +0,0 @@
/*
* Copyright 2012-2015 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.loader.tools;
/**
* Strategy for creating instances of {@link Layout}.
*
* @author Dave Syer
*
*/
public interface LayoutFactory {
Layout getLayout();
String getName();
}

@ -1,101 +0,0 @@
/*
* Copyright 2012-2015 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.loader.tools;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Archive layout types.
*
* @author Dave Syer
*/
public enum LayoutType {
/**
* Jar Layout.
*/
JAR(new Layouts.Jar()),
/**
* War Layout.
*/
WAR(new Layouts.War()),
/**
* Zip Layout.
*/
ZIP(new Layouts.Expanded()),
/**
* Dir Layout.
*/
DIR(new Layouts.Expanded()),
/**
* Module Layout.
*/
MODULE(new Layouts.Module()),
/**
* No Layout.
*/
NONE(new Layouts.None());
private static Map<String, Layout> customTypes;
private final Layout layout;
public Layout layout() {
return this.layout;
}
LayoutType(Layout layout) {
this.layout = layout;
}
public static Layout layout(String value) {
try {
return valueOf(value).layout();
}
catch (IllegalArgumentException e) {
if (customTypes == null) {
customTypes = new HashMap<String, Layout>();
lookupCustomTypes();
}
Layout layout = customTypes.get(value);
if (layout == null) {
throw new IllegalArgumentException(
"Cannot resolve custom layout type: " + value);
}
return layout;
}
}
private static void lookupCustomTypes() {
ClassLoader classLoader = LayoutType.class.getClassLoader();
List<LayoutFactory> factories = SpringFactoriesLoader
.loadFactories(LayoutFactory.class, classLoader);
for (LayoutFactory factory : factories) {
customTypes.put(factory.getName(), factory.getLayout());
}
}
}

@ -33,11 +33,6 @@ import java.util.Set;
*/ */
public final class Layouts { public final class Layouts {
/**
* Default value for {@link Layout#getLoaderJarPath()}.
*/
public static final String DEFAULT_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar";
private Layouts() { private Layouts() {
} }
@ -92,11 +87,6 @@ public final class Layouts {
return true; return true;
} }
@Override
public String getLoaderJarPath() {
return DEFAULT_LOADER_JAR;
}
} }
/** /**
@ -126,11 +116,6 @@ public final class Layouts {
return false; return false;
} }
@Override
public String getLoaderJarPath() {
return DEFAULT_LOADER_JAR;
}
} }
/** /**
@ -169,11 +154,6 @@ public final class Layouts {
return true; return true;
} }
@Override
public String getLoaderJarPath() {
return DEFAULT_LOADER_JAR;
}
} }
/** /**
@ -208,11 +188,6 @@ public final class Layouts {
return false; return false;
} }
@Override
public String getLoaderJarPath() {
return DEFAULT_LOADER_JAR;
}
} }
} }

@ -216,7 +216,7 @@ public class Repackager {
} }
writeNestedLibraries(standardLibraries, seen, writer); writeNestedLibraries(standardLibraries, seen, writer);
if (this.layout.isExecutable()) { if (this.layout.isExecutable()) {
writer.writeLoaderClasses(this.layout.getLoaderJarPath()); writer.writeLoaderClasses();
} }
} }
finally { finally {

@ -1,53 +0,0 @@
/*
* Copyright 2012-2015 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.loader.tools;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Dave Syer
*
*/
public class LayoutTypeTests {
@Test
public void standardType() {
assertThat(LayoutType.layout("DIR"))
.isEqualTo(LayoutType.valueOf("DIR").layout());
}
@Test
public void customType() {
assertThat(LayoutType.layout("CUSTOM")).isNotNull();
}
public static class TestLayoutFactory implements LayoutFactory {
@Override
public Layout getLayout() {
return new Layouts.Jar();
}
@Override
public String getName() {
return "CUSTOM";
}
}
}

@ -1,2 +0,0 @@
org.springframework.boot.loader.tools.LayoutFactory=\
org.springframework.boot.loader.tools.LayoutTypeTests.TestLayoutFactory

@ -371,6 +371,10 @@ public class JarFile extends java.util.jar.JarFile {
return this.pathFromRoot; return this.pathFromRoot;
} }
JarFileType getType() {
return this.type;
}
/** /**
* Register a {@literal 'java.protocol.handler.pkgs'} property so that a * Register a {@literal 'java.protocol.handler.pkgs'} property so that a
* {@link URLStreamHandler} will be located to deal with jar URLs. * {@link URLStreamHandler} will be located to deal with jar URLs.
@ -396,7 +400,10 @@ public class JarFile extends java.util.jar.JarFile {
} }
} }
private enum JarFileType { /**
* The type of a {@link JarFile}.
*/
enum JarFileType {
DIRECT, NESTED_DIRECTORY, NESTED_JAR DIRECT, NESTED_DIRECTORY, NESTED_JAR
} }

@ -29,6 +29,8 @@ import java.net.URLEncoder;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import java.security.Permission; import java.security.Permission;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
/** /**
* {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}. * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}.
* *
@ -160,11 +162,14 @@ final class JarURLConnection extends java.net.JarURLConnection {
if (this.jarFile == null) { if (this.jarFile == null) {
throw FILE_NOT_FOUND_EXCEPTION; throw FILE_NOT_FOUND_EXCEPTION;
} }
if (this.jarEntryName.isEmpty()) { if (this.jarEntryName.isEmpty()
&& this.jarFile.getType() == JarFile.JarFileType.DIRECT) {
throw new IOException("no entry name specified"); throw new IOException("no entry name specified");
} }
connect(); connect();
InputStream inputStream = this.jarFile.getInputStream(this.jarEntry); InputStream inputStream = (this.jarEntryName.isEmpty()
? this.jarFile.getData().getInputStream(ResourceAccess.ONCE)
: this.jarFile.getInputStream(this.jarEntry));
if (inputStream == null) { if (inputStream == null) {
throwFileNotFound(this.jarEntryName, this.jarFile); throwFileNotFound(this.jarEntryName, this.jarFile);
} }

@ -28,6 +28,7 @@ import java.net.URLClassLoader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify;
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JarFileTests { public class JarFileTests {
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader"; private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";
@ -270,6 +272,12 @@ public class JarFileTests {
assertThat(conn.getJarFile()).isSameAs(nestedJarFile); assertThat(conn.getJarFile()).isSameAs(nestedJarFile);
assertThat(conn.getJarFileURL().toString()) assertThat(conn.getJarFileURL().toString())
.isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); .isEqualTo("jar:" + this.rootJarFile.toURI() + "!/nested.jar");
assertThat(conn.getInputStream()).isNotNull();
JarInputStream jarInputStream = new JarInputStream(conn.getInputStream());
assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("3.dat");
assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("4.dat");
assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("\u00E4.dat");
jarInputStream.close();
assertThat(conn.getPermission()).isInstanceOf(FilePermission.class); assertThat(conn.getPermission()).isInstanceOf(FilePermission.class);
FilePermission permission = (FilePermission) conn.getPermission(); FilePermission permission = (FilePermission) conn.getPermission();
assertThat(permission.getActions()).isEqualTo("read"); assertThat(permission.getActions()).isEqualTo("read");

@ -42,7 +42,8 @@ import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.LaunchScript;
import org.springframework.boot.loader.tools.LayoutType; import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts;
import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Repackager; import org.springframework.boot.loader.tools.Repackager;
@ -130,7 +131,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
* @since 1.0 * @since 1.0
*/ */
@Parameter @Parameter
private String layout; private LayoutType layout;
/** /**
* A list of the libraries that must be unpacked from fat jars in order to run. * A list of the libraries that must be unpacked from fat jars in order to run.
@ -227,7 +228,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
repackager.setMainClass(this.mainClass); repackager.setMainClass(this.mainClass);
if (this.layout != null) { if (this.layout != null) {
getLog().info("Layout: " + this.layout); getLog().info("Layout: " + this.layout);
repackager.setLayout(LayoutType.layout(this.layout)); repackager.setLayout(this.layout.layout());
} }
return repackager; return repackager;
} }
@ -308,6 +309,53 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
} }
} }
/**
* Archive layout types.
*/
public enum LayoutType {
/**
* Jar Layout.
*/
JAR(new Layouts.Jar()),
/**
* War Layout.
*/
WAR(new Layouts.War()),
/**
* Zip Layout.
*/
ZIP(new Layouts.Expanded()),
/**
* Dir Layout.
*/
DIR(new Layouts.Expanded()),
/**
* Module Layout.
*/
MODULE(new Layouts.Module()),
/**
* No Layout.
*/
NONE(new Layouts.None());
private final Layout layout;
public Layout layout() {
return this.layout;
}
LayoutType(Layout layout) {
this.layout = layout;
}
}
private static class LoggingRepackager extends Repackager { private static class LoggingRepackager extends Repackager {
private final Log log; private final Log log;

@ -1,57 +0,0 @@
-----
Use a custom layout
-----
Dave Syer
-----
2016-10-30
-----
Spring Boot repackages the jar file for this project using a custom
layout defined in the additional jar file, provided as a dependency
to the build plugin:
---
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<layout>CUSTOM</layout>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>custom-layout</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</dependency>
</dependencies>
...
</plugin>
...
</plugins>
...
</build>
...
</project>
---
The layout is provided as an implementation of <<LayoutFactory>>
(from spring-boot-loader-tools) listed in
<<META-INF/spring-factories>> inside the <<custom-layout>> jar.

@ -48,8 +48,6 @@ Spring Boot Maven Plugin
* {{{./examples/repackage-disable-attach.html}Local repackaged artifact}} * {{{./examples/repackage-disable-attach.html}Local repackaged artifact}}
* {{{./examples/custom-layout.html}Custom layout}}
* {{{./examples/exclude-dependency.html}Exclude a dependency}} * {{{./examples/exclude-dependency.html}Exclude a dependency}}
* {{{./examples/run-debug.html}Debug the application}} * {{{./examples/run-debug.html}Debug the application}}

@ -17,9 +17,6 @@
package org.springframework.boot.context.embedded.tomcat; package org.springframework.boot.context.embedded.tomcat;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
@ -49,18 +46,19 @@ class SkipPatternJarScanner extends StandardJarScanner {
private final JarScanner jarScanner; private final JarScanner jarScanner;
private final SkipPattern pattern; private final Set<String> patterns;
SkipPatternJarScanner(JarScanner jarScanner, Set<String> patterns) { SkipPatternJarScanner(JarScanner jarScanner, Set<String> patterns) {
Assert.notNull(jarScanner, "JarScanner must not be null"); Assert.notNull(jarScanner, "JarScanner must not be null");
Assert.notNull(jarScanner, "Patterns must not be null");
this.jarScanner = jarScanner; this.jarScanner = jarScanner;
this.pattern = (patterns == null ? new SkipPattern(defaultPatterns()) : new SkipPattern(patterns)); this.patterns = patterns;
setPatternToTomcat8SkipFilter(this.pattern); setPatternToTomcat8SkipFilter();
} }
private void setPatternToTomcat8SkipFilter(SkipPattern pattern) { private void setPatternToTomcat8SkipFilter() {
if (ClassUtils.isPresent(JAR_SCAN_FILTER_CLASS, null)) { if (ClassUtils.isPresent(JAR_SCAN_FILTER_CLASS, null)) {
new Tomcat8TldSkipSetter(this).setSkipPattern(pattern); new Tomcat8TldSkipSetter(this).setSkipPattern(this.patterns);
} }
} }
@ -73,105 +71,13 @@ class SkipPatternJarScanner extends StandardJarScanner {
Assert.notNull(scanMethod, "Unable to find scan method"); Assert.notNull(scanMethod, "Unable to find scan method");
try { try {
scanMethod.invoke(this.jarScanner, context, classloader, callback, scanMethod.invoke(this.jarScanner, context, classloader, callback,
(jarsToSkip == null ? this.pattern.asSet() : jarsToSkip)); (jarsToSkip == null ? this.patterns : jarsToSkip));
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException("Tomcat 7 reflection failed", ex); throw new IllegalStateException("Tomcat 7 reflection failed", ex);
} }
} }
/**
* Return the default skip patterns to use.
* @return the default skip patterns
*/
static Set<String> defaultPatterns() {
return new LinkedHashSet<String>(Arrays.asList(
// Same as Tomcat
"ant-*.jar",
"aspectj*.jar",
"commons-beanutils*.jar",
"commons-codec*.jar",
"commons-collections*.jar",
"commons-dbcp*.jar",
"commons-digester*.jar",
"commons-fileupload*.jar",
"commons-httpclient*.jar",
"commons-io*.jar",
"commons-lang*.jar",
"commons-logging*.jar",
"commons-math*.jar",
"commons-pool*.jar",
"geronimo-spec-jaxrpc*.jar",
"h2*.jar",
"hamcrest*.jar",
"hibernate*.jar",
"jmx*.jar",
"jmx-tools-*.jar",
"jta*.jar",
"junit-*.jar",
"httpclient*.jar",
"log4j-*.jar",
"mail*.jar",
"org.hamcrest*.jar",
"slf4j*.jar",
"tomcat-embed-core-*.jar",
"tomcat-embed-logging-*.jar",
"tomcat-jdbc-*.jar",
"tomcat-juli-*.jar",
"tools.jar",
"wsdl4j*.jar",
"xercesImpl-*.jar",
"xmlParserAPIs-*.jar",
"xml-apis-*.jar",
// Additional
"antlr-*.jar",
"aopalliance-*.jar",
"aspectjrt-*.jar",
"aspectjweaver-*.jar",
"classmate-*.jar",
"dom4j-*.jar",
"ecj-*.jar",
"ehcache-core-*.jar",
"hibernate-core-*.jar",
"hibernate-commons-annotations-*.jar",
"hibernate-entitymanager-*.jar",
"hibernate-jpa-2.1-api-*.jar",
"hibernate-validator-*.jar",
"hsqldb-*.jar",
"jackson-annotations-*.jar",
"jackson-core-*.jar",
"jackson-databind-*.jar",
"jandex-*.jar",
"javassist-*.jar",
"jboss-logging-*.jar",
"jboss-transaction-api_*.jar",
"jcl-over-slf4j-*.jar",
"jdom-*.jar",
"jul-to-slf4j-*.jar",
"log4j-over-slf4j-*.jar",
"logback-classic-*.jar",
"logback-core-*.jar",
"rome-*.jar",
"slf4j-api-*.jar",
"spring-aop-*.jar",
"spring-aspects-*.jar",
"spring-beans-*.jar",
"spring-boot-*.jar",
"spring-core-*.jar",
"spring-context-*.jar",
"spring-data-*.jar",
"spring-expression-*.jar",
"spring-jdbc-*.jar,",
"spring-orm-*.jar",
"spring-oxm-*.jar",
"spring-tx-*.jar",
"snakeyaml-*.jar",
"tomcat-embed-el-*.jar",
"validation-api-*.jar",
"xml-apis-*.jar"));
}
/** /**
* Apply this decorator the specified context. * Apply this decorator the specified context.
* @param context the context to apply to * @param context the context to apply to
@ -194,43 +100,12 @@ class SkipPatternJarScanner extends StandardJarScanner {
this.jarScanner = jarScanner; this.jarScanner = jarScanner;
} }
public void setSkipPattern(SkipPattern pattern) { public void setSkipPattern(Set<String> patterns) {
StandardJarScanFilter filter = new StandardJarScanFilter(); StandardJarScanFilter filter = new StandardJarScanFilter();
filter.setTldSkip(pattern.asCommaDelimitedString()); filter.setTldSkip(StringUtils.collectionToCommaDelimitedString(patterns));
this.jarScanner.setJarScanFilter(filter); this.jarScanner.setJarScanFilter(filter);
} }
} }
/**
* Skip patterns used by Spring Boot.
*/
private static class SkipPattern {
private Set<String> patterns = new LinkedHashSet<String>();
SkipPattern(Set<String> patterns) {
for (String pattern : patterns) {
add(pattern);
}
}
protected void add(String patterns) {
Assert.notNull(patterns, "Patterns must not be null");
if (patterns.length() > 0 && !patterns.trim().startsWith(",")) {
this.patterns.add(",");
}
this.patterns.add(patterns);
}
public String asCommaDelimitedString() {
return StringUtils.collectionToCommaDelimitedString(this.patterns);
}
public Set<String> asSet() {
return Collections.unmodifiableSet(this.patterns);
}
}
} }

@ -0,0 +1,139 @@
/*
* Copyright 2012-2016 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.context.embedded.tomcat;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* TLD Skip Patterns used by Spring Boot.
*
* @author Phillip Webb
*/
final class TldSkipPatterns {
private static final Set<String> TOMCAT;
static {
// Same as Tomcat
Set<String> patterns = new LinkedHashSet<String>();
patterns.add("ant-*.jar");
patterns.add("aspectj*.jar");
patterns.add("commons-beanutils*.jar");
patterns.add("commons-codec*.jar");
patterns.add("commons-collections*.jar");
patterns.add("commons-dbcp*.jar");
patterns.add("commons-digester*.jar");
patterns.add("commons-fileupload*.jar");
patterns.add("commons-httpclient*.jar");
patterns.add("commons-io*.jar");
patterns.add("commons-lang*.jar");
patterns.add("commons-logging*.jar");
patterns.add("commons-math*.jar");
patterns.add("commons-pool*.jar");
patterns.add("geronimo-spec-jaxrpc*.jar");
patterns.add("h2*.jar");
patterns.add("hamcrest*.jar");
patterns.add("hibernate*.jar");
patterns.add("jmx*.jar");
patterns.add("jmx-tools-*.jar");
patterns.add("jta*.jar");
patterns.add("junit-*.jar");
patterns.add("httpclient*.jar");
patterns.add("log4j-*.jar");
patterns.add("mail*.jar");
patterns.add("org.hamcrest*.jar");
patterns.add("slf4j*.jar");
patterns.add("tomcat-embed-core-*.jar");
patterns.add("tomcat-embed-logging-*.jar");
patterns.add("tomcat-jdbc-*.jar");
patterns.add("tomcat-juli-*.jar");
patterns.add("tools.jar");
patterns.add("wsdl4j*.jar");
patterns.add("xercesImpl-*.jar");
patterns.add("xmlParserAPIs-*.jar");
patterns.add("xml-apis-*.jar");
TOMCAT = Collections.unmodifiableSet(patterns);
}
private static final Set<String> ADDITIONAL;
static {
// Additional typical for Spring Boot applications
Set<String> patterns = new LinkedHashSet<String>();
patterns.add("antlr-*.jar");
patterns.add("aopalliance-*.jar");
patterns.add("aspectjrt-*.jar");
patterns.add("aspectjweaver-*.jar");
patterns.add("classmate-*.jar");
patterns.add("dom4j-*.jar");
patterns.add("ecj-*.jar");
patterns.add("ehcache-core-*.jar");
patterns.add("hibernate-core-*.jar");
patterns.add("hibernate-commons-annotations-*.jar");
patterns.add("hibernate-entitymanager-*.jar");
patterns.add("hibernate-jpa-2.1-api-*.jar");
patterns.add("hibernate-validator-*.jar");
patterns.add("hsqldb-*.jar");
patterns.add("jackson-annotations-*.jar");
patterns.add("jackson-core-*.jar");
patterns.add("jackson-databind-*.jar");
patterns.add("jandex-*.jar");
patterns.add("javassist-*.jar");
patterns.add("jboss-logging-*.jar");
patterns.add("jboss-transaction-api_*.jar");
patterns.add("jcl-over-slf4j-*.jar");
patterns.add("jdom-*.jar");
patterns.add("jul-to-slf4j-*.jar");
patterns.add("log4j-over-slf4j-*.jar");
patterns.add("logback-classic-*.jar");
patterns.add("logback-core-*.jar");
patterns.add("rome-*.jar");
patterns.add("slf4j-api-*.jar");
patterns.add("spring-aop-*.jar");
patterns.add("spring-aspects-*.jar");
patterns.add("spring-beans-*.jar");
patterns.add("spring-boot-*.jar");
patterns.add("spring-core-*.jar");
patterns.add("spring-context-*.jar");
patterns.add("spring-data-*.jar");
patterns.add("spring-expression-*.jar");
patterns.add("spring-jdbc-*.jar,");
patterns.add("spring-orm-*.jar");
patterns.add("spring-oxm-*.jar");
patterns.add("spring-tx-*.jar");
patterns.add("snakeyaml-*.jar");
patterns.add("tomcat-embed-el-*.jar");
patterns.add("validation-api-*.jar");
patterns.add("xml-apis-*.jar");
ADDITIONAL = Collections.unmodifiableSet(patterns);
}
static final Set<String> DEFAULT;
static {
Set<String> patterns = new LinkedHashSet<String>();
patterns.addAll(TOMCAT);
patterns.addAll(ADDITIONAL);
DEFAULT = Collections.unmodifiableSet(patterns);
}
private TldSkipPatterns() {
}
}

@ -127,7 +127,7 @@ public class TomcatEmbeddedServletContainerFactory
private String protocol = DEFAULT_PROTOCOL; private String protocol = DEFAULT_PROTOCOL;
private Set<String> tldSkipPatterns = new LinkedHashSet<String>( private Set<String> tldSkipPatterns = new LinkedHashSet<String>(
SkipPatternJarScanner.defaultPatterns()); TldSkipPatterns.DEFAULT);
private Charset uriEncoding = DEFAULT_CHARSET; private Charset uriEncoding = DEFAULT_CHARSET;
@ -554,23 +554,12 @@ public class TomcatEmbeddedServletContainerFactory
* A comma-separated list of jars to ignore for TLD scanning. See Tomcat's * A comma-separated list of jars to ignore for TLD scanning. See Tomcat's
* catalina.properties for typical values. Defaults to a list drawn from that source. * catalina.properties for typical values. Defaults to a list drawn from that source.
* @param tldSkip the jars to skip when scanning for TLDs etc * @param tldSkip the jars to skip when scanning for TLDs etc
* @deprecated since 1.5.0 in favor of {@link #setTldSkipPatterns(List)} * @deprecated since 1.5.0 in favor of {@link #setTldSkipPatterns(Collection)}
*/ */
@Deprecated @Deprecated
public void setTldSkip(String tldSkip) { public void setTldSkip(String tldSkip) {
Assert.notNull(tldSkip, "TldSkip must not be null"); Assert.notNull(tldSkip, "TldSkip must not be null");
setTldSkipPatterns(Arrays.asList( setTldSkipPatterns(StringUtils.commaDelimitedListToSet(tldSkip));
StringUtils.commaDelimitedListToStringArray(tldSkip)));
}
/**
* Set the patterns that match jars to ignore for TLD scanning. See Tomcat's
* catalina.properties for typical values. Defaults to a list drawn from that source.
* @param patterns the jar patterns to skip when scanning for TLDs etc
*/
public void setTldSkipPatterns(List<String> patterns) {
Assert.notNull(patterns, "patterns must not be null");
this.tldSkipPatterns = new LinkedHashSet<String>(patterns);
} }
/** /**
@ -581,13 +570,23 @@ public class TomcatEmbeddedServletContainerFactory
return this.tldSkipPatterns; return this.tldSkipPatterns;
} }
/**
* Set the patterns that match jars to ignore for TLD scanning. See Tomcat's
* catalina.properties for typical values. Defaults to a list drawn from that source.
* @param patterns the jar patterns to skip when scanning for TLDs etc
*/
public void setTldSkipPatterns(Collection<String> patterns) {
Assert.notNull(patterns, "Patterns must not be null");
this.tldSkipPatterns = new LinkedHashSet<String>(patterns);
}
/** /**
* Add patterns that match jars to ignore for TLD scanning. See Tomcat's * Add patterns that match jars to ignore for TLD scanning. See Tomcat's
* catalina.properties for typical values. * catalina.properties for typical values.
* @param patterns the additional jar patterns to skip when scanning for TLDs etc * @param patterns the additional jar patterns to skip when scanning for TLDs etc
*/ */
public void addAdditionalTldSkipPatterns(String... patterns) { public void addTldSkipPatterns(String... patterns) {
Assert.notNull(patterns, "patterns must not be null"); Assert.notNull(patterns, "Patterns must not be null");
this.tldSkipPatterns.addAll(Arrays.asList(patterns)); this.tldSkipPatterns.addAll(Arrays.asList(patterns));
} }

@ -30,6 +30,7 @@ import org.springframework.boot.ApplicationPid;
import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -49,7 +50,8 @@ import org.springframework.util.Assert;
* <p> * <p>
* Note: access to the Spring {@link Environment} is only possible when the * Note: access to the Spring {@link Environment} is only possible when the
* {@link #setTriggerEventType(Class) triggerEventType} is set to * {@link #setTriggerEventType(Class) triggerEventType} is set to
* {@link ApplicationEnvironmentPreparedEvent} or {@link ApplicationPreparedEvent}. * {@link ApplicationEnvironmentPreparedEvent}, {@link ApplicationReadyEvent}, or
* {@link ApplicationPreparedEvent}.
* *
* @author Jakub Kubrynski * @author Jakub Kubrynski
* @author Dave Syer * @author Dave Syer
@ -231,6 +233,10 @@ public class ApplicationPidFileWriter
return ((ApplicationPreparedEvent) event).getApplicationContext() return ((ApplicationPreparedEvent) event).getApplicationContext()
.getEnvironment(); .getEnvironment();
} }
if (event instanceof ApplicationReadyEvent) {
return ((ApplicationReadyEvent) event).getApplicationContext()
.getEnvironment();
}
return null; return null;
} }

@ -29,6 +29,7 @@ import org.junit.rules.TemporaryFolder;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -99,7 +100,7 @@ public class ApplicationPidFileWriterTests {
} }
@Test @Test
public void differentEventTypes() throws Exception { public void tryEnvironmentPreparedEvent() throws Exception {
File file = this.temporaryFolder.newFile(); File file = this.temporaryFolder.newFile();
SpringApplicationEvent event = createEnvironmentPreparedEvent("spring.pid.file", SpringApplicationEvent event = createEnvironmentPreparedEvent("spring.pid.file",
file.getAbsolutePath()); file.getAbsolutePath());
@ -111,6 +112,19 @@ public class ApplicationPidFileWriterTests {
assertThat(FileCopyUtils.copyToString(new FileReader(file))).isNotEmpty(); assertThat(FileCopyUtils.copyToString(new FileReader(file))).isNotEmpty();
} }
@Test
public void tryReadyEvent() throws Exception {
File file = this.temporaryFolder.newFile();
SpringApplicationEvent event = createReadyEvent("spring.pid.file",
file.getAbsolutePath());
ApplicationPidFileWriter listener = new ApplicationPidFileWriter();
listener.onApplicationEvent(event);
assertThat(FileCopyUtils.copyToString(new FileReader(file))).isEmpty();
listener.setTriggerEventType(ApplicationReadyEvent.class);
listener.onApplicationEvent(event);
assertThat(FileCopyUtils.copyToString(new FileReader(file))).isNotEmpty();
}
@Test @Test
public void withNoEnvironment() throws Exception { public void withNoEnvironment() throws Exception {
File file = this.temporaryFolder.newFile(); File file = this.temporaryFolder.newFile();
@ -170,6 +184,15 @@ public class ApplicationPidFileWriterTests {
context); context);
} }
private SpringApplicationEvent createReadyEvent(String propName, String propValue) {
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
ConfigurableApplicationContext context = mock(
ConfigurableApplicationContext.class);
given(context.getEnvironment()).willReturn(environment);
return new ApplicationReadyEvent(new SpringApplication(), new String[] {},
context);
}
private ConfigurableEnvironment createEnvironment(String propName, String propValue) { private ConfigurableEnvironment createEnvironment(String propName, String propValue) {
MockPropertySource propertySource = mockPropertySource(propName, propValue); MockPropertySource propertySource = mockPropertySource(propName, propValue);
ConfigurableEnvironment environment = new StandardEnvironment(); ConfigurableEnvironment environment = new StandardEnvironment();

Loading…
Cancel
Save