This commit is contained in:
Phillip Webb 2014-12-01 19:32:05 -08:00
parent 237defaf18
commit 48db5457f1
39 changed files with 517 additions and 407 deletions

View File

@ -105,16 +105,22 @@ public class EndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public HealthEndpoint healthEndpoint() {
// The default sensitivity depends on whether all the endpoints by default are
// secure or not. User can always override with endpoints.health.sensitive.
boolean secure = this.management != null && this.management.getSecurity() != null
&& this.management.getSecurity().isEnabled();
HealthEndpoint endpoint = new HealthEndpoint(this.healthAggregator,
this.healthIndicators);
endpoint.setSensitive(secure);
endpoint.setSensitive(isHealthEndpointSensitive());
return endpoint;
}
/**
* The default health endpoint sensitivity depends on whether all the endpoints by
* default are secure or not. User can always override with
* {@literal endpoints.health.sensitive}.
*/
private boolean isHealthEndpointSensitive() {
return (this.management != null) && (this.management.getSecurity() != null)
&& this.management.getSecurity().isEnabled();
}
@Bean
@ConditionalOnMissingBean
public BeansEndpoint beansEndpoint() {

View File

@ -224,7 +224,6 @@ public class ManagementSecurityAutoConfiguration {
@Override
protected void configure(HttpSecurity http) throws Exception {
// secure endpoints
String[] paths = getEndpointPaths(this.endpointHandlerMapping);
if (paths.length > 0 && this.management.getSecurity().isEnabled()) {
@ -237,27 +236,15 @@ public class ManagementSecurityAutoConfiguration {
http.requestMatchers().antMatchers(paths);
String[] endpointPaths = this.server.getPathsArray(getEndpointPaths(
this.endpointHandlerMapping, false));
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http
.authorizeRequests();
authorizeRequests.antMatchers(endpointPaths).permitAll();
if (this.endpointHandlerMapping != null) {
authorizeRequests.requestMatchers(
new PrincipalHandlerRequestMatcher()).permitAll();
}
authorizeRequests.anyRequest().hasRole(
this.management.getSecurity().getRole());
configureAuthorizeRequests(endpointPaths, http.authorizeRequests());
http.httpBasic();
// No cookies for management endpoints by default
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(
this.management.getSecurity().getSessions());
SpringBootWebSecurityConfiguration.configureHeaders(http.headers(),
this.security.getHeaders());
}
}
private AuthenticationEntryPoint entryPoint() {
@ -266,12 +253,25 @@ public class ManagementSecurityAutoConfiguration {
return entryPoint;
}
private void configureAuthorizeRequests(
String[] endpointPaths,
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry requests) {
requests.antMatchers(endpointPaths).permitAll();
if (this.endpointHandlerMapping != null) {
requests.requestMatchers(new PrincipalHandlerRequestMatcher())
.permitAll();
}
requests.anyRequest().hasRole(this.management.getSecurity().getRole());
}
private final class PrincipalHandlerRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
return ManagementWebSecurityConfigurerAdapter.this.endpointHandlerMapping
.isPrincipalHandler(request);
}
}
}
@ -287,7 +287,6 @@ public class ManagementSecurityAutoConfiguration {
if (endpointHandlerMapping == null) {
return NO_PATHS;
}
Set<? extends MvcEndpoint> endpoints = endpointHandlerMapping.getEndpoints();
List<String> paths = new ArrayList<String>(endpoints.size());
for (MvcEndpoint endpoint : endpoints) {

View File

@ -119,21 +119,17 @@ public class ConfigurationPropertiesReportEndpoint extends
* {@link Map}.
*/
protected Map<String, Object> extract(ApplicationContext context) {
Map<String, Object> result = new HashMap<String, Object>();
Map<String, Object> beans = new HashMap<String, Object>(
context.getBeansWithAnnotation(ConfigurationProperties.class));
ConfigurationBeanFactoryMetaData beanFactoryMetaData = null;
if (context.getBeanNamesForType(ConfigurationBeanFactoryMetaData.class).length == 1) {
beanFactoryMetaData = context.getBean(ConfigurationBeanFactoryMetaData.class);
beans.putAll(beanFactoryMetaData
.getBeansWithFactoryAnnotation(ConfigurationProperties.class));
}
// Serialize beans into map structure and sanitize values
ObjectMapper mapper = new ObjectMapper();
configureObjectMapper(mapper);
return extract(context, mapper);
}
private Map<String, Object> extract(ApplicationContext context, ObjectMapper mapper) {
Map<String, Object> result = new HashMap<String, Object>();
ConfigurationBeanFactoryMetaData beanFactoryMetaData = getBeanFactoryMetaData(context);
Map<String, Object> beans = getConfigurationPropertiesBeans(context,
beanFactoryMetaData);
for (Map.Entry<String, Object> entry : beans.entrySet()) {
String beanName = entry.getKey();
Object bean = entry.getValue();
@ -143,14 +139,34 @@ public class ConfigurationPropertiesReportEndpoint extends
root.put("properties", sanitize(safeSerialize(mapper, bean, prefix)));
result.put(beanName, root);
}
if (context.getParent() != null) {
result.put("parent", extract(context.getParent()));
result.put("parent", extract(context.getParent(), mapper));
}
return result;
}
private ConfigurationBeanFactoryMetaData getBeanFactoryMetaData(
ApplicationContext context) {
Map<String, ConfigurationBeanFactoryMetaData> beans = context
.getBeansOfType(ConfigurationBeanFactoryMetaData.class);
if (beans.size() == 1) {
return beans.values().iterator().next();
}
return null;
}
private Map<String, Object> getConfigurationPropertiesBeans(
ApplicationContext context,
ConfigurationBeanFactoryMetaData beanFactoryMetaData) {
Map<String, Object> beans = new HashMap<String, Object>();
beans.putAll(context.getBeansWithAnnotation(ConfigurationProperties.class));
if (beanFactoryMetaData != null) {
beans.putAll(beanFactoryMetaData
.getBeansWithFactoryAnnotation(ConfigurationProperties.class));
}
return beans;
}
/**
* Cautiously serialize the bean to a map (returning a map with an error message
* instead of throwing an exception if there is a problem).
@ -166,7 +182,7 @@ public class ConfigurationPropertiesReportEndpoint extends
this.metadata.extractMap(bean, prefix), Map.class));
return result;
}
catch (Exception e) {
catch (Exception ex) {
return new HashMap<String, Object>(Collections.<String, Object> singletonMap(
"error", "Cannot serialize '" + prefix + "'"));
}

View File

@ -48,10 +48,8 @@ public class HealthEndpoint extends AbstractEndpoint<Health> {
public HealthEndpoint(HealthAggregator healthAggregator,
Map<String, HealthIndicator> healthIndicators) {
super("health", false, true);
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
healthAggregator);
for (Map.Entry<String, HealthIndicator> h : healthIndicators.entrySet()) {

View File

@ -18,8 +18,10 @@ package org.springframework.boot.actuate.endpoint.mvc;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
@ -41,7 +43,6 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
* The semantics of {@code @RequestMapping} should be identical to a normal
* {@code @Controller}, but the endpoints should not be annotated as {@code @Controller}
* (otherwise they will be mapped by the normal MVC mechanisms).
*
* <p>
* One of the aims of the mapping is to support endpoints that work as HTTP endpoints but
* can still provide useful service interfaces when there is no HTTP server (and no Spring
@ -97,59 +98,38 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping impleme
@Override
protected void registerHandlerMethod(Object handler, Method method,
RequestMappingInfo mapping) {
if (mapping == null) {
return;
}
Set<String> defaultPatterns = mapping.getPatternsCondition().getPatterns();
String[] patterns = new String[defaultPatterns.isEmpty() ? 1 : defaultPatterns
.size()];
String path = "";
Object bean = handler;
if (bean instanceof String) {
bean = getApplicationContext().getBean((String) handler);
}
if (bean instanceof MvcEndpoint) {
MvcEndpoint endpoint = (MvcEndpoint) bean;
path = endpoint.getPath();
}
int i = 0;
String prefix = StringUtils.hasText(this.prefix) ? this.prefix + path : path;
if (defaultPatterns.isEmpty()) {
patterns[0] = prefix;
}
else {
for (String pattern : defaultPatterns) {
patterns[i] = prefix + pattern;
i++;
}
}
PatternsRequestCondition patternsInfo = new PatternsRequestCondition(patterns);
RequestMappingInfo modified = new RequestMappingInfo(patternsInfo,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
String[] patterns = getPatterns(handler, mapping);
if (handlesPrincipal(method)) {
this.principalHandlers.add(new HandlerMethod(handler, method));
}
super.registerHandlerMethod(handler, method, modified);
super.registerHandlerMethod(handler, method, withNewPatterns(mapping, patterns));
}
public boolean isPrincipalHandler(HttpServletRequest request) {
HandlerExecutionChain handler;
try {
handler = getHandler(request);
private String[] getPatterns(Object handler, RequestMappingInfo mapping) {
String path = getPath(handler);
String prefix = StringUtils.hasText(this.prefix) ? this.prefix + path : path;
Set<String> defaultPatterns = mapping.getPatternsCondition().getPatterns();
if (defaultPatterns.isEmpty()) {
return new String[] { prefix };
}
catch (Exception e) {
return false;
List<String> patterns = new ArrayList<String>(defaultPatterns);
for (int i = 0; i < patterns.size(); i++) {
patterns.set(i, prefix + patterns.get(i));
}
return (handler != null && this.principalHandlers.contains(handler.getHandler()));
return patterns.toArray(new String[patterns.size()]);
}
private String getPath(Object handler) {
if (handler instanceof String) {
handler = getApplicationContext().getBean((String) handler);
}
if (handler instanceof MvcEndpoint) {
return ((MvcEndpoint) handler).getPath();
}
return "";
}
private boolean handlesPrincipal(Method method) {
@ -161,6 +141,32 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping impleme
return false;
}
private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping,
String[] patternStrings) {
PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings);
return new RequestMappingInfo(patterns, mapping.getMethodsCondition(),
mapping.getParamsCondition(), mapping.getHeadersCondition(),
mapping.getConsumesCondition(), mapping.getProducesCondition(),
mapping.getCustomCondition());
}
/**
* Returns {@code true} if the given request is mapped to an endpoint and to a method
* that includes a {@link Principal} argument.
* @param request the http request
* @return {@code true} if the request is
*/
public boolean isPrincipalHandler(HttpServletRequest request) {
try {
HandlerExecutionChain handlerChain = getHandler(request);
Object handler = (handlerChain == null ? null : handlerChain.getHandler());
return (handler != null && this.principalHandlers.contains(handler));
}
catch (Exception ex) {
return false;
}
}
/**
* @param prefix the prefix to set
*/

View File

@ -32,6 +32,8 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link EndpointWebMvcAutoConfiguration} of the {@link HealthMvcEndpoint}.
*
* @author Dave Syer
*/
public class HealthMvcEndpointAutoConfigurationTests {

View File

@ -31,6 +31,11 @@ import org.springframework.context.annotation.Configuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Tests for {@link ConfigurationPropertiesReportEndpoint} when used with bean methods.
*
* @author Dave Syer
*/
public class ConfigurationPropertiesReportEndpointMethodAnnotationsTests {
private AnnotationConfigApplicationContext context;

View File

@ -32,6 +32,12 @@ import org.springframework.context.annotation.Configuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link ConfigurationPropertiesReportEndpoint} when used with a parent
* context.
*
* @author Dave Syer
*/
public class ConfigurationPropertiesReportEndpointParentTests {
private AnnotationConfigApplicationContext context;

View File

@ -34,6 +34,11 @@ import org.springframework.context.annotation.Import;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Tests for {@link ConfigurationPropertiesReportEndpoint} serialization.
*
* @author Dave Syer
*/
public class ConfigurationPropertiesReportEndpointSerializationTests {
private AnnotationConfigApplicationContext context;

View File

@ -31,6 +31,11 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link ConfigurationPropertiesReportEndpoint}.
*
* @author Dave Syer
*/
public class ConfigurationPropertiesReportEndpointTests extends
AbstractEndpointTests<ConfigurationPropertiesReportEndpoint> {

View File

@ -18,7 +18,9 @@ package org.springframework.boot.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -26,7 +28,6 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
@ -90,13 +91,12 @@ public abstract class AutoConfigurationPackages {
* @param packageNames the package names to set
*/
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
augmentBasePackages(constructorArguments, packageNames));
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
@ -108,17 +108,14 @@ public abstract class AutoConfigurationPackages {
}
}
private static String[] augmentBasePackages(
private static String[] addBasePackages(
ConstructorArgumentValues constructorArguments, String[] packageNames) {
ValueHolder valueHolder = constructorArguments.getIndexedArgumentValue(0,
List.class);
List<String> packages = new ArrayList<String>(
Arrays.asList((String[]) valueHolder.getValue()));
packages.addAll(Arrays.asList(packageNames));
return packages.toArray(new String[packages.size()]);
String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0,
String[].class).getValue();
Set<String> merged = new LinkedHashSet<String>();
merged.addAll(Arrays.asList(existing));
merged.addAll(Arrays.asList(packageNames));
return merged.toArray(new String[merged.size()]);
}
/**

View File

@ -165,12 +165,12 @@ public class MongoProperties {
public MongoClient createMongoClient(MongoClientOptions options)
throws UnknownHostException {
try {
if (customAddress() || customCredentials()) {
if (hasCustomAddress() || hasCustomCredentials()) {
if (options == null) {
options = MongoClientOptions.builder().build();
}
List<MongoCredential> credentials = null;
if (customCredentials()) {
if (hasCustomCredentials()) {
credentials = Arrays.asList(MongoCredential.createMongoCRCredential(
this.username, getMongoClientDatabase(), this.password));
}
@ -187,11 +187,11 @@ public class MongoProperties {
}
}
private boolean customAddress() {
private boolean hasCustomAddress() {
return this.host != null || this.port != null;
}
private boolean customCredentials() {
private boolean hasCustomCredentials() {
return this.username != null && this.password != null;
}

View File

@ -23,8 +23,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.boot.autoconfigure.packages.one.FirstConfiguration;
import org.springframework.boot.autoconfigure.packages.two.SecondConfiguration;
import org.springframework.boot.autoconfigure.packagestest.one.FirstConfiguration;
import org.springframework.boot.autoconfigure.packagestest.two.SecondConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -66,16 +66,12 @@ public class AutoConfigurationPackagesTests {
@Test
public void detectsMultipleAutoConfigurationPackages() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
FirstConfiguration.class, SecondConfiguration.class);
List<String> packages = AutoConfigurationPackages.get(context.getBeanFactory());
assertThat(
packages,
hasItems(FirstConfiguration.class.getPackage().getName(),
SecondConfiguration.class.getPackage().getName()));
Package package1 = FirstConfiguration.class.getPackage();
Package package2 = SecondConfiguration.class.getPackage();
assertThat(packages, hasItems(package1.getName(), package2.getName()));
assertThat(packages, hasSize(2));
}

View File

@ -78,24 +78,20 @@ public class MongoPropertiesTests {
MongoProperties properties = new MongoProperties();
properties.setUsername("user");
properties.setPassword("secret".toCharArray());
MongoClient client = properties.createMongoClient(null);
assertMongoCredential(client.getCredentialsList().get(0), "user", "secret");
}
@Test
public void uriCanBeCustomized() throws UnknownHostException {
MongoProperties properties = new MongoProperties();
properties
.setUri("mongodb://user:secret@mongo1.example.com:12345,mongo2.example.com:23456/test");
properties.setUri("mongodb://user:secret@mongo1.example.com:12345,"
+ "mongo2.example.com:23456/test");
MongoClient client = properties.createMongoClient(null);
List<ServerAddress> allAddresses = client.getAllAddress();
assertEquals(2, allAddresses.size());
assertServerAddress(allAddresses.get(0), "mongo1.example.com", 12345);
assertServerAddress(allAddresses.get(1), "mongo2.example.com", 23456);
List<MongoCredential> credentialsList = client.getCredentialsList();
assertEquals(1, credentialsList.size());
assertMongoCredential(credentialsList.get(0), "user", "secret");

View File

@ -14,12 +14,18 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.packages.one;
package org.springframework.boot.autoconfigure.packagestest.one;
import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests;
import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests.TestRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Sample configuration used in {@link AutoConfigurationPackagesTests}.
*
* @author Oliver Gierke
*/
@Configuration
@Import(TestRegistrar.class)
public class FirstConfiguration {

View File

@ -14,12 +14,18 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.packages.two;
package org.springframework.boot.autoconfigure.packagestest.two;
import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests;
import org.springframework.boot.autoconfigure.AutoConfigurationPackagesTests.TestRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* Sample configuration used in {@link AutoConfigurationPackagesTests}.
*
* @author Oliver Gierke
*/
@Configuration
@Import(TestRegistrar.class)
public class SecondConfiguration {

View File

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
@ -1537,7 +1535,7 @@
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<!-- Repositories to allow snapshot and milestone BOM imports during
<!-- Repositories to allow snapshot and milestone BOM imports during
development. This section is stripped out when a full release is prepared. -->
<repository>
<id>spring-milestones</id>

View File

@ -413,16 +413,15 @@ If you don't want to expose endpoints over HTTP you can set the management port
[[production-ready-health-access-restrictions]]
=== Health endpoint anonymous access restrictions
The information exposed by the health endpoint varies depending on whether or not it's
accessed anonymously. By default, when accessed anonymously, any details about the server's health
are hidden and the endpoint will simply indicate whether or not the server is up or
down. Furthermore, when accessed anonymously, the response is cached for a configurable
period to prevent the endpoint being used in a denial of service attack.
accessed anonymously. By default, when accessed anonymously, any details about the
server's health are hidden and the endpoint will simply indicate whether or not the server
is up or down. Furthermore, when accessed anonymously, the response is cached for a
configurable period to prevent the endpoint being used in a denial of service attack.
The `endpoints.health.time-to-live` property is used to configure the caching period in
milliseconds. It defaults to 1000, i.e. one second.
The above-described restrictions can be disabled, thereby allowing anonymous users full
access to the health endpoint. To do so, set `endpoints.health.sensitive`
to `false`.
access to the health endpoint. To do so, set `endpoints.health.sensitive` to `false`.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2014 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.

View File

@ -70,10 +70,7 @@ public class ConfigurationBeanFactoryMetaData implements BeanFactoryPostProcesso
public <A extends Annotation> A findFactoryAnnotation(String beanName, Class<A> type) {
Method method = findFactoryMethod(beanName);
if (method != null) {
return AnnotationUtils.findAnnotation(method, type);
}
return null;
return (method == null ? null : AnnotationUtils.findAnnotation(method, type));
}
private Method findFactoryMethod(String beanName) {

View File

@ -0,0 +1,172 @@
/*
* Copyright 2012-2014 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.json;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.StringUtils;
/**
* Really basic JSON parser for when you have nothing else available. Comes with some
* limitations with respect to the JSON specification (e.g. only supports String values),
* so users will probably prefer to have a library handle things instead (Jackson or Snake
* YAML are supported).
*
* @author Dave Syer
* @see JsonParserFactory
* @since 1.2.0
*/
public class BasicJsonParser implements JsonParser {
@Override
public Map<String, Object> parseMap(String json) {
if (json != null) {
json = json.trim();
if (json.startsWith("{")) {
return parseMapInternal(json);
}
else if (json.equals("")) {
return new HashMap<String, Object>();
}
}
return null;
}
@Override
public List<Object> parseList(String json) {
if (json != null) {
json = json.trim();
if (json.startsWith("[")) {
return parseListInternal(json);
}
else if (json.trim().equals("")) {
return new ArrayList<Object>();
}
}
return null;
}
private List<Object> parseListInternal(String json) {
List<Object> list = new ArrayList<Object>();
json = trimLeadingCharacter(trimTrailingCharacter(json, ']'), '[');
for (String value : tokenize(json)) {
list.add(parseInternal(value));
}
return list;
}
private Object parseInternal(String json) {
if (json.startsWith("[")) {
return parseListInternal(json);
}
if (json.startsWith("{")) {
return parseMapInternal(json);
}
if (json.startsWith("\"")) {
return trimTrailingCharacter(trimLeadingCharacter(json, '"'), '"');
}
try {
return Long.valueOf(json);
}
catch (NumberFormatException ex) {
// ignore
}
try {
return Double.valueOf(json);
}
catch (NumberFormatException ex) {
// ignore
}
return json;
}
private static String trimTrailingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(string.length() - 1) == c) {
return string.substring(0, string.length() - 1);
}
return string;
}
private static String trimLeadingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(0) == c) {
return string.substring(1);
}
return string;
}
private Map<String, Object> parseMapInternal(String json) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
json = trimLeadingCharacter(trimTrailingCharacter(json, '}'), '{');
for (String pair : tokenize(json)) {
String[] values = StringUtils.trimArrayElements(StringUtils.split(pair, ":"));
String key = trimLeadingCharacter(trimTrailingCharacter(values[0], '"'), '"');
Object value = null;
if (values.length > 0) {
String string = trimLeadingCharacter(
trimTrailingCharacter(values[1], '"'), '"');
value = parseInternal(string);
}
map.put(key, value);
}
return map;
}
private List<String> tokenize(String json) {
List<String> list = new ArrayList<String>();
int index = 0;
int inObject = 0;
int inList = 0;
StringBuilder build = new StringBuilder();
while (index < json.length()) {
char current = json.charAt(index);
if (current == '{') {
inObject++;
}
if (current == '}') {
inObject--;
}
if (current == '[') {
inList++;
}
if (current == ']') {
inList--;
}
if (current == ',' && inObject == 0 && inList == 0) {
list.add(build.toString());
build.setLength(0);
}
else {
build.append(current);
}
index++;
}
if (build.length() > 0) {
list.add(build.toString());
}
return list;
}
}

View File

@ -21,12 +21,11 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Thin wrapper to adapt {@link JSONObject} to a {@link JsonParser}.
* Thin wrapper to adapt {@link org.json.JSONObject} to a {@link JsonParser}.
*
* @author Dave Syer
* @since 1.2.0
@ -37,29 +36,50 @@ public class JsonJsonParser implements JsonParser {
@Override
public Map<String, Object> parseMap(String json) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
try {
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) new JSONParser()
.parse(json);
map.putAll(value);
}
catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse Json", e);
}
putAll(map, new JSONObject(json));
return map;
}
private void putAll(Map<String, Object> map, JSONObject object) {
for (Object key : object.keySet()) {
String name = key.toString();
Object value = object.get(name);
if (value instanceof JSONObject) {
Map<String, Object> nested = new LinkedHashMap<String, Object>();
putAll(nested, (JSONObject) value);
value = nested;
}
if (value instanceof JSONArray) {
List<Object> nested = new ArrayList<Object>();
addAll(nested, (JSONArray) value);
value = nested;
}
map.put(name, value);
}
}
private void addAll(List<Object> list, JSONArray array) {
for (int i = 0; i < array.length(); i++) {
Object value = array.get(i);
if (value instanceof JSONObject) {
Map<String, Object> nested = new LinkedHashMap<String, Object>();
putAll(nested, (JSONObject) value);
value = nested;
}
if (value instanceof JSONArray) {
List<Object> nested = new ArrayList<Object>();
addAll(nested, (JSONArray) value);
value = nested;
}
list.add(value);
}
}
@Override
public List<Object> parseList(String json) {
List<Object> nested = new ArrayList<Object>();
try {
@SuppressWarnings("unchecked")
List<Object> value = (List<Object>) new JSONParser().parse(json);
nested.addAll(value);
}
catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse Json", e);
}
addAll(nested, new JSONArray(json));
return nested;
}
}

View File

@ -24,7 +24,9 @@ import org.springframework.util.ClassUtils;
* @author Dave Syer
* @see JacksonJsonParser
* @see YamlJsonParser
* @see SimpleJsonParser
* @see JsonSimpleJsonParser
* @see JsonJsonParser
* @see BasicJsonParser
*/
public abstract class JsonParserFactory {
@ -39,19 +41,19 @@ public abstract class JsonParserFactory {
if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
return new JacksonJsonParser();
}
if (ClassUtils.isPresent("org.json.JSONObject", null)) {
return new JsonJsonParser();
}
if (ClassUtils.isPresent("org.json.simple.JSONObject", null)) {
return new SimpleJsonJsonParser();
}
if (ClassUtils.isPresent("com.google.gson.Gson", null)) {
return new GsonJsonParser();
}
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
return new YamlJsonParser();
}
return new SimpleJsonParser();
if (ClassUtils.isPresent("org.json.simple.JSONObject", null)) {
return new JsonSimpleJsonParser();
}
if (ClassUtils.isPresent("org.json.JSONObject", null)) {
return new JsonJsonParser();
}
return new BasicJsonParser();
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2012-2014 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.json;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
/**
* Thin wrapper to adapt {@link org.json.simple.JSONObject} to a {@link JsonParser}.
*
* @author Dave Syer
* @since 1.2.0
* @see JsonParserFactory
*/
public class JsonSimpleJsonParser implements JsonParser {
@Override
public Map<String, Object> parseMap(String json) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
try {
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) new JSONParser()
.parse(json);
map.putAll(value);
}
catch (ParseException ex) {
throw new IllegalArgumentException("Cannot parse JSON", ex);
}
return map;
}
@Override
public List<Object> parseList(String json) {
List<Object> nested = new ArrayList<Object>();
try {
@SuppressWarnings("unchecked")
List<Object> value = (List<Object>) new JSONParser().parse(json);
nested.addAll(value);
}
catch (ParseException ex) {
throw new IllegalArgumentException("Cannot parse JSON", ex);
}
return nested;
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright 2012-2014 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.json;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Thin wrapper to adapt {@link JSONObject} to a {@link JsonParser}.
*
* @author Dave Syer
* @since 1.2.0
* @see JsonParserFactory
*/
public class SimpleJsonJsonParser implements JsonParser {
@Override
public Map<String, Object> parseMap(String json) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
putAll(map, new JSONObject(json));
return map;
}
private void putAll(Map<String, Object> map, JSONObject object) {
for (Object key : object.keySet()) {
String name = key.toString();
Object value = object.get(name);
if (value instanceof JSONObject) {
Map<String, Object> nested = new LinkedHashMap<String, Object>();
putAll(nested, (JSONObject) value);
value = nested;
}
if (value instanceof JSONArray) {
List<Object> nested = new ArrayList<Object>();
addAll(nested, (JSONArray) value);
value = nested;
}
map.put(name, value);
}
}
private void addAll(List<Object> list, JSONArray array) {
for (int i = 0; i < array.length(); i++) {
Object value = array.get(i);
if (value instanceof JSONObject) {
Map<String, Object> nested = new LinkedHashMap<String, Object>();
putAll(nested, (JSONObject) value);
value = nested;
}
if (value instanceof JSONArray) {
List<Object> nested = new ArrayList<Object>();
addAll(nested, (JSONArray) value);
value = nested;
}
list.add(value);
}
}
@Override
public List<Object> parseList(String json) {
List<Object> nested = new ArrayList<Object>();
addAll(nested, new JSONArray(json));
return nested;
}
}

View File

@ -16,14 +16,6 @@
package org.springframework.boot.json;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.StringUtils;
/**
* Really basic JSON parser for when you have nothing else available. Comes with some
* limitations with respect to the JSON specification (e.g. only supports String values),
@ -32,140 +24,8 @@ import org.springframework.util.StringUtils;
*
* @author Dave Syer
* @see JsonParserFactory
* @deprecated since 1.2.0 in favor of {@link BasicJsonParser}.
*/
public class SimpleJsonParser implements JsonParser {
@Override
public Map<String, Object> parseMap(String json) {
if (json != null) {
json = json.trim();
if (json.startsWith("{")) {
return parseMapInternal(json);
}
else if (json.equals("")) {
return new HashMap<String, Object>();
}
}
return null;
}
@Override
public List<Object> parseList(String json) {
if (json != null) {
json = json.trim();
if (json.startsWith("[")) {
return parseListInternal(json);
}
else if (json.trim().equals("")) {
return new ArrayList<Object>();
}
}
return null;
}
private List<Object> parseListInternal(String json) {
List<Object> list = new ArrayList<Object>();
json = trimLeadingCharacter(trimTrailingCharacter(json, ']'), '[');
for (String value : tokenize(json)) {
list.add(parseInternal(value));
}
return list;
}
private Object parseInternal(String json) {
if (json.startsWith("[")) {
return parseListInternal(json);
}
if (json.startsWith("{")) {
return parseMapInternal(json);
}
if (json.startsWith("\"")) {
return trimTrailingCharacter(trimLeadingCharacter(json, '"'), '"');
}
try {
return Long.valueOf(json);
}
catch (NumberFormatException ex) {
// ignore
}
try {
return Double.valueOf(json);
}
catch (NumberFormatException ex) {
// ignore
}
return json;
}
private static String trimTrailingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(string.length() - 1) == c) {
return string.substring(0, string.length() - 1);
}
return string;
}
private static String trimLeadingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(0) == c) {
return string.substring(1);
}
return string;
}
private Map<String, Object> parseMapInternal(String json) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
json = trimLeadingCharacter(trimTrailingCharacter(json, '}'), '{');
for (String pair : tokenize(json)) {
String[] values = StringUtils.trimArrayElements(StringUtils.split(pair, ":"));
String key = trimLeadingCharacter(trimTrailingCharacter(values[0], '"'), '"');
Object value = null;
if (values.length > 0) {
String string = trimLeadingCharacter(
trimTrailingCharacter(values[1], '"'), '"');
value = parseInternal(string);
}
map.put(key, value);
}
return map;
}
private List<String> tokenize(String json) {
List<String> list = new ArrayList<String>();
int index = 0;
int inObject = 0;
int inList = 0;
StringBuilder build = new StringBuilder();
while (index < json.length()) {
char current = json.charAt(index);
if (current == '{') {
inObject++;
}
if (current == '}') {
inObject--;
}
if (current == '[') {
inList++;
}
if (current == ']') {
inList--;
}
if (current == ',' && inObject == 0 && inList == 0) {
list.add(build.toString());
build.setLength(0);
}
else {
build.append(current);
}
index++;
}
if (build.length() > 0) {
list.add(build.toString());
}
return list;
}
@Deprecated
public class SimpleJsonParser extends BasicJsonParser {
}

View File

@ -24,17 +24,15 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link SimpleJsonParser}.
* Base for {@link JsonParser} tests.
*
* @author Dave Syer
*/
public class SimpleJsonParserTests {
public abstract class AbstractJsonParserTests {
private final JsonParser parser = getParser();
protected JsonParser getParser() {
return new SimpleJsonParser();
}
protected abstract JsonParser getParser();
@Test
public void testSimpleMap() {

View File

@ -0,0 +1,31 @@
/*
* Copyright 2012-2014 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.json;
/**
* Tests for {@link BasicJsonParser}.
*
* @author Dave Syer
*/
public class BasicJsonParserTests extends AbstractJsonParserTests {
@Override
protected JsonParser getParser() {
return new BasicJsonParser();
}
}

View File

@ -21,10 +21,11 @@ package org.springframework.boot.json;
*
* @author Dave Syer
*/
public class GsonJsonParserTests extends SimpleJsonParserTests {
public class GsonJsonParserTests extends AbstractJsonParserTests {
@Override
protected JsonParser getParser() {
return new GsonJsonParser();
}
}

View File

@ -21,10 +21,11 @@ package org.springframework.boot.json;
*
* @author Dave Syer
*/
public class JacksonParserTests extends SimpleJsonParserTests {
public class JacksonParserTests extends AbstractJsonParserTests {
@Override
protected JsonParser getParser() {
return new JacksonJsonParser();
}
}

View File

@ -17,14 +17,15 @@
package org.springframework.boot.json;
/**
* Tests for {@link JsonJsonParser}.
* Tests for {@link JsonSimpleJsonParser}.
*
* @author Dave Syer
*/
public class JsonJsonParserTests extends SimpleJsonParserTests {
public class JsonJsonParserTests extends AbstractJsonParserTests {
@Override
protected JsonParser getParser() {
return new JsonJsonParser();
return new JsonSimpleJsonParser();
}
}

View File

@ -17,14 +17,15 @@
package org.springframework.boot.json;
/**
* Tests for {@link SimpleJsonJsonParser}.
* Tests for {@link JsonJsonParser}.
*
* @author Dave Syer
*/
public class SimpleJsonJsonParserTests extends SimpleJsonParserTests {
public class SimpleJsonJsonParserTests extends AbstractJsonParserTests {
@Override
protected JsonParser getParser() {
return new SimpleJsonJsonParser();
return new JsonJsonParser();
}
}

View File

@ -21,10 +21,11 @@ package org.springframework.boot.json;
*
* @author Dave Syer
*/
public class YamlJsonParserTests extends SimpleJsonParserTests {
public class YamlJsonParserTests extends AbstractJsonParserTests {
@Override
protected JsonParser getParser() {
return new YamlJsonParser();
}
}