This commit is contained in:
Phillip Webb 2013-07-11 11:21:34 -07:00
parent 6dbd6d7c4c
commit 7d0c0fc0dd
11 changed files with 167 additions and 79 deletions

View File

@ -16,10 +16,6 @@
package org.springframework.autoconfigure.orm.jpa; package org.springframework.autoconfigure.orm.jpa;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.junit.After; import org.junit.After;
@ -40,6 +36,10 @@ import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* Tests for {@link HibernateJpaAutoConfiguration}. * Tests for {@link HibernateJpaAutoConfiguration}.
* *
@ -51,14 +51,15 @@ public class HibernateJpaAutoConfigurationTests {
@After @After
public void close() { public void close() {
if (context != null) { if (this.context != null) {
context.close(); this.context.close();
} }
} }
@Test @Test
public void testEntityManagerCreated() throws Exception { public void testEntityManagerCreated() throws Exception {
((AnnotationConfigApplicationContext) this.context).register(ComponentScanDetectorConfiguration.class, ((AnnotationConfigApplicationContext) this.context).register(
ComponentScanDetectorConfiguration.class,
EmbeddedDatabaseConfiguration.class, HibernateJpaAutoConfiguration.class, EmbeddedDatabaseConfiguration.class, HibernateJpaAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class); PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class);
this.context.refresh(); this.context.refresh();
@ -68,10 +69,11 @@ public class HibernateJpaAutoConfigurationTests {
@Test @Test
public void testDataSourceTransactionManagerNotCreated() throws Exception { public void testDataSourceTransactionManagerNotCreated() throws Exception {
((AnnotationConfigApplicationContext) this.context).register(ComponentScanDetectorConfiguration.class, ((AnnotationConfigApplicationContext) this.context).register(
ComponentScanDetectorConfiguration.class,
EmbeddedDatabaseConfiguration.class, HibernateJpaAutoConfiguration.class, EmbeddedDatabaseConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class,
TestConfiguration.class); PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class);
this.context.refresh(); this.context.refresh();
assertNotNull(this.context.getBean(DataSource.class)); assertNotNull(this.context.getBean(DataSource.class));
assertTrue(this.context.getBean("transactionManager") instanceof JpaTransactionManager); assertTrue(this.context.getBean("transactionManager") instanceof JpaTransactionManager);
@ -80,33 +82,44 @@ public class HibernateJpaAutoConfigurationTests {
@Test @Test
public void testOpenEntityManagerInViewInterceptorCreated() throws Exception { public void testOpenEntityManagerInViewInterceptorCreated() throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(ComponentScanDetectorConfiguration.class, EmbeddedDatabaseConfiguration.class, context.register(ComponentScanDetectorConfiguration.class,
HibernateJpaAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedDatabaseConfiguration.class, HibernateJpaAutoConfiguration.class,
TestConfiguration.class); PropertyPlaceholderAutoConfiguration.class, TestConfiguration.class);
this.context = context; this.context = context;
this.context.refresh(); this.context.refresh();
assertNotNull(this.context.getBean(OpenEntityManagerInViewInterceptor.class)); assertNotNull(this.context.getBean(OpenEntityManagerInViewInterceptor.class));
} }
@Test @Test
public void testOpenEntityManagerInViewInterceptorNotRegisteredWhenFilterPresent() throws Exception { public void testOpenEntityManagerInViewInterceptorNotRegisteredWhenFilterPresent()
throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(TestFilterConfiguration.class, ComponentScanDetectorConfiguration.class, EmbeddedDatabaseConfiguration.class, context.register(ComponentScanDetectorConfiguration.class,
HibernateJpaAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); EmbeddedDatabaseConfiguration.class, HibernateJpaAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, TestFilterConfiguration.class);
this.context = context; this.context = context;
this.context.refresh(); this.context.refresh();
assertEquals(0, this.context.getBeanNamesForType(OpenEntityManagerInViewInterceptor.class).length); assertEquals(
0,
this.context
.getBeanNamesForType(OpenEntityManagerInViewInterceptor.class).length);
} }
@Test @Test
public void testOpenEntityManagerInViewInterceptorNotRegisteredWhenExplicitlyOff() throws Exception { public void testOpenEntityManagerInViewInterceptorNotRegisteredWhenExplicitlyOff()
throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
TestUtils.addEnviroment(context, "spring.jpa.open_in_view:false"); TestUtils.addEnviroment(context, "spring.jpa.open_in_view:false");
context.register(TestConfiguration.class, ComponentScanDetectorConfiguration.class, EmbeddedDatabaseConfiguration.class, context.register(TestConfiguration.class,
HibernateJpaAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); ComponentScanDetectorConfiguration.class,
EmbeddedDatabaseConfiguration.class, HibernateJpaAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context = context; this.context = context;
this.context.refresh(); this.context.refresh();
assertEquals(0, this.context.getBeanNamesForType(OpenEntityManagerInViewInterceptor.class).length); assertEquals(
0,
this.context
.getBeanNamesForType(OpenEntityManagerInViewInterceptor.class).length);
} }
@ComponentScan(basePackageClasses = { City.class }) @ComponentScan(basePackageClasses = { City.class })

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.config; package org.springframework.bootstrap.config;
import java.util.Properties; import java.util.Properties;
@ -26,6 +27,8 @@ import org.springframework.util.StringUtils;
* Matches a document containing a given key and where the value of that key is an array * Matches a document containing a given key and where the value of that key is an array
* containing one of the given values, or where one of the values matches one of the given * containing one of the given values, or where one of the values matches one of the given
* values (interpreted as regexes). * values (interpreted as regexes).
*
* @author Dave Syer
*/ */
public class ArrayDocumentMatcher implements DocumentMatcher { public class ArrayDocumentMatcher implements DocumentMatcher {

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.config; package org.springframework.bootstrap.config;
import java.util.Properties; import java.util.Properties;
@ -25,7 +26,6 @@ import org.springframework.bootstrap.config.YamlProcessor.MatchStatus;
* explicitly (i.e. matches if "spring.profiles" is not found and not otherwise). * explicitly (i.e. matches if "spring.profiles" is not found and not otherwise).
* *
* @author Dave Syer * @author Dave Syer
*
*/ */
public final class DefaultProfileDocumentMatcher implements DocumentMatcher { public final class DefaultProfileDocumentMatcher implements DocumentMatcher {
@ -33,7 +33,8 @@ public final class DefaultProfileDocumentMatcher implements DocumentMatcher {
public MatchStatus matches(Properties properties) { public MatchStatus matches(Properties properties) {
if (!properties.containsKey("spring.profiles")) { if (!properties.containsKey("spring.profiles")) {
return MatchStatus.FOUND; return MatchStatus.FOUND;
} else { }
else {
return MatchStatus.NOT_FOUND; return MatchStatus.NOT_FOUND;
} }
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.config; package org.springframework.bootstrap.config;
import java.io.IOException; import java.io.IOException;
@ -26,6 +27,8 @@ import org.springframework.core.io.support.PropertiesLoaderUtils;
/** /**
* Strategy to load '.properties' files into a {@link PropertySource}. * Strategy to load '.properties' files into a {@link PropertySource}.
*
* @author Dave Syer
*/ */
public class PropertiesPropertySourceLoader implements PropertySourceLoader { public class PropertiesPropertySourceLoader implements PropertySourceLoader {
@ -39,7 +42,8 @@ public class PropertiesPropertySourceLoader implements PropertySourceLoader {
try { try {
Properties properties = loadProperties(resource, environment); Properties properties = loadProperties(resource, environment);
return new PropertiesPropertySource(resource.getDescription(), properties); return new PropertiesPropertySource(resource.getDescription(), properties);
} catch (IOException ex) { }
catch (IOException ex) {
throw new IllegalStateException("Could not load properties from " + resource, throw new IllegalStateException("Could not load properties from " + resource,
ex); ex);
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.config; package org.springframework.bootstrap.config;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -21,11 +22,14 @@ import org.springframework.core.io.Resource;
/** /**
* Strategy interface used to load a {@link PropertySource}. * Strategy interface used to load a {@link PropertySource}.
*
* @author Dave Syer
*/ */
public interface PropertySourceLoader { public interface PropertySourceLoader {
/** /**
* @return Is this resource supported? * Returns {@code true} if the {@link Resource} is supported.
* @return if the resource is supported
*/ */
public boolean supports(Resource resource); public boolean supports(Resource resource);

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.config; package org.springframework.bootstrap.config;
import java.util.Properties; import java.util.Properties;
@ -20,19 +21,25 @@ import java.util.Properties;
import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher; import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher;
import org.springframework.bootstrap.config.YamlProcessor.MatchStatus; import org.springframework.bootstrap.config.YamlProcessor.MatchStatus;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/** /**
* @author Dave Syer * {@link DocumentMatcher} backed by {@link Environment#getActiveProfiles()}.
* *
* @author Dave Syer
*/ */
public class SpringProfileDocumentMatcher implements DocumentMatcher { public class SpringProfileDocumentMatcher implements DocumentMatcher {
private static final String[] DEFAULT_PROFILES = new String[] { "default" };
private final Environment environment; private final Environment environment;
/** /**
* @param environment * Create a new {@link SpringProfileDocumentMatcher} instance.
* @param environment the environment
*/ */
public SpringProfileDocumentMatcher(Environment environment) { public SpringProfileDocumentMatcher(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment; this.environment = environment;
} }
@ -40,7 +47,7 @@ public class SpringProfileDocumentMatcher implements DocumentMatcher {
public MatchStatus matches(Properties properties) { public MatchStatus matches(Properties properties) {
String[] profiles = this.environment.getActiveProfiles(); String[] profiles = this.environment.getActiveProfiles();
if (profiles.length == 0) { if (profiles.length == 0) {
profiles = new String[] { "default" }; profiles = DEFAULT_PROFILES;
} }
return new ArrayDocumentMatcher("spring.profiles", profiles).matches(properties); return new ArrayDocumentMatcher("spring.profiles", profiles).matches(properties);
} }

View File

@ -76,13 +76,12 @@ public class YamlMapFactoryBean extends YamlProcessor implements
public Map<String, Object> getObject() { public Map<String, Object> getObject() {
if (!this.singleton || this.instance == null) { if (!this.singleton || this.instance == null) {
final Map<String, Object> result = new LinkedHashMap<String, Object>(); final Map<String, Object> result = new LinkedHashMap<String, Object>();
MatchCallback callback = new MatchCallback() { process(new MatchCallback() {
@Override @Override
public void process(Properties properties, Map<String, Object> map) { public void process(Properties properties, Map<String, Object> map) {
merge(result, map); merge(result, map);
} }
}; });
process(callback);
this.instance = result; this.instance = result;
} }
return this.instance; return this.instance;

View File

@ -27,6 +27,7 @@ import java.util.Properties;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
@ -92,17 +93,13 @@ public class YamlProcessor {
/** /**
* Method to use for resolving resources. Each resource will be converted to a Map, so * Method to use for resolving resources. Each resource will be converted to a Map, so
* this property is used to decide which map entries to keep in the final output from * this property is used to decide which map entries to keep in the final output from
* this factory. Possible values: * this factory.
* <ul>
* <li><code>OVERRIDE</code> for replacing values from earlier in the list</li>
* <li><code>FIRST_FOUND</code> if you want to take the first resource in the list
* that exists and use just that.</li>
* </ul>
* *
* * @param resolutionMethod the resolution method to set (defaults to
* @param resolutionMethod the resolution method to set. Defaults to OVERRIDE. * {@link ResolutionMethod#OVERRIDE}).
*/ */
public void setResolutionMethod(ResolutionMethod resolutionMethod) { public void setResolutionMethod(ResolutionMethod resolutionMethod) {
Assert.notNull(resolutionMethod, "ResolutionMethod must not be null");
this.resolutionMethod = resolutionMethod; this.resolutionMethod = resolutionMethod;
} }
@ -147,7 +144,8 @@ public class YamlProcessor {
} }
this.logger.info("Loaded " + count + " document" + (count > 1 ? "s" : "") this.logger.info("Loaded " + count + " document" + (count > 1 ? "s" : "")
+ " from YAML resource: " + resource); + " from YAML resource: " + resource);
} catch (IOException ex) { }
catch (IOException ex) {
handleProcessError(resource, ex); handleProcessError(resource, ex);
} }
return count > 0; return count > 0;
@ -175,12 +173,13 @@ public class YamlProcessor {
if (this.documentMatchers.isEmpty()) { if (this.documentMatchers.isEmpty()) {
this.logger.debug("Merging document (no matchers set)" + map); this.logger.debug("Merging document (no matchers set)" + map);
callback.process(properties, map); callback.process(properties, map);
} else { }
else {
boolean valueFound = false; boolean valueFound = false;
MatchStatus result = MatchStatus.ABSTAIN; MatchStatus result = MatchStatus.ABSTAIN;
for (DocumentMatcher matcher : this.documentMatchers) { for (DocumentMatcher matcher : this.documentMatchers) {
MatchStatus match = matcher.matches(properties); MatchStatus match = matcher.matches(properties);
result = match.ordinal() < result.ordinal() ? match : result; result = MatchStatus.getMostSpecific(match, result);
if (match == MatchStatus.FOUND) { if (match == MatchStatus.FOUND) {
this.logger.debug("Matched document with document matcher: " this.logger.debug("Matched document with document matcher: "
+ properties); + properties);
@ -193,7 +192,8 @@ public class YamlProcessor {
if (result == MatchStatus.ABSTAIN && this.matchDefault) { if (result == MatchStatus.ABSTAIN && this.matchDefault) {
this.logger.debug("Matched document with default matcher: " + map); this.logger.debug("Matched document with default matcher: " + map);
callback.process(properties, map); callback.process(properties, map);
} else if (!valueFound) { }
else if (!valueFound) {
this.logger.debug("Unmatched document"); this.logger.debug("Unmatched document");
return false; return false;
} }
@ -208,19 +208,22 @@ public class YamlProcessor {
if (StringUtils.hasText(path)) { if (StringUtils.hasText(path)) {
if (key.startsWith("[")) { if (key.startsWith("[")) {
key = path + key; key = path + key;
} else { }
else {
key = path + "." + key; key = path + "." + key;
} }
} }
Object value = entry.getValue(); Object value = entry.getValue();
if (value instanceof String) { if (value instanceof String) {
properties.put(key, value); properties.put(key, value);
} else if (value instanceof Map) { }
else if (value instanceof Map) {
// Need a compound key // Need a compound key
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value; Map<String, Object> map = (Map<String, Object>) value;
assignProperties(properties, map, key); assignProperties(properties, map, key);
} else if (value instanceof Collection) { }
else if (value instanceof Collection) {
// Need a compound key // Need a compound key
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) value; Collection<Object> collection = (Collection<Object>) value;
@ -229,26 +232,74 @@ public class YamlProcessor {
assignProperties(properties, assignProperties(properties,
Collections.singletonMap("[" + (count++) + "]", object), key); Collections.singletonMap("[" + (count++) + "]", object), key);
} }
} else { }
else {
properties.put(key, value == null ? "" : value); properties.put(key, value == null ? "" : value);
} }
} }
} }
/**
* Callback interface used to process properties in a resulting map.
*/
public interface MatchCallback { public interface MatchCallback {
/**
* Process the properties.
* @param properties the properties to process
* @param map a mutable result map
*/
void process(Properties properties, Map<String, Object> map); void process(Properties properties, Map<String, Object> map);
} }
/**
* Strategy interface used the test if properties match.
*/
public interface DocumentMatcher { public interface DocumentMatcher {
/**
* Test if the given properties match.
* @param properties the properties to test
* @return the status of the match.
*/
MatchStatus matches(Properties properties); MatchStatus matches(Properties properties);
} }
public static enum ResolutionMethod { /**
OVERRIDE, OVERRIDE_AND_IGNORE, FIRST_FOUND * Status returned from {@link DocumentMatcher#matches(Properties)}
} */
public static enum MatchStatus { public static enum MatchStatus {
FOUND, NOT_FOUND, ABSTAIN FOUND, NOT_FOUND, ABSTAIN;
/**
* Compare two {@link MatchStatus} items, returning the most specific status.
*/
public static MatchStatus getMostSpecific(MatchStatus a, MatchStatus b) {
return a.ordinal() < b.ordinal() ? a : b;
}
}
/**
* Resolution methods.
*/
public static enum ResolutionMethod {
/**
* Replace values from earlier in the list.
*/
OVERRIDE,
/**
* Replace values from earlier in the list, ignoring any failures.
*/
OVERRIDE_AND_IGNORE,
/**
* Take the first resource in the list that exists and use just that.
*/
FIRST_FOUND
} }
} }

View File

@ -77,13 +77,12 @@ public class YamlPropertiesFactoryBean extends YamlProcessor implements
public Properties getObject() { public Properties getObject() {
if (!this.singleton || this.instance == null) { if (!this.singleton || this.instance == null) {
final Properties result = new Properties(); final Properties result = new Properties();
MatchCallback callback = new MatchCallback() { process(new MatchCallback() {
@Override @Override
public void process(Properties properties, Map<String, Object> map) { public void process(Properties properties, Map<String, Object> map) {
result.putAll(properties); result.putAll(properties);
} }
}; });
process(callback);
this.instance = result; this.instance = result;
} }
return this.instance; return this.instance;

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.config; package org.springframework.bootstrap.config;
import java.io.IOException; import java.io.IOException;
@ -27,35 +28,16 @@ import org.springframework.core.io.Resource;
/** /**
* Strategy to load '.yml' files into a {@link PropertySource}. * Strategy to load '.yml' files into a {@link PropertySource}.
*
* @author Dave Syer
*/ */
public class YamlPropertySourceLoader extends PropertiesPropertySourceLoader { public class YamlPropertySourceLoader extends PropertiesPropertySourceLoader {
private List<DocumentMatcher> matchers; private List<DocumentMatcher> matchers;
/** /**
* A property source loader that loads all properties and matches all documents. * Create a {@link YamlPropertySourceLoader} instance with the specified matchers.
* * @param matchers the document matchers
* @return a property source loader
*/
public static YamlPropertySourceLoader matchAllLoader() {
return new YamlPropertySourceLoader();
}
/**
* A property source loader that matches documents that have no explicit profile or
* which have an explicit "spring.profiles.active" value in the current active
* profiles.
*
* @return a property source loader
*/
public static YamlPropertySourceLoader springProfileAwareLoader(
Environment environment) {
return new YamlPropertySourceLoader(new SpringProfileDocumentMatcher(environment),
new DefaultProfileDocumentMatcher());
}
/**
* @param matchers
*/ */
public YamlPropertySourceLoader(DocumentMatcher... matchers) { public YamlPropertySourceLoader(DocumentMatcher... matchers) {
this.matchers = Arrays.asList(matchers); this.matchers = Arrays.asList(matchers);
@ -78,4 +60,27 @@ public class YamlPropertySourceLoader extends PropertiesPropertySourceLoader {
return factory.getObject(); return factory.getObject();
} }
/**
* A property source loader that loads all properties and matches all documents.
*
* @return a property source loader
*/
public static YamlPropertySourceLoader matchAllLoader() {
return new YamlPropertySourceLoader();
}
/**
* A property source loader that matches documents that have no explicit profile or
* which have an explicit "spring.profiles.active" value in the current active
* profiles.
*
* @return a property source loader
*/
public static YamlPropertySourceLoader springProfileAwareLoader(
Environment environment) {
return new YamlPropertySourceLoader(
new SpringProfileDocumentMatcher(environment),
new DefaultProfileDocumentMatcher());
}
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.config; package org.springframework.bootstrap.config;
import java.io.IOException; import java.io.IOException;
@ -26,8 +27,9 @@ import org.springframework.core.io.support.PropertiesLoaderUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
* @author Dave Syer * Tests for {@link ArrayDocumentMatcher}.
* *
* @author Dave Syer
*/ */
public class ArrayDocumentMatcherTests { public class ArrayDocumentMatcherTests {