Validate schema and data resources
Previously, if a user specifies a path to a schema or data DDL that does not exist, the application will start up fine and the missing DDL would not be reported. This commit validates that user-defined resources actually exist and throw a new `ResourceNotFoundException` if they don't. Closes gh-7088
This commit is contained in:
parent
3ac22e7cdf
commit
2c630b5c61
|
@ -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<DataSourceInitialized
|
|||
}
|
||||
|
||||
private void runSchemaScripts() {
|
||||
List<Resource> scripts = getScripts(this.properties.getSchema(), "schema");
|
||||
List<Resource> 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<DataSourceInitialized
|
|||
}
|
||||
|
||||
private void runDataScripts() {
|
||||
List<Resource> scripts = getScripts(this.properties.getData(), "data");
|
||||
List<Resource> 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<Resource> getScripts(String locations, String fallback) {
|
||||
if (locations == null) {
|
||||
String platform = this.properties.getPlatform();
|
||||
locations = "classpath*:" + fallback + "-" + platform + ".sql,";
|
||||
locations += "classpath*:" + fallback + ".sql";
|
||||
private List<Resource> getScripts(String propertyName,
|
||||
List<String> resources, String fallback) {
|
||||
if (resources != null) {
|
||||
return getResources(propertyName, resources, true);
|
||||
}
|
||||
return getResources(locations);
|
||||
String platform = this.properties.getPlatform();
|
||||
List<String> fallbackResources = new ArrayList<String>();
|
||||
fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
|
||||
fallbackResources.add("classpath*:" + fallback + ".sql");
|
||||
return getResources(propertyName, fallbackResources, false);
|
||||
}
|
||||
|
||||
private List<Resource> getResources(String locations) {
|
||||
return getResources(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray(locations)));
|
||||
}
|
||||
|
||||
private List<Resource> getResources(List<String> locations) {
|
||||
SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(
|
||||
this.applicationContext, locations);
|
||||
try {
|
||||
factory.afterPropertiesSet();
|
||||
List<Resource> resources = new ArrayList<Resource>();
|
||||
for (Resource resource : factory.getObject()) {
|
||||
private List<Resource> getResources(String propertyName,
|
||||
List<String> locations, boolean validate) {
|
||||
List<Resource> resources = new ArrayList<Resource>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> data;
|
||||
|
||||
/**
|
||||
* User of the database to execute DML scripts.
|
||||
|
@ -388,11 +389,11 @@ public class DataSourceProperties
|
|||
this.platform = platform;
|
||||
}
|
||||
|
||||
public String getSchema() {
|
||||
public List<String> getSchema() {
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
public void setSchema(String schema) {
|
||||
public void setSchema(List<String> schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
|
@ -412,12 +413,12 @@ public class DataSourceProperties
|
|||
this.schemaPassword = schemaPassword;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
public List<String> getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public void setData(String script) {
|
||||
this.data = script;
|
||||
public void setData(List<String> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getDataUsername() {
|
||||
|
|
|
@ -368,6 +368,17 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.data",
|
||||
"providers": [
|
||||
{
|
||||
"name": "handle-as",
|
||||
"parameters": {
|
||||
"target": "java.util.List<org.springframework.core.io.Resource>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.driver-class-name",
|
||||
"providers": [
|
||||
|
@ -379,6 +390,17 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.schema",
|
||||
"providers": [
|
||||
{
|
||||
"name": "handle-as",
|
||||
"parameters": {
|
||||
"target": "java.util.List<org.springframework.core.io.Resource>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.xa.data-source-class-name",
|
||||
"providers": [
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue