mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-05 00:56:58 +08:00
[bs-80] Add configurable / switchable web request trace logging (headers etc)
* Added a bean post processor for the Spring Security filter chain (so you only get traces by default if security is on) * Every request is logged at trace level if the dump requests flag is on * Requests are also dumped to a TraceRepository for later analysis (very useful for tracing problems in real time when a support call comes in) [Fixes #48976001]
This commit is contained in:
parent
dd1fc3f992
commit
833b13bbbc
9
pom.xml
9
pom.xml
|
@ -237,12 +237,17 @@
|
|||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.1.4</version>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.1.4</version>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-joda</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<profile>
|
||||
<id>tomcat</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -33,7 +33,7 @@
|
|||
<profile>
|
||||
<id>jetty</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
@ -91,7 +91,7 @@ public class VarzContextServiceBootstrapApplicationTests {
|
|||
@Test
|
||||
public void testHealthz() throws Exception {
|
||||
ResponseEntity<String> entity = getRestTemplate().getForEntity(
|
||||
"http://localhost:" + managementPort + "healthz", String.class);
|
||||
"http://localhost:" + managementPort + "/healthz", String.class);
|
||||
assertEquals(HttpStatus.OK, entity.getStatusCode());
|
||||
assertEquals("ok", entity.getBody());
|
||||
}
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
|
@ -65,6 +70,10 @@
|
|||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-joda</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-javaconfig</artifactId>
|
||||
|
|
|
@ -40,16 +40,16 @@ public class ManagementAutoConfiguration implements ApplicationContextAware,
|
|||
private ApplicationContext parent;
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private ContainerProperties configuration = new ContainerProperties();
|
||||
|
||||
@ConditionalOnExpression("${container.port:8080} == ${container.management_port:8080}")
|
||||
@Configuration
|
||||
@Import({ VarzAutoConfiguration.class, HealthzAutoConfiguration.class,
|
||||
ShutdownAutoConfiguration.class })
|
||||
ShutdownAutoConfiguration.class, TraceAutoConfiguration.class })
|
||||
public static class ManagementEndpointsConfiguration {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ContainerProperties configuration = new ContainerProperties();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
|
@ -73,7 +73,7 @@ public class ManagementAutoConfiguration implements ApplicationContextAware,
|
|||
context.setParent(this.parent);
|
||||
context.register(ManagementContainerConfiguration.class,
|
||||
VarzAutoConfiguration.class, HealthzAutoConfiguration.class,
|
||||
ShutdownAutoConfiguration.class);
|
||||
ShutdownAutoConfiguration.class, TraceAutoConfiguration.class);
|
||||
context.refresh();
|
||||
this.context = context;
|
||||
}
|
||||
|
|
|
@ -16,11 +16,19 @@
|
|||
|
||||
package org.springframework.bootstrap.autoconfigure.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
|
||||
import org.springframework.bootstrap.service.annotation.EnableConfigurationProperties;
|
||||
import org.springframework.bootstrap.service.properties.ContainerProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.joda.JodaModule;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for service apps.
|
||||
|
@ -31,7 +39,20 @@ import org.springframework.context.annotation.Import;
|
|||
@Import({ ManagementAutoConfiguration.class, MetricAutoConfiguration.class,
|
||||
ContainerConfiguration.class, SecurityAutoConfiguration.class,
|
||||
MetricFilterAutoConfiguration.class })
|
||||
public class ServiceAutoConfiguration {
|
||||
public class ServiceAutoConfiguration extends WebMvcConfigurationSupport {
|
||||
|
||||
@Override
|
||||
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
addDefaultHttpMessageConverters(converters);
|
||||
for (HttpMessageConverter<?> converter : converters) {
|
||||
if (converter instanceof MappingJackson2HttpMessageConverter) {
|
||||
MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
|
||||
jacksonConverter.getObjectMapper().registerModule(new JodaModule());
|
||||
jacksonConverter.getObjectMapper().disable(
|
||||
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ContainerProperties has to be declared in a non-conditional bean, so that it gets
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.autoconfigure.service;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
|
||||
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
|
||||
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
|
||||
import org.springframework.bootstrap.service.properties.ContainerProperties;
|
||||
import org.springframework.bootstrap.service.trace.InMemoryTraceRepository;
|
||||
import org.springframework.bootstrap.service.trace.SecurityFilterPostProcessor;
|
||||
import org.springframework.bootstrap.service.trace.TraceEndpoint;
|
||||
import org.springframework.bootstrap.service.trace.TraceRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for /trace endpoint.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
|
||||
@ConditionalOnMissingBean({ TraceEndpoint.class })
|
||||
public class TraceAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ContainerProperties configuration = new ContainerProperties();
|
||||
|
||||
@Autowired(required = false)
|
||||
private TraceRepository traceRepository = new InMemoryTraceRepository();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean({ TraceRepository.class })
|
||||
protected TraceRepository traceRepository() {
|
||||
return this.traceRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterPostProcessor securityFilterPostProcessor() {
|
||||
SecurityFilterPostProcessor processor = new SecurityFilterPostProcessor(
|
||||
traceRepository());
|
||||
processor.setDumpRequests(this.configuration.isDumpRequests());
|
||||
return processor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TraceEndpoint traceEndpoint() {
|
||||
return new TraceEndpoint(this.traceRepository);
|
||||
}
|
||||
|
||||
}
|
|
@ -19,11 +19,14 @@ package org.springframework.bootstrap.service.annotation;
|
|||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.bootstrap.bind.PropertySourcesBindingPostProcessor;
|
||||
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
|
@ -47,6 +50,10 @@ public class ConfigurationPropertiesBindingConfiguration {
|
|||
@Autowired(required = false)
|
||||
private Environment environment;
|
||||
|
||||
@Autowired(required = false)
|
||||
@Qualifier(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)
|
||||
private ConversionService conversionService;
|
||||
|
||||
/**
|
||||
* Lifecycle hook that binds application properties to any bean whose type is
|
||||
* decorated with {@link ConfigurationProperties} annotation.
|
||||
|
@ -72,6 +79,7 @@ public class ConfigurationPropertiesBindingConfiguration {
|
|||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
processor.setValidator(validator);
|
||||
processor.setConversionService(this.conversionService);
|
||||
processor.setPropertySources(propertySources);
|
||||
return processor;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ public class ContainerProperties {
|
|||
|
||||
private Tomcat tomcat = new Tomcat();
|
||||
|
||||
private boolean dumpRequests;
|
||||
|
||||
public Tomcat getTomcat() {
|
||||
return this.tomcat;
|
||||
}
|
||||
|
@ -74,6 +76,14 @@ public class ContainerProperties {
|
|||
this.allowShutdown = allowShutdown;
|
||||
}
|
||||
|
||||
public boolean isDumpRequests() {
|
||||
return this.dumpRequests;
|
||||
}
|
||||
|
||||
public void setDumpRequests(boolean dumpRequests) {
|
||||
this.dumpRequests = dumpRequests;
|
||||
}
|
||||
|
||||
public static class Tomcat {
|
||||
|
||||
private String accessLogPattern;
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.service.trace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class InMemoryTraceRepository implements TraceRepository {
|
||||
|
||||
private int capacity = 100;
|
||||
|
||||
private List<Trace> traces = new ArrayList<Trace>();
|
||||
|
||||
/**
|
||||
* @param capacity the capacity to set
|
||||
*/
|
||||
public void setCapacity(int capacity) {
|
||||
this.capacity = capacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Trace> traces() {
|
||||
synchronized (this.traces) {
|
||||
return Collections.unmodifiableList(this.traces);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Map<String, Object> map) {
|
||||
Trace trace = new Trace(new DateTime(), map);
|
||||
synchronized (this.traces) {
|
||||
while (this.traces.size() >= this.capacity) {
|
||||
this.traces.remove(0);
|
||||
}
|
||||
this.traces.add(trace);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.service.trace;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Bean post processor that adds a filter to Spring Security. The filter (optionally) logs
|
||||
* request headers at trace level and also sends the headers to a {@link TraceRepository}
|
||||
* for later analysis.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SecurityFilterPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final static Log logger = LogFactory
|
||||
.getLog(SecurityFilterPostProcessor.class);
|
||||
private boolean dumpRequests = false;
|
||||
private List<String> ignore = Collections.emptyList();
|
||||
|
||||
private TraceRepository traceRepository = new InMemoryTraceRepository();
|
||||
|
||||
/**
|
||||
* @param traceRepository
|
||||
*/
|
||||
public SecurityFilterPostProcessor(TraceRepository traceRepository) {
|
||||
super();
|
||||
this.traceRepository = traceRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of filter chains which should be ignored completely.
|
||||
*/
|
||||
public void setIgnore(List<String> ignore) {
|
||||
Assert.notNull(ignore);
|
||||
this.ignore = ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugging feature. If enabled, and trace logging is enabled
|
||||
*/
|
||||
public void setDumpRequests(boolean dumpRequests) {
|
||||
this.dumpRequests = dumpRequests;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
|
||||
if (!this.ignore.contains(beanName)) {
|
||||
if (bean instanceof FilterChainProxy) {
|
||||
FilterChainProxy proxy = (FilterChainProxy) bean;
|
||||
for (SecurityFilterChain filterChain : proxy.getFilterChains()) {
|
||||
processFilterChain(filterChain, beanName);
|
||||
}
|
||||
}
|
||||
if (bean instanceof SecurityFilterChain) {
|
||||
processFilterChain((SecurityFilterChain) bean, beanName);
|
||||
}
|
||||
}
|
||||
|
||||
return bean;
|
||||
|
||||
}
|
||||
|
||||
private void processFilterChain(SecurityFilterChain filterChain, String beanName) {
|
||||
logger.info("Processing security filter chain " + beanName);
|
||||
Filter loggingFilter = new WebRequestLoggingFilter(beanName);
|
||||
filterChain.getFilters().add(0, loggingFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
class WebRequestLoggingFilter implements Filter {
|
||||
|
||||
final Log logger = LogFactory.getLog(WebRequestLoggingFilter.class);
|
||||
private final String name;
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
WebRequestLoggingFilter(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
||||
Map<String, Object> trace = getTrace(request);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> headers = (Map<String, Object>) trace.get("headers");
|
||||
SecurityFilterPostProcessor.this.traceRepository.add(trace);
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Filter chain '" + this.name + "' processing request "
|
||||
+ request.getMethod() + " " + request.getRequestURI());
|
||||
if (SecurityFilterPostProcessor.this.dumpRequests) {
|
||||
try {
|
||||
this.logger.trace("Headers: "
|
||||
+ this.objectMapper.writeValueAsString(headers));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("Cannot create JSON", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
protected Map<String, Object> getTrace(HttpServletRequest request) {
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
List<String> values = Collections.list(request.getHeaders(name));
|
||||
Object value = values;
|
||||
if (values.size() == 1) {
|
||||
value = values.get(0);
|
||||
} else if (values.isEmpty()) {
|
||||
value = "";
|
||||
}
|
||||
map.put(name, value);
|
||||
|
||||
}
|
||||
Map<String, Object> trace = new LinkedHashMap<String, Object>();
|
||||
trace.put("chain", this.name);
|
||||
trace.put("method", request.getMethod());
|
||||
trace.put("path", request.getRequestURI());
|
||||
trace.put("headers", map);
|
||||
return trace;
|
||||
}
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.service.trace;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class Trace {
|
||||
|
||||
private DateTime timestamp;
|
||||
|
||||
private Map<String, Object> info;
|
||||
|
||||
public Trace(DateTime timestamp, Map<String, Object> info) {
|
||||
super();
|
||||
this.timestamp = timestamp;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public DateTime getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public Map<String, Object> getInfo() {
|
||||
return this.info;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.service.trace;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Controller
|
||||
public class TraceEndpoint {
|
||||
|
||||
private TraceRepository tracer;
|
||||
|
||||
/**
|
||||
* @param tracer
|
||||
*/
|
||||
public TraceEndpoint(TraceRepository tracer) {
|
||||
super();
|
||||
this.tracer = tracer;
|
||||
}
|
||||
|
||||
@RequestMapping("${endpoints.trace.path:/trace}")
|
||||
@ResponseBody
|
||||
public List<Trace> trace() {
|
||||
return this.tracer.traces();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.service.trace;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A repository for traces. Traces are simple documents (maps) with a timestamp, and can
|
||||
* be used for analysing contextual information like HTTP headers.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public interface TraceRepository {
|
||||
|
||||
List<Trace> traces();
|
||||
|
||||
void add(Map<String, Object> trace);
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.service.trace;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class InMemoryTraceRepositoryTests {
|
||||
|
||||
private InMemoryTraceRepository repository = new InMemoryTraceRepository();
|
||||
|
||||
@Test
|
||||
public void capacityLimited() {
|
||||
this.repository.setCapacity(2);
|
||||
this.repository.add(Collections.<String, Object> singletonMap("foo", "bar"));
|
||||
this.repository.add(Collections.<String, Object> singletonMap("bar", "foo"));
|
||||
this.repository.add(Collections.<String, Object> singletonMap("bar", "bar"));
|
||||
List<Trace> traces = this.repository.traces();
|
||||
assertEquals(2, traces.size());
|
||||
assertEquals("bar", traces.get(1).getInfo().get("bar"));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2012-2013 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.bootstrap.service.trace;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.bootstrap.service.trace.SecurityFilterPostProcessor.WebRequestLoggingFilter;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class SecurityFilterPostProcessorTests {
|
||||
|
||||
private SecurityFilterPostProcessor processor = new SecurityFilterPostProcessor(
|
||||
new InMemoryTraceRepository());
|
||||
|
||||
@Test
|
||||
public void filterDumpsRequest() {
|
||||
WebRequestLoggingFilter filter = this.processor.new WebRequestLoggingFilter("foo");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
|
||||
request.addHeader("Accept", "application/json");
|
||||
Map<String, Object> trace = filter.getTrace(request);
|
||||
assertEquals("GET", trace.get("method"));
|
||||
assertEquals("/foo", trace.get("path"));
|
||||
assertEquals("{Accept=application/json}", trace.get("headers").toString());
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import org.springframework.beans.factory.FactoryBean;
|
|||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.MessageSourceAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.BindException;
|
||||
|
@ -68,6 +69,8 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
|||
|
||||
private String targetName;
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
/**
|
||||
* @param target the target object to bind too
|
||||
* @see #PropertiesConfigurationFactory(Class)
|
||||
|
@ -142,6 +145,13 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
|||
this.propertySources = propertySources;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param conversionService the conversionService to set
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param validator the validator to set
|
||||
*/
|
||||
|
@ -176,6 +186,9 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
|
|||
if (this.validator != null) {
|
||||
dataBinder.setValidator(this.validator);
|
||||
}
|
||||
if (this.conversionService != null) {
|
||||
dataBinder.setConversionService(this.conversionService);
|
||||
}
|
||||
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
|
||||
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
|
||||
customizeBinder(dataBinder);
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.springframework.beans.factory.BeanCreationException;
|
|||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
|
@ -33,6 +34,8 @@ public class PropertySourcesBindingPostProcessor implements BeanPostProcessor {
|
|||
|
||||
private Validator validator;
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
/**
|
||||
* @param propertySources
|
||||
*/
|
||||
|
@ -47,6 +50,13 @@ public class PropertySourcesBindingPostProcessor implements BeanPostProcessor {
|
|||
this.validator = validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param conversionService the conversionService to set
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
|
@ -63,6 +73,7 @@ public class PropertySourcesBindingPostProcessor implements BeanPostProcessor {
|
|||
bean);
|
||||
factory.setPropertySources(this.propertySources);
|
||||
factory.setValidator(this.validator);
|
||||
factory.setConversionService(this.conversionService);
|
||||
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
|
||||
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
|
||||
String targetName = "".equals(annotation.value()) ? ("".equals(annotation
|
||||
|
|
|
@ -56,7 +56,6 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
|
|||
@Override
|
||||
public synchronized void stop() {
|
||||
try {
|
||||
this.server.setGracefulShutdown(10000);
|
||||
this.server.stop();
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
|
|
|
@ -32,8 +32,9 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.bootstrap.bind.RelaxedDataBinderTests.OAuthConfiguration.OAuthConfigurationValidator;
|
||||
import org.springframework.context.support.StaticMessageSource;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
@ -55,6 +56,8 @@ public class RelaxedDataBinderTests {
|
|||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
@Test
|
||||
public void testBindString() throws Exception {
|
||||
VanillaTarget target = new VanillaTarget();
|
||||
|
@ -108,6 +111,23 @@ public class RelaxedDataBinderTests {
|
|||
assertEquals(123, target.getNested().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedList() throws Exception {
|
||||
TargetWithNestedList target = new TargetWithNestedList();
|
||||
bind(target, "nested: bar,foo");
|
||||
bind(target, "nested[0]: bar");
|
||||
bind(target, "nested[1]: foo");
|
||||
assertEquals("[bar, foo]", target.getNested().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedListCommaDelimitedONly() throws Exception {
|
||||
TargetWithNestedList target = new TargetWithNestedList();
|
||||
this.conversionService = new DefaultConversionService();
|
||||
bind(target, "nested: bar,foo");
|
||||
assertEquals("[bar, foo]", target.getNested().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNestedMap() throws Exception {
|
||||
TargetWithNestedMap target = new TargetWithNestedMap();
|
||||
|
@ -174,96 +194,13 @@ public class RelaxedDataBinderTests {
|
|||
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
|
||||
validatorFactoryBean.afterPropertiesSet();
|
||||
binder.setValidator(validatorFactoryBean);
|
||||
binder.setConversionService(this.conversionService);
|
||||
binder.bind(new MutablePropertyValues(properties));
|
||||
binder.validate();
|
||||
|
||||
return binder.getBindingResult();
|
||||
}
|
||||
|
||||
@Documented
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RUNTIME)
|
||||
@Constraint(validatedBy = OAuthConfigurationValidator.class)
|
||||
public @interface ValidOAuthConfiguration {
|
||||
}
|
||||
|
||||
@ValidOAuthConfiguration
|
||||
public static class OAuthConfiguration {
|
||||
|
||||
private Client client;
|
||||
|
||||
private Map<String, OAuthClient> clients;
|
||||
|
||||
public Client getClient() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Map<String, OAuthClient> getClients() {
|
||||
return this.clients;
|
||||
}
|
||||
|
||||
public void setClients(Map<String, OAuthClient> clients) {
|
||||
this.clients = clients;
|
||||
}
|
||||
|
||||
public static class Client {
|
||||
|
||||
private List<String> autoapprove;
|
||||
|
||||
public List<String> getAutoapprove() {
|
||||
return this.autoapprove;
|
||||
}
|
||||
|
||||
public void setAutoapprove(List<String> autoapprove) {
|
||||
this.autoapprove = autoapprove;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class OAuthClient {
|
||||
|
||||
private String id;
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class OAuthConfigurationValidator implements
|
||||
ConstraintValidator<ValidOAuthConfiguration, OAuthConfiguration> {
|
||||
|
||||
@Override
|
||||
public void initialize(ValidOAuthConfiguration constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(OAuthConfiguration value,
|
||||
ConstraintValidatorContext context) {
|
||||
boolean valid = true;
|
||||
if (value.client != null && value.client.autoapprove != null) {
|
||||
if (value.clients != null) {
|
||||
context.buildConstraintViolationWithTemplate(
|
||||
"Please use oauth.clients to specifiy autoapprove not client.autoapprove")
|
||||
.addConstraintViolation();
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Documented
|
||||
@Target({ ElementType.FIELD })
|
||||
@Retention(RUNTIME)
|
||||
|
@ -332,6 +269,18 @@ public class RelaxedDataBinderTests {
|
|||
}
|
||||
}
|
||||
|
||||
public static class TargetWithNestedList {
|
||||
private List<String> nested;
|
||||
|
||||
public List<String> getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
public void setNested(List<String> nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TargetWithNestedObject {
|
||||
private VanillaTarget nested;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user