Commit 48db5457 authored by Phillip Webb's avatar Phillip Webb

Polish

parent 237defaf
......@@ -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() {
......
......@@ -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) {
......
......@@ -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 + "'"));
}
......
......@@ -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()) {
......
......@@ -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();
String[] patterns = getPatterns(handler, mapping);
if (handlesPrincipal(method)) {
this.principalHandlers.add(new HandlerMethod(handler, method));
}
super.registerHandlerMethod(handler, method, withNewPatterns(mapping, patterns));
}
int i = 0;
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()) {
patterns[0] = prefix;
}
else {
for (String pattern : defaultPatterns) {
patterns[i] = prefix + pattern;
i++;
}
return new String[] { prefix };
}
PatternsRequestCondition patternsInfo = new PatternsRequestCondition(patterns);
RequestMappingInfo modified = new RequestMappingInfo(patternsInfo,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
if (handlesPrincipal(method)) {
this.principalHandlers.add(new HandlerMethod(handler, method));
List<String> patterns = new ArrayList<String>(defaultPatterns);
for (int i = 0; i < patterns.size(); i++) {
patterns.set(i, prefix + patterns.get(i));
}
super.registerHandlerMethod(handler, method, modified);
return patterns.toArray(new String[patterns.size()]);
}
public boolean isPrincipalHandler(HttpServletRequest request) {
HandlerExecutionChain handler;
try {
handler = getHandler(request);
private String getPath(Object handler) {
if (handler instanceof String) {
handler = getApplicationContext().getBean((String) handler);
}
catch (Exception e) {
return false;
if (handler instanceof MvcEndpoint) {
return ((MvcEndpoint) handler).getPath();
}
return (handler != null && this.principalHandlers.contains(handler.getHandler()));
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
*/
......
......@@ -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 {
......
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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> {
......
......@@ -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()]);
}
/**
......
......@@ -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;
}
......
......@@ -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));
}
......
......@@ -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");
......
......@@ -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 {
......
......@@ -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 {
......
<?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>
......
......@@ -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`.
......
/*
* 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.
......
/*
* 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.
......
/*
* 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.
......
/*
* 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.
......
/*
* 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.
......
/*
* 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.
......
/*
* 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.
......
......@@ -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) {
......
/*
* 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;
}
}
......@@ -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);
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);
}
catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse Json", e);
}
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);
}
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 e) {
throw new IllegalArgumentException("Cannot parse Json", e);
}
addAll(nested, new JSONArray(json));
return nested;
}
}
......@@ -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();
}
}
......@@ -21,65 +21,44 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
/**
* Thin wrapper to adapt {@link JSONObject} to a {@link JsonParser}.
* Thin wrapper to adapt {@link org.json.simple.JSONObject} to a {@link JsonParser}.
*
* @author Dave Syer
* @since 1.2.0
* @see JsonParserFactory
*/
public class SimpleJsonJsonParser implements JsonParser {
public class JsonSimpleJsonParser 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);
try {
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) new JSONParser()
.parse(json);
map.putAll(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);
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>();
addAll(nested, new JSONArray(json));
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;
}
}
......@@ -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 {
}
......@@ -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() {
......
/*
* 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();
}
}
......@@ -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();
}
}
......@@ -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();
}
}
......@@ -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();
}
}
......@@ -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();
}
}
......@@ -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();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment