diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java index a2ff0e4994c..dde619245f5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java @@ -17,7 +17,7 @@ package org.springframework.boot.autoconfigure.jdbc; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; @@ -27,6 +27,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.config.ResourceNotFoundException; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.Resource; @@ -43,6 +44,7 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Phillip Webb * @author EddĂș MelĂ©ndez + * @author Stephane Nicoll * @since 1.1.0 * @see DataSourceAutoConfiguration */ @@ -78,7 +80,8 @@ class DataSourceInitializer implements ApplicationListener scripts = getScripts(this.properties.getSchema(), "schema"); + List scripts = getScripts("spring.datasource.schema", + this.properties.getSchema(), "schema"); if (!scripts.isEmpty()) { String username = this.properties.getSchemaUsername(); String password = this.properties.getSchemaPassword(); @@ -114,41 +117,50 @@ class DataSourceInitializer implements ApplicationListener scripts = getScripts(this.properties.getData(), "data"); + List scripts = getScripts("spring.datasource.data", + this.properties.getData(), "data"); String username = this.properties.getDataUsername(); String password = this.properties.getDataPassword(); runScripts(scripts, username, password); } - private List getScripts(String locations, String fallback) { - if (locations == null) { - String platform = this.properties.getPlatform(); - locations = "classpath*:" + fallback + "-" + platform + ".sql,"; - locations += "classpath*:" + fallback + ".sql"; + private List getScripts(String propertyName, + List resources, String fallback) { + if (resources != null) { + return getResources(propertyName, resources, true); } - return getResources(locations); + String platform = this.properties.getPlatform(); + List fallbackResources = new ArrayList(); + fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql"); + fallbackResources.add("classpath*:" + fallback + ".sql"); + return getResources(propertyName, fallbackResources, false); } - private List getResources(String locations) { - return getResources( - Arrays.asList(StringUtils.commaDelimitedListToStringArray(locations))); - } - - private List getResources(List locations) { - SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean( - this.applicationContext, locations); - try { - factory.afterPropertiesSet(); - List resources = new ArrayList(); - for (Resource resource : factory.getObject()) { + private List getResources(String propertyName, + List locations, boolean validate) { + List resources = new ArrayList(); + for (String location : locations) { + for (Resource resource : doGetResources(location)) { if (resource.exists()) { resources.add(resource); } + else if (validate) { + throw new ResourceNotFoundException(propertyName, resource); + } } - return resources; + } + return resources; + } + + private Resource[] doGetResources(String location) { + try { + SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean( + this.applicationContext, Collections.singletonList(location)); + factory.afterPropertiesSet(); + return factory.getObject(); } catch (Exception ex) { - throw new IllegalStateException("Unable to load resources from " + locations, + throw new IllegalStateException("Unable to load resources from " + location, ex); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index f7834620e37..6b79062f894 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.jdbc; import java.nio.charset.Charset; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -106,9 +107,9 @@ public class DataSourceProperties private String platform = "all"; /** - * Schema (DDL) script resource reference. + * Schema (DDL) script resource references. */ - private String schema; + private List schema; /** * User of the database to execute DDL scripts (if different). @@ -121,9 +122,9 @@ public class DataSourceProperties private String schemaPassword; /** - * Data (DML) script resource reference. + * Data (DML) script resource references. */ - private String data; + private List data; /** * User of the database to execute DML scripts. @@ -388,11 +389,11 @@ public class DataSourceProperties this.platform = platform; } - public String getSchema() { + public List getSchema() { return this.schema; } - public void setSchema(String schema) { + public void setSchema(List schema) { this.schema = schema; } @@ -412,12 +413,12 @@ public class DataSourceProperties this.schemaPassword = schemaPassword; } - public String getData() { + public List getData() { return this.data; } - public void setData(String script) { - this.data = script; + public void setData(List data) { + this.data = data; } public String getDataUsername() { diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index fe52f44404e..da5955848ef 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -368,6 +368,17 @@ } ] }, + { + "name": "spring.datasource.data", + "providers": [ + { + "name": "handle-as", + "parameters": { + "target": "java.util.List" + } + } + ] + }, { "name": "spring.datasource.driver-class-name", "providers": [ @@ -379,6 +390,17 @@ } ] }, + { + "name": "spring.datasource.schema", + "providers": [ + { + "name": "handle-as", + "parameters": { + "target": "java.util.List" + } + } + ] + }, { "name": "spring.datasource.xa.data-source-class-name", "providers": [ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java index 7d14b042f01..99563aaf57f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java @@ -26,7 +26,9 @@ import javax.sql.DataSource; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; @@ -54,9 +56,13 @@ import static org.junit.Assert.fail; * Tests for {@link DataSourceInitializer}. * * @author Dave Syer + * @author Stephane Nicoll */ public class DataSourceInitializerTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @Before @@ -271,6 +277,37 @@ public class DataSourceInitializerTests { .isEqualTo(1); } + @Test + public void testDataSourceInitializedWithInvalidSchemaResource() { + this.context.register(DataSourceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:true", + "spring.datasource.schema:classpath:does/not/exist.sql"); + + this.thrown.expect(BeanCreationException.class); + this.thrown.expectMessage("does/not/exist.sql"); + this.thrown.expectMessage("spring.datasource.schema"); + this.context.refresh(); + } + + @Test + public void testDataSourceInitializedWithInvalidDataResource() { + this.context.register(DataSourceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:true", + "spring.datasource.schema:" + + ClassUtils.addResourcePathToPackagePath(getClass(), + "schema.sql"), + "spring.datasource.data:classpath:does/not/exist.sql"); + + this.thrown.expect(BeanCreationException.class); + this.thrown.expectMessage("does/not/exist.sql"); + this.thrown.expectMessage("spring.datasource.data"); + this.context.refresh(); + } + @Configuration @EnableConfigurationProperties protected static class TwoDataSources { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index e78f4017acd..1c2b4798acc 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -27,7 +27,9 @@ import javax.transaction.UserTransaction; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.junit.After; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; @@ -50,6 +52,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @After public void cleanup() { HibernateVersion.setRunning(null); @@ -67,9 +72,10 @@ public class HibernateJpaAutoConfigurationTests // Missing: "spring.datasource.schema:classpath:/ddl.sql"); setupTestConfiguration(); + this.thrown.expectMessage("ddl.sql"); + this.thrown.expectMessage("spring.datasource.schema"); this.context.refresh(); - assertThat(new JdbcTemplate(this.context.getBean(DataSource.class)) - .queryForObject("SELECT COUNT(*) from CITY", Integer.class)).isEqualTo(1); + } // This can't succeed because the data SQL is executed immediately after the schema diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index c719953b9f4..1521e2077bd 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -603,7 +603,7 @@ content into your application; rather pick only the properties that you need. # DATASOURCE ({sc-spring-boot-autoconfigure}/jdbc/DataSourceAutoConfiguration.{sc-ext}[DataSourceAutoConfiguration] & {sc-spring-boot-autoconfigure}/jdbc/DataSourceProperties.{sc-ext}[DataSourceProperties]) spring.datasource.continue-on-error=false # Do not stop if an error occurs while initializing the database. - spring.datasource.data= # Data (DML) script resource reference. + spring.datasource.data= # Data (DML) script resource references. spring.datasource.data-username= # User of the database to execute DML scripts (if different). spring.datasource.data-password= # Password of the database to execute DML scripts (if different). spring.datasource.dbcp2.*= # Commons DBCP2 specific settings @@ -616,7 +616,7 @@ content into your application; rather pick only the properties that you need. spring.datasource.name=testdb # Name of the datasource. spring.datasource.password= # Login password of the database. spring.datasource.platform=all # Platform to use in the schema resource (schema-${platform}.sql). - spring.datasource.schema= # Schema (DDL) script resource reference. + spring.datasource.schema= # Schema (DDL) script resource references. spring.datasource.schema-username= # User of the database to execute DDL scripts (if different). spring.datasource.schema-password= # Password of the database to execute DDL scripts (if different). spring.datasource.separator=; # Statement separator in SQL initialization scripts. diff --git a/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceNotFoundException.java b/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceNotFoundException.java new file mode 100644 index 00000000000..29b68ca8fb4 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceNotFoundException.java @@ -0,0 +1,56 @@ +/* + * 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.config; + +import org.springframework.core.io.Resource; + +/** + * Exception thrown when a {@link Resource} defined by a property is not found. + * + * @author Stephane Nicoll + * @since 1.5.0 + */ +@SuppressWarnings("serial") +public class ResourceNotFoundException extends RuntimeException { + + private final String propertyName; + + private final Resource resource; + + public ResourceNotFoundException(String propertyName, Resource resource) { + super(String.format("%s defined by '%s' does not exist", resource, propertyName)); + this.propertyName = propertyName; + this.resource = resource; + } + + /** + * Return the name of the property that defines the resource. + * @return the property + */ + public String getPropertyName() { + return this.propertyName; + } + + /** + * Return the {@link Resource}. + * @return the resource + */ + public Resource getResource() { + return this.resource; + } + +}