Numerous changes to actuator

Numerous changes to the actuator project, including:
- Specific Endpoint interface
- Spring MVC/Enpoint adapter
- Management server context changes
- Consistent auto-configuration class naming
- Auto-configuration ordering
- Javadoc, code formatting and tests
pull/2/merge
Phillip Webb 12 years ago
parent dd69d0f660
commit 8c347fc99b

@ -247,16 +247,11 @@ can be used to specify
on an internal or ops-facing network, for instance, or to only on an internal or ops-facing network, for instance, or to only
listen for connections from localhost (by specifying "127.0.0.1") listen for connections from localhost (by specifying "127.0.0.1")
* The context root of the management endpoints (TODO: does this work?) * The context root of the management endpoints
The `EndpointsProperties` are also bound, and you can use those to
change the paths of the management endpoints, e.g.
endpoints.error.path: /errors/generic
## Error Handling ## Error Handling
The Actuator provides an `/error` endpoint by default that handles all The Actuator provides an `/error` mapping by default that handles all
errors in a sensible way. If you want more specific error pages for errors in a sensible way. If you want more specific error pages for
some conditions, the embedded servlet containers support a uniform some conditions, the embedded servlet containers support a uniform
Java DSL for customizing the error handling. To do this you have to Java DSL for customizing the error handling. To do this you have to

@ -16,80 +16,118 @@
package org.springframework.bootstrap.actuate.audit; package org.springframework.bootstrap.actuate.audit;
import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.util.Assert;
/** /**
* A value object representing an audit event: at a particular time, a particular user or * A value object representing an audit event: at a particular time, a particular user or
* agent carried out an action of a particular type. This object records the details of * agent carried out an action of a particular type. This object records the details of
* such an event. * such an event.
* *
* <p>
* Users can inject a {@link AuditEventRepository} to publish their own events or
* alternatively use Springs {@link AuthenticationEventPublisher} (usually obtained by
* implementing {@link ApplicationEventPublisherAware}).
*
* @author Dave Syer * @author Dave Syer
* @see AuditEventRepository
*/ */
public class AuditEvent { public class AuditEvent implements Serializable {
private final Date timestamp;
private final String principal;
final private Date timestamp; private final String type;
final private String principal;
final private String type; private final Map<String, Object> data;
final private Map<String, Object> data;
/** /**
* Create a new audit event for the current time from data provided as name-value * Create a new audit event for the current time.
* pairs * @param principal The user principal responsible
* @param type the event type
* @param data The event data
*/ */
public AuditEvent(String principal, String type, String... data) { public AuditEvent(String principal, String type, Map<String, Object> data) {
this(new Date(), principal, type, convert(data)); this(new Date(), principal, type, data);
} }
/** /**
* Create a new audit event for the current time * Create a new audit event for the current time from data provided as name-value
* pairs
* @param principal The user principal responsible
* @param type the event type
* @param data The event data in the form 'key=value' or simply 'key'
*/ */
public AuditEvent(String principal, String type, Map<String, Object> data) { public AuditEvent(String principal, String type, String... data) {
this(new Date(), principal, type, data); this(new Date(), principal, type, convert(data));
} }
/** /**
* Create a new audit event. * Create a new audit event.
* @param timestamp The date/time of the event
* @param principal The user principal responsible
* @param type the event type
* @param data The event data
*/ */
public AuditEvent(Date timestamp, String principal, String type, public AuditEvent(Date timestamp, String principal, String type,
Map<String, Object> data) { Map<String, Object> data) {
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(type, "Type must not be null");
this.timestamp = timestamp; this.timestamp = timestamp;
this.principal = principal; this.principal = principal;
this.type = type; this.type = type;
this.data = Collections.unmodifiableMap(data); this.data = Collections.unmodifiableMap(data);
} }
private static Map<String, Object> convert(String[] data) {
Map<String, Object> result = new HashMap<String, Object>();
for (String entry : data) {
if (entry.contains("=")) {
int index = entry.indexOf("=");
result.put(entry.substring(0, index), entry.substring(index + 1));
} else {
result.put(entry, null);
}
}
return result;
}
/**
* Returns the date/time that the even was logged.
*/
public Date getTimestamp() { public Date getTimestamp() {
return this.timestamp; return this.timestamp;
} }
/**
* Returns the user principal responsible for the event or {@code null}.
*/
public String getPrincipal() { public String getPrincipal() {
return this.principal; return this.principal;
} }
/**
* Returns the type of event.
*/
public String getType() { public String getType() {
return this.type; return this.type;
} }
/**
* Returns the event data.
*/
public Map<String, Object> getData() { public Map<String, Object> getData() {
return this.data; return this.data;
} }
private static Map<String, Object> convert(String[] data) {
Map<String, Object> result = new HashMap<String, Object>();
for (String entry : data) {
if (entry.contains("=")) {
int index = entry.indexOf("=");
result.put(entry.substring(0, index), entry.substring(index + 1));
} else {
result.put(entry, null);
}
}
return result;
}
@Override @Override
public String toString() { public String toString() {
return "AuditEvent [timestamp=" + this.timestamp + ", principal=" return "AuditEvent [timestamp=" + this.timestamp + ", principal="

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.audit; package org.springframework.bootstrap.actuate.audit;
import java.util.ArrayList; import java.util.ArrayList;

@ -16,11 +16,15 @@
package org.springframework.bootstrap.actuate.audit.listener; package org.springframework.bootstrap.actuate.audit.listener;
import java.util.Date;
import java.util.Map;
import org.springframework.bootstrap.actuate.audit.AuditEvent; import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
import org.springframework.util.Assert;
/** /**
* {@link ApplicationEvent} to encapsulate {@link AuditEvent}s. * Spring {@link ApplicationEvent} to encapsulate {@link AuditEvent}s.
* *
* @author Dave Syer * @author Dave Syer
*/ */
@ -29,10 +33,41 @@ public class AuditApplicationEvent extends ApplicationEvent {
private AuditEvent auditEvent; private AuditEvent auditEvent;
/** /**
* Create a new {@link AuditApplicationEvent} that wraps a newly created
* {@link AuditEvent}.
* @see AuditEvent#AuditEvent(String, String, Map)
*/
public AuditApplicationEvent(String principal, String type, Map<String, Object> data) {
this(new AuditEvent(principal, type, data));
}
/**
* Create a new {@link AuditApplicationEvent} that wraps a newly created
* {@link AuditEvent}.
* @see AuditEvent#AuditEvent(String, String, String...)
*/
public AuditApplicationEvent(String principal, String type, String... data) {
this(new AuditEvent(principal, type, data));
}
/**
* Create a new {@link AuditApplicationEvent} that wraps a newly created
* {@link AuditEvent}.
* @see AuditEvent#AuditEvent(Date, String, String, Map)
*/
public AuditApplicationEvent(Date timestamp, String principal, String type,
Map<String, Object> data) {
this(new AuditEvent(timestamp, principal, type, data));
}
/**
* Create a new {@link AuditApplicationEvent} that wraps the specified
* {@link AuditEvent}.
* @param auditEvent the source of this event * @param auditEvent the source of this event
*/ */
public AuditApplicationEvent(AuditEvent auditEvent) { public AuditApplicationEvent(AuditEvent auditEvent) {
super(auditEvent); super(auditEvent);
Assert.notNull(auditEvent, "AuditEvent must not be null");
this.auditEvent = auditEvent; this.auditEvent = auditEvent;
} }

@ -23,7 +23,8 @@ import org.springframework.bootstrap.actuate.audit.AuditEventRepository;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
/** /**
* {@link ApplicationListener} for {@link AuditEvent}s. * {@link ApplicationListener} that listens for {@link AuditEvent}s and stores them in a
* {@link AuditEventRepository}.
* *
* @author Dave Syer * @author Dave Syer
*/ */

@ -1,59 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.springframework.bootstrap.actuate.properties.EndpointsProperties;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for service apps.
*
* @author Dave Syer
*/
@Configuration
@Import({ ActuatorWebConfiguration.class, MetricRepositoryConfiguration.class,
ErrorConfiguration.class, TraceFilterConfiguration.class,
MetricFilterConfiguration.class, AuditConfiguration.class })
public class ActuatorAutoConfiguration {
// ServerProperties has to be declared in a non-conditional bean, so that it gets
// added to the context early enough
@EnableConfigurationProperties
public static class ServerPropertiesConfiguration {
@ConditionalOnMissingBean(ManagementServerProperties.class)
@Bean(name = "org.springframework.bootstrap.actuate.properties.ManagementServerProperties")
public ManagementServerProperties managementServerProperties() {
return new ManagementServerProperties();
}
@Bean
@ConditionalOnMissingBean(EndpointsProperties.class)
public EndpointsProperties endpointsProperties() {
return new EndpointsProperties();
}
}
}

@ -1,55 +0,0 @@
/*
* 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.actuate.autoconfigure;
import java.util.List;
import javax.servlet.Servlet;
import org.springframework.bootstrap.autoconfigure.web.WebMvcAutoConfiguration.WebMvcConfiguration;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* {@link WebMvcConfiguration} for actuator.
*
* @author Dave Syer
*/
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@Configuration
public class ActuatorWebConfiguration extends DelegatingWebMvcConfiguration {
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
addDefaultHttpMessageConverters(converters);
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
jacksonConverter.getObjectMapper().disable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
super.configureMessageConverters(converters);
}
}

@ -17,6 +17,7 @@
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.bootstrap.actuate.audit.AuditEventRepository; import org.springframework.bootstrap.actuate.audit.AuditEventRepository;
import org.springframework.bootstrap.actuate.audit.InMemoryAuditEventRepository; import org.springframework.bootstrap.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.bootstrap.actuate.audit.listener.AuditListener; import org.springframework.bootstrap.actuate.audit.listener.AuditListener;
@ -24,26 +25,21 @@ import org.springframework.bootstrap.actuate.security.AuthenticationAuditListene
import org.springframework.bootstrap.actuate.security.AuthorizationAuditListener; import org.springframework.bootstrap.actuate.security.AuthorizationAuditListener;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for {@link AuditEvent}s.
*
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @Configuration
public class AuditConfiguration { public class AuditAutoConfiguration {
@Autowired(required = false) @Autowired(required = false)
private AuditEventRepository auditEventRepository = new InMemoryAuditEventRepository(); private AuditEventRepository auditEventRepository = new InMemoryAuditEventRepository();
@ConditionalOnMissingBean(AuditEventRepository.class)
protected static class AuditEventRepositoryConfiguration {
@Bean
public AuditEventRepository auditEventRepository() throws Exception {
return new InMemoryAuditEventRepository();
}
}
@Bean @Bean
public AuditListener auditListener() throws Exception { public AuditListener auditListener() throws Exception {
return new AuditListener(this.auditEventRepository); return new AuditListener(this.auditEventRepository);
@ -61,4 +57,12 @@ public class AuditConfiguration {
return new AuthorizationAuditListener(); return new AuthorizationAuditListener();
} }
@ConditionalOnMissingBean(AuditEventRepository.class)
protected static class AuditEventRepositoryConfiguration {
@Bean
public AuditEventRepository auditEventRepository() throws Exception {
return new InMemoryAuditEventRepository();
}
}
} }

@ -1,41 +0,0 @@
/*
* 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.actuate.autoconfigure;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* A bean with this annotation will only be instantiated in the management context,
* whether that is the current context or a child context. Using this feature makes it
* easy to have a single set of configuration beans for both contexts and be able to
* switch the management features to a child context externally. Very useful if (for
* instance) you want management endpoints but open, or differently secured public facing
* endpoints.
*
* @author Dave Syer
*/
@Conditional(OnManagementContextCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ConditionalOnManagementContext {
}

@ -13,20 +13,32 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import javax.servlet.Servlet;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.actuate.endpoint.info.InfoEndpoint; import org.springframework.bootstrap.actuate.endpoint.BeansEndpoint;
import org.springframework.bootstrap.actuate.endpoint.DumpEndpoint;
import org.springframework.bootstrap.actuate.endpoint.Endpoint;
import org.springframework.bootstrap.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.bootstrap.actuate.endpoint.HealthEndpoint;
import org.springframework.bootstrap.actuate.endpoint.InfoEndpoint;
import org.springframework.bootstrap.actuate.endpoint.MetricsEndpoint;
import org.springframework.bootstrap.actuate.endpoint.PublicMetrics;
import org.springframework.bootstrap.actuate.endpoint.ShutdownEndpoint;
import org.springframework.bootstrap.actuate.endpoint.TraceEndpoint;
import org.springframework.bootstrap.actuate.endpoint.VanillaPublicMetrics;
import org.springframework.bootstrap.actuate.health.HealthIndicator;
import org.springframework.bootstrap.actuate.health.VanillaHealthIndicator;
import org.springframework.bootstrap.actuate.metrics.InMemoryMetricRepository;
import org.springframework.bootstrap.actuate.metrics.MetricRepository;
import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.bootstrap.bind.PropertiesConfigurationFactory; import org.springframework.bootstrap.bind.PropertiesConfigurationFactory;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -35,39 +47,90 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.DispatcherServlet;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for /info endpoint. * {@link EnableAutoConfiguration Auto-configuration} for common management
* {@link Endpoint}s.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
@Configuration @Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) public class EndpointAutoConfiguration {
@ConditionalOnMissingBean({ InfoEndpoint.class })
public class InfoConfiguration { @Autowired(required = false)
private HealthIndicator<? extends Object> healthIndicator = new VanillaHealthIndicator();
@Autowired @Autowired
private InfoPropertiesConfiguration properties; private InfoPropertiesConfiguration properties;
@Autowired(required = false)
private MetricRepository metricRepository = new InMemoryMetricRepository();
@Autowired(required = false)
private PublicMetrics metrics;
@Autowired(required = false)
private TraceRepository traceRepository = new InMemoryTraceRepository();
@Bean
@ConditionalOnMissingBean
public EnvironmentEndpoint environmentEndpoint() {
return new EnvironmentEndpoint();
}
@Bean
@ConditionalOnMissingBean
public HealthEndpoint<Object> healthEndpoint() {
return new HealthEndpoint<Object>(this.healthIndicator);
}
@Bean
@ConditionalOnMissingBean
public BeansEndpoint beansEndpoint() {
return new BeansEndpoint();
}
@Bean @Bean
protected Map<String, Object> applicationInfo() throws Exception { @ConditionalOnMissingBean
public InfoEndpoint infoEndpoint() throws Exception {
LinkedHashMap<String, Object> info = new LinkedHashMap<String, Object>(); LinkedHashMap<String, Object> info = new LinkedHashMap<String, Object>();
info.putAll(this.properties.infoMap()); info.putAll(this.properties.infoMap());
GitInfo gitInfo = this.properties.gitInfo(); GitInfo gitInfo = this.properties.gitInfo();
if (gitInfo.getBranch() != null) { if (gitInfo.getBranch() != null) {
info.put("git", gitInfo); info.put("git", gitInfo);
} }
return info; return new InfoEndpoint(info);
} }
@Bean @Bean
public InfoEndpoint infoEndpoint() throws Exception { @ConditionalOnMissingBean
return new InfoEndpoint(applicationInfo()); public MetricsEndpoint metricsEndpoint() {
if (this.metrics == null) {
this.metrics = new VanillaPublicMetrics(this.metricRepository);
}
return new MetricsEndpoint(this.metrics);
}
@Bean
@ConditionalOnMissingBean
public TraceEndpoint traceEndpoint() {
return new TraceEndpoint(this.traceRepository);
} }
@Component @Bean
@ConditionalOnMissingBean
public DumpEndpoint dumpEndpoint() {
return new DumpEndpoint();
}
@Bean
@ConditionalOnMissingBean
public ShutdownEndpoint shutdownEndpoint() {
return new ShutdownEndpoint();
}
@Configuration
protected static class InfoPropertiesConfiguration { protected static class InfoPropertiesConfiguration {
@Autowired @Autowired
@ -136,4 +199,5 @@ public class InfoConfiguration {
} }
} }
} }
} }

@ -0,0 +1,163 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Servlet;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.endpoint.Endpoint;
import org.springframework.bootstrap.actuate.endpoint.mvc.EndpointHandlerAdapter;
import org.springframework.bootstrap.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.bootstrap.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.bootstrap.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.bootstrap.context.annotation.AutoConfigureAfter;
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.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.bootstrap.properties.ServerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} to enable Spring MVC to handle
* {@link Endpoint} requests. If the {@link ManagementServerProperties} specifies a
* different port to {@link ServerProperties} a new child context is created, otherwise it
* is assumed that endpoint requests will be mapped and handled via an already registered
* {@link DispatcherServlet}.
*
* @author Dave Syer
* @author Phillip Webb
*/
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent> {
private static final Integer DISABLED_PORT = Integer.valueOf(0);
private ApplicationContext applicationContext;
@Autowired(required = false)
private ServerProperties serverProperties = new ServerProperties();
@Autowired(required = false)
private ManagementServerProperties managementServerProperties = new ManagementServerProperties();
@Bean
@ConditionalOnMissingBean
public EndpointHandlerMapping endpointHandlerMapping() {
EndpointHandlerMapping mapping = new EndpointHandlerMapping();
mapping.setDisabled(ManagementServerPort.get(this.applicationContext) != ManagementServerPort.SAME);
mapping.setPrefix(this.managementServerProperties.getContextPath());
return mapping;
}
@Bean
@ConditionalOnMissingBean
public EndpointHandlerAdapter endpointHandlerAdapter() {
return new EndpointHandlerAdapter();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
if (ManagementServerPort.get(this.applicationContext) == ManagementServerPort.DIFFERENT) {
createChildManagementContext();
}
}
}
private void createChildManagementContext() {
final AnnotationConfigEmbeddedWebApplicationContext childContext = new AnnotationConfigEmbeddedWebApplicationContext();
childContext.setParent(this.applicationContext);
childContext.setId(this.applicationContext.getId() + ":management");
// Register the ManagementServerChildContextConfiguration first followed
// by various specific AutoConfiguration classes. NOTE: The child context
// is intentionally not completely auto-configured.
childContext.register(EndpointWebMvcChildContextConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class);
// Ensure close on the parent also closes the child
if (this.applicationContext instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) this.applicationContext)
.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (event.getApplicationContext() == EndpointWebMvcAutoConfiguration.this.applicationContext) {
childContext.close();
}
}
});
}
childContext.refresh();
}
private enum ManagementServerPort {
DISABLE, SAME, DIFFERENT;
public static ManagementServerPort get(BeanFactory beanFactory) {
ServerProperties serverProperties;
try {
serverProperties = beanFactory.getBean(ServerProperties.class);
} catch (NoSuchBeanDefinitionException ex) {
serverProperties = new ServerProperties();
}
ManagementServerProperties managementServerProperties;
try {
managementServerProperties = beanFactory
.getBean(ManagementServerProperties.class);
} catch (NoSuchBeanDefinitionException ex) {
managementServerProperties = new ManagementServerProperties();
}
if (DISABLED_PORT.equals(managementServerProperties.getPort())) {
return DISABLE;
}
return managementServerProperties.getPort() == null
|| serverProperties.getPort() == managementServerProperties.getPort() ? SAME
: DIFFERENT;
}
};
}

@ -0,0 +1,98 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Filter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.endpoint.mvc.EndpointHandlerAdapter;
import org.springframework.bootstrap.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.context.annotation.ConditionalOnBean;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.EnableWebSecurity;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
/**
* Configuration for triggered from {@link EndpointWebMvcAutoConfiguration} when a new
* {@link EmbeddedServletContainer} running on a different port is required.
*
* @see EndpointWebMvcAutoConfiguration
*/
@Configuration
public class EndpointWebMvcChildContextConfiguration implements
EmbeddedServletContainerCustomizer {
@Autowired
private ManagementServerProperties managementServerProperties;
@Override
public void customize(ConfigurableEmbeddedServletContainerFactory factory) {
factory.setPort(this.managementServerProperties.getPort());
factory.setAddress(this.managementServerProperties.getAddress());
factory.setContextPath(this.managementServerProperties.getContextPath());
}
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// Ensure the parent configuration does not leak down to us
dispatcherServlet.setDetectAllHandlerAdapters(false);
dispatcherServlet.setDetectAllHandlerExceptionResolvers(false);
dispatcherServlet.setDetectAllHandlerMappings(false);
dispatcherServlet.setDetectAllViewResolvers(false);
return dispatcherServlet;
}
@Bean
public HandlerMapping handlerMapping() {
return new EndpointHandlerMapping();
}
@Bean
public HandlerAdapter handlerAdapter() {
return new EndpointHandlerAdapter();
}
@Configuration
@ConditionalOnClass({ EnableWebSecurity.class, Filter.class })
public static class EndpointWebMvcChildContextSecurityConfiguration {
// FIXME reuse of security filter here is not good. What if totally different
// security config is required. Perhaps we can just drop it on the management
// port?
@Bean
@ConditionalOnBean(name = "springSecurityFilterChain")
public Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) {
BeanFactory parent = beanFactory.getParentBeanFactory();
return parent.getBean("springSecurityFilterChain", Filter.class);
}
}
}

@ -1,49 +0,0 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Servlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.endpoint.env.EnvEndpoint;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} for /metrics endpoint.
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnMissingBean({ EnvEndpoint.class })
public class EnvConfiguration {
@Autowired
private ConfigurableEnvironment environment;
@Bean
public EnvEndpoint envEndpoint() {
return new EnvEndpoint(this.environment);
}
}

@ -19,31 +19,33 @@ package org.springframework.bootstrap.actuate.autoconfigure;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.actuate.endpoint.error.ErrorEndpoint; import org.springframework.bootstrap.actuate.web.BasicErrorController;
import org.springframework.bootstrap.actuate.web.ErrorController;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; 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.context.embedded.ConfigurableEmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.bootstrap.context.embedded.ErrorPage; import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.context.annotation.Import;
/** /**
* Configuration for injecting externalized properties into the container (e.g. tomcat). * {@link EnableAutoConfiguration Auto-configuration} to render errors via a MVC error
* controller.
* *
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnClass({ Servlet.class }) public class ErrorMvcAutoConfiguration implements EmbeddedServletContainerCustomizer {
@Import(InfoConfiguration.class)
public class ErrorConfiguration implements EmbeddedServletContainerCustomizer {
@Value("${endpoints.error.path:/error}") @Value("${error.path:/error}")
private String errorPath = "/error"; private String errorPath = "/error";
@Bean @Bean
public ErrorEndpoint errorEndpoint() { @ConditionalOnMissingBean(ErrorController.class)
return new ErrorEndpoint(); public BasicErrorController basicErrorController() {
return new BasicErrorController();
} }
@Override @Override

@ -1,50 +0,0 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Servlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.endpoint.health.HealthEndpoint;
import org.springframework.bootstrap.actuate.endpoint.health.HealthIndicator;
import org.springframework.bootstrap.actuate.endpoint.health.VanillaHealthIndicator;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} for /health endpoint.
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnMissingBean({ HealthEndpoint.class })
public class HealthConfiguration {
@Autowired(required = false)
private HealthIndicator<? extends Object> healthIndicator = new VanillaHealthIndicator();
@Bean
public HealthEndpoint<? extends Object> healthEndpoint() {
return new HealthEndpoint<Object>(this.healthIndicator);
}
}

@ -1,161 +0,0 @@
/*
* 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.actuate.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.autoconfigure.ManagementAutoConfiguration.RememberManagementConfiguration;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.autoconfigure.web.EmbeddedContainerCustomizerConfiguration;
import org.springframework.bootstrap.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.bootstrap.properties.ServerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
/**
* @author Dave Syer
*/
@Configuration
@Conditional(RememberManagementConfiguration.class)
@Import(ManagementEndpointsRegistration.class)
public class ManagementAutoConfiguration implements ApplicationContextAware {
public static final String MEMO_BEAN_NAME = ManagementAutoConfiguration.class
.getName() + ".MEMO";
private ApplicationContext parent;
private ConfigurableApplicationContext context;
@Autowired
private ServerProperties configuration = new ServerProperties();
@Autowired
private ManagementServerProperties management = new ManagementServerProperties();
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.parent = applicationContext;
}
@Bean
public ApplicationListener<ContextClosedEvent> managementContextClosedListener() {
return new ApplicationListener<ContextClosedEvent>() {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (event.getSource() != ManagementAutoConfiguration.this.parent) {
return;
}
if (ManagementAutoConfiguration.this.context != null) {
ManagementAutoConfiguration.this.context.close();
}
}
};
}
@Bean
public ApplicationListener<ContextRefreshedEvent> managementContextRefeshedListener() {
return new ApplicationListener<ContextRefreshedEvent>() {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getSource() != ManagementAutoConfiguration.this.parent) {
return;
}
if (ManagementAutoConfiguration.this.configuration.getPort() != ManagementAutoConfiguration.this.management
.getPort()) {
AnnotationConfigEmbeddedWebApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext();
context.setParent(ManagementAutoConfiguration.this.parent);
context.register(assembleConfigClasses(ManagementAutoConfiguration.this.parent));
context.refresh();
ManagementAutoConfiguration.this.context = context;
}
}
};
}
protected static class RememberManagementConfiguration implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
int serverPort = environment.getProperty("server.port", Integer.class, 8080);
int managementPort = environment.getProperty("management.port",
Integer.class, serverPort);
if (!context.getBeanFactory().containsSingleton(MEMO_BEAN_NAME)) {
context.getBeanFactory().registerSingleton(MEMO_BEAN_NAME,
managementPort > 0);
}
return managementPort > 0;
}
}
protected Class<?>[] assembleConfigClasses(BeanFactory parent) {
// Some basic context configuration that all child context need
ArrayList<Class<?>> configs = new ArrayList<Class<?>>(Arrays.<Class<?>> asList(
EmbeddedContainerCustomizerConfiguration.class,
ManagementServerConfiguration.class, ErrorConfiguration.class));
String managementContextBeanName = OnManagementContextCondition.class.getName();
// Management context only beans pulled in from the deferred list in the parent
// context
if (parent.containsBean(managementContextBeanName)) {
String[] names = parent.getBean(managementContextBeanName, String[].class);
for (String name : names) {
try {
configs.add(ClassUtils.forName(name,
ManagementAutoConfiguration.this.parent.getClassLoader()));
} catch (ClassNotFoundException e) {
throw new BeanCreationException(managementContextBeanName,
"Class not found: " + name);
}
}
}
return configs.toArray(new Class<?>[configs.size()]);
}
}

@ -13,32 +13,32 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import javax.servlet.Servlet; import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.bootstrap.actuate.endpoint.shutdown.ShutdownEndpoint; import org.springframework.bootstrap.context.annotation.AutoConfigureAfter;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for /shutdown endpoint. * {@link EnableAutoConfiguration Auto-configuration} for the
* {@link ManagementServerPropertiesAutoConfiguration} bean.
* *
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) @AutoConfigureAfter(ServerPropertiesAutoConfiguration.class)
@ConditionalOnMissingBean({ ShutdownEndpoint.class }) @EnableConfigurationProperties
public class ShutdownConfiguration { public class ManagementServerPropertiesAutoConfiguration {
@Bean @Bean(name = "org.springframework.bootstrap.actuate.properties.ManagementServerProperties")
public ShutdownEndpoint shutdownEndpoint() { @ConditionalOnMissingBean
return new ShutdownEndpoint(); public ManagementServerProperties serverProperties() {
return new ManagementServerProperties();
} }
} }

@ -30,85 +30,93 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.metrics.CounterService; import org.springframework.bootstrap.actuate.metrics.CounterService;
import org.springframework.bootstrap.actuate.metrics.GaugeService; import org.springframework.bootstrap.actuate.metrics.GaugeService;
import org.springframework.bootstrap.context.annotation.AutoConfigureAfter;
import org.springframework.bootstrap.context.annotation.ConditionalOnBean; import org.springframework.bootstrap.context.annotation.ConditionalOnBean;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.UrlPathHelper;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for service apps. * {@link EnableAutoConfiguration Auto-configuration} that records Servlet interactions
* with a {@link CounterService} and {@link GaugeService}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
@Configuration @Configuration
// FIXME: make this conditional @ConditionalOnBean({ CounterService.class, GaugeService.class })
// @ConditionalOnBean({ CounterService.class, GaugeService.class })
@ConditionalOnClass({ Servlet.class }) @ConditionalOnClass({ Servlet.class })
public class MetricFilterConfiguration { @AutoConfigureAfter(MetricRepositoryAutoConfiguration.class)
public class MetricFilterAutoConfiguration {
@Autowired(required = false) private static final int UNDEFINED_HTTP_STATUS = 999;
@Autowired
private CounterService counterService; private CounterService counterService;
@Autowired(required = false) @Autowired
private GaugeService gaugeService; private GaugeService gaugeService;
@Bean @Bean
@ConditionalOnBean({ CounterService.class, GaugeService.class })
public Filter metricFilter() { public Filter metricFilter() {
return new CounterServiceFilter(); return new MetricsFilter();
} }
/** /**
* Filter that counts requests and measures processing times. * Filter that counts requests and measures processing times.
*
* @author Dave Syer
*
*/ */
@Order(Integer.MIN_VALUE) @Order(Ordered.HIGHEST_PRECEDENCE)
// TODO: parameterize the order (ideally it runs before any other filter) private final class MetricsFilter extends GenericFilterBean {
private final class CounterServiceFilter extends GenericFilterBean {
// FIXME parameterize the order (ideally it runs before any other filter)
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException { FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request; if ((request instanceof HttpServletRequest)
HttpServletResponse servletResponse = (HttpServletResponse) response; && (response instanceof HttpServletResponse)) {
UrlPathHelper helper = new UrlPathHelper(); doFilter((HttpServletRequest) request, (HttpServletResponse) response,
String suffix = helper.getPathWithinApplication(servletRequest); chain);
int status = 999; } else {
long t0 = System.currentTimeMillis();
try {
chain.doFilter(request, response); chain.doFilter(request, response);
} finally {
try {
status = servletResponse.getStatus();
} catch (Exception e) {
// ignore
}
set("response", suffix, System.currentTimeMillis() - t0);
increment("status." + status, suffix);
} }
} }
private void increment(String prefix, String suffix) { public void doFilter(HttpServletRequest request, HttpServletResponse response,
if (MetricFilterConfiguration.this.counterService != null) { FilterChain chain) throws IOException, ServletException {
String key = getKey(prefix + suffix); UrlPathHelper helper = new UrlPathHelper();
MetricFilterConfiguration.this.counterService.increment(key); String suffix = helper.getPathWithinApplication(request);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
chain.doFilter(request, response);
} finally {
stopWatch.stop();
String gaugeKey = getKey("response" + suffix);
MetricFilterAutoConfiguration.this.gaugeService.set(gaugeKey,
stopWatch.getTotalTimeMillis());
String counterKey = getKey("status." + getStatus(response) + suffix);
MetricFilterAutoConfiguration.this.counterService.increment(counterKey);
} }
} }
private void set(String prefix, String suffix, double value) { private int getStatus(HttpServletResponse response) {
if (MetricFilterConfiguration.this.gaugeService != null) { try {
String key = getKey(prefix + suffix); return response.getStatus();
MetricFilterConfiguration.this.gaugeService.set(key, value); } catch (Exception e) {
return UNDEFINED_HTTP_STATUS;
} }
} }
private String getKey(String string) { private String getKey(String string) {
String value = string.replace("/", "."); // graphite compatible metric names // graphite compatible metric names
String value = string.replace("/", ".");
value = value.replace("..", "."); value = value.replace("..", ".");
if (value.endsWith(".")) { if (value.endsWith(".")) {
value = value + "root"; value = value + "root";

@ -33,22 +33,22 @@ import org.springframework.context.annotation.Configuration;
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @Configuration
public class MetricRepositoryConfiguration { public class MetricRepositoryAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean({ CounterService.class }) @ConditionalOnMissingBean
public CounterService counterService() { public CounterService counterService() {
return new DefaultCounterService(metricRepository()); return new DefaultCounterService(metricRepository());
} }
@Bean @Bean
@ConditionalOnMissingBean({ GaugeService.class }) @ConditionalOnMissingBean
public GaugeService gaugeService() { public GaugeService gaugeService() {
return new DefaultGaugeService(metricRepository()); return new DefaultGaugeService(metricRepository());
} }
@Bean @Bean
@ConditionalOnMissingBean({ MetricRepository.class }) @ConditionalOnMissingBean
protected MetricRepository metricRepository() { protected MetricRepository metricRepository() {
return new InMemoryMetricRepository(); return new InMemoryMetricRepository();
} }

@ -1,57 +0,0 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Servlet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.endpoint.metrics.MetricsEndpoint;
import org.springframework.bootstrap.actuate.endpoint.metrics.PublicMetrics;
import org.springframework.bootstrap.actuate.endpoint.metrics.VanillaPublicMetrics;
import org.springframework.bootstrap.actuate.metrics.MetricRepository;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} for /metrics endpoint.
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnMissingBean({ MetricsEndpoint.class })
public class MetricsConfiguration {
@Autowired
private MetricRepository repository;
@Autowired(required = false)
private PublicMetrics metrics;
@Bean
public MetricsEndpoint metricsEndpoint() {
if (this.metrics == null) {
this.metrics = new VanillaPublicMetrics(this.repository);
}
return new MetricsEndpoint(this.metrics);
}
}

@ -1,109 +0,0 @@
/*
* 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.actuate.autoconfigure;
import java.util.Collection;
import java.util.HashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.bootstrap.context.annotation.ConditionLogUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
/**
* A condition that can determine if the bean it applies to is in the management context
* (the application context with the management endpoints).
*
* @author Dave Syer
* @see ConditionalOnManagementContext
*/
public class OnManagementContextCondition implements Condition {
private static Log logger = LogFactory.getLog(OnManagementContextCondition.class);
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String checking = ConditionLogUtils.getPrefix(logger, metadata);
Environment environment = context.getEnvironment();
int serverPort = environment.getProperty("server.port", Integer.class, 8080);
int managementPort = environment.getProperty("management.port", Integer.class,
serverPort);
// If there is no management context, the decision is easy (match=false)
boolean managementEnabled = managementPort > 0;
// The management context is the same as the parent context
boolean managementContextInParent = serverPort == managementPort;
// The current context is a child context with a management server
boolean managementChildContext = context.getBeanFactory().getBeanNamesForType(
ManagementServerConfiguration.class).length > 0;
// The management auto configuration either hasn't been added yet or has been
// added to the context and it is enabled
boolean containsManagementBeans = !context.getBeanFactory().containsSingleton(
ManagementAutoConfiguration.MEMO_BEAN_NAME)
|| (Boolean) context.getBeanFactory().getSingleton(
ManagementAutoConfiguration.MEMO_BEAN_NAME);
boolean result = managementEnabled
&& ((managementContextInParent && containsManagementBeans) || managementChildContext);
if (logger.isDebugEnabled()) {
if (!managementEnabled) {
logger.debug(checking + "Management context is disabled");
} else {
logger.debug(checking + "Management context is in parent: "
+ managementContextInParent + " (management.port="
+ managementPort + ", server.port=" + serverPort + ")");
logger.debug(checking + "In management child context: "
+ managementChildContext);
logger.debug(checking + "In management parent context: "
+ containsManagementBeans);
logger.debug(checking + "Finished matching and result is matches="
+ result);
}
}
if (!result && metadata instanceof AnnotationMetadata) {
Collection<String> beanClasses = getManagementContextClasses(context
.getBeanFactory());
beanClasses.add(((AnnotationMetadata) metadata).getClassName());
}
return result;
}
private Collection<String> getManagementContextClasses(
ConfigurableListableBeanFactory beanFactory) {
String name = OnManagementContextCondition.class.getName();
if (!beanFactory.containsSingleton(name)) {
beanFactory.registerSingleton(name, new HashSet<String>());
}
@SuppressWarnings("unchecked")
Collection<String> result = (Collection<String>) beanFactory.getSingleton(name);
return result;
}
}

@ -21,13 +21,17 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.properties.EndpointsProperties; import org.springframework.bootstrap.actuate.endpoint.Endpoint;
import org.springframework.bootstrap.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.bootstrap.actuate.properties.SecurityProperties; import org.springframework.bootstrap.actuate.properties.SecurityProperties;
import org.springframework.bootstrap.actuate.web.ErrorController;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties; import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
@ -37,48 +41,40 @@ import org.springframework.security.config.annotation.authentication.Authenticat
import org.springframework.security.config.annotation.web.EnableWebSecurity; import org.springframework.security.config.annotation.web.EnableWebSecurity;
import org.springframework.security.config.annotation.web.HttpConfiguration; import org.springframework.security.config.annotation.web.HttpConfiguration;
import org.springframework.security.config.annotation.web.WebSecurityBuilder; import org.springframework.security.config.annotation.web.WebSecurityBuilder;
import org.springframework.security.config.annotation.web.WebSecurityBuilder.IgnoredRequestRegistry;
import org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
/** /**
* <p> * {@link EnableAutoConfiguration Auto-configuration} for security of a web application or
* Auto configuration for security of a web application or service. By default everything * service. By default everything is secured with HTTP Basic authentication except the
* is secured with HTTP Basic authentication except the
* {@link SecurityProperties#getIgnored() explicitly ignored} paths (defaults to * {@link SecurityProperties#getIgnored() explicitly ignored} paths (defaults to
* <code>/css&#47;**, /js&#47;**, /images&#47;**, &#47;**&#47;favicon.ico</code>). Many * <code>&#47;css&#47;**, &#47;js&#47;**, &#47;images&#47;**, &#47;**&#47;favicon.ico</code>
* aspects of the behaviour can be controller with {@link SecurityProperties} via * ). Many aspects of the behavior can be controller with {@link SecurityProperties} via
* externalized application properties (or via an bean definition of that type to set the * externalized application properties (or via an bean definition of that type to set the
* defaults). The user details for authentication are just placeholders * defaults). The user details for authentication are just placeholders
* <code>(username=user, * <code>(username=user,
* password=password)</code> but can easily be customized by providing a bean definition * password=password)</code> but can easily be customized by providing a bean definition
* of type {@link AuthenticationManager}. Also provides audit logging of authentication * of type {@link AuthenticationManager}. Also provides audit logging of authentication
* events. * events.
* </p>
* *
* <p> * <p>
* The framework {@link EndpointsProperties} configuration bean has explicitly * The framework {@link Endpoint}s (used to expose application information to operations)
* {@link EndpointsProperties#getSecurePaths() secure} and * include a {@link Endpoint#isSensitive() sensitive} configuration option which will be
* {@link EndpointsProperties#getOpenPaths() open} paths (by name) which are always * used as a security hint by the filter created here.
* respected by the filter created here. You can override the paths of those endpoints
* using application properties (e.g. <code>endpoints.info.path</code> is open, and
* <code>endpoints.metrics.path</code> is secure), but not the security aspects. The
* always secure paths are management endpoints that would be inadvisable to expose to all
* users.
* </p>
* *
* <p> * <p>
* Some common simple customizations: * Some common simple customizations:
* <ul> * <ul>
* <li>Switch off security completely and permanently: remove Spring Security from the * <li>Switch off security completely and permanently: remove Spring Security from the
* classpath</li> * classpath or {@link EnableAutoConfiguration#exclude() exclude} this configuration.</li>
* <li>Switch off security temporarily (e.g. for a dev environment): set * <li>Switch off security temporarily (e.g. for a dev environment): set
* <code>security.basic.enabled: false</code></li> * <code>security.basic.enabled: false</code></li>
* <li>Customize the user details: add an AuthenticationManager bean</li> * <li>Customize the user details: add an AuthenticationManager bean</li>
* <li>Add form login for user facing resources: add a * <li>Add form login for user facing resources: add a
* {@link WebSecurityConfigurerAdapter} and use {@link HttpConfiguration#formLogin()}</li> * {@link WebSecurityConfigurerAdapter} and use {@link HttpConfiguration#formLogin()}</li>
* </ul> * </ul>
* </p>
* *
* @author Dave Syer * @author Dave Syer
*/ */
@ -88,14 +84,14 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationEn
@EnableConfigurationProperties @EnableConfigurationProperties
public class SecurityAutoConfiguration { public class SecurityAutoConfiguration {
@ConditionalOnMissingBean(SecurityProperties.class)
@Bean(name = "org.springframework.bootstrap.actuate.properties.SecurityProperties") @Bean(name = "org.springframework.bootstrap.actuate.properties.SecurityProperties")
@ConditionalOnMissingBean
public SecurityProperties securityProperties() { public SecurityProperties securityProperties() {
return new SecurityProperties(); return new SecurityProperties();
} }
@Bean @Bean
@ConditionalOnMissingBean({ AuthenticationEventPublisher.class }) @ConditionalOnMissingBean
public AuthenticationEventPublisher authenticationEventPublisher() { public AuthenticationEventPublisher authenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher(); return new DefaultAuthenticationEventPublisher();
} }
@ -107,19 +103,24 @@ public class SecurityAutoConfiguration {
} }
// Give user-supplied filters a chance to be last in line // Give user-supplied filters a chance to be last in line
@Order(Integer.MAX_VALUE - 10) @Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class BoostrapWebSecurityConfigurerAdapter extends private static class BoostrapWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter { WebSecurityConfigurerAdapter {
private static final String[] NO_PATHS = new String[0];
@Autowired @Autowired
private SecurityProperties security; private SecurityProperties security;
@Autowired @Autowired(required = false)
private EndpointsProperties endpoints; private EndpointHandlerMapping endpointHandlerMapping;
@Autowired @Autowired
private AuthenticationEventPublisher authenticationEventPublisher; private AuthenticationEventPublisher authenticationEventPublisher;
@Autowired(required = false)
private ErrorController errorController;
@Override @Override
protected void configure(HttpConfiguration http) throws Exception { protected void configure(HttpConfiguration http) throws Exception {
@ -128,25 +129,22 @@ public class SecurityAutoConfiguration {
} }
if (this.security.getBasic().isEnabled()) { if (this.security.getBasic().isEnabled()) {
String[] paths = getSecurePaths(); String[] paths = getSecurePaths();
http.exceptionHandling().authenticationEntryPoint(entryPoint()).and() http.exceptionHandling().authenticationEntryPoint(entryPoint()).and()
.requestMatchers().antMatchers(paths); .requestMatchers().antMatchers(paths);
http.httpBasic().and().anonymous().disable(); http.httpBasic().and().anonymous().disable();
http.authorizeUrls().anyRequest() http.authorizeUrls().anyRequest()
.hasRole(this.security.getBasic().getRole()); .hasRole(this.security.getBasic().getRole());
} }
// No cookies for service endpoints by default // No cookies for service endpoints by default
http.sessionManagement().sessionCreationPolicy(this.security.getSessions()); http.sessionManagement().sessionCreationPolicy(this.security.getSessions());
} }
private String[] getSecurePaths() { private String[] getSecurePaths() {
List<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
for (String path : this.security.getBasic().getPath()) { for (String path : this.security.getBasic().getPath()) {
path = path == null ? "" : path.trim(); path = (path == null ? "" : path.trim());
if (path.equals("/**")) { if (path.equals("/**")) {
return new String[] { path }; return new String[] { path };
} }
@ -154,7 +152,8 @@ public class SecurityAutoConfiguration {
list.add(path); list.add(path);
} }
} }
list.addAll(Arrays.asList(this.endpoints.getSecurePaths())); // FIXME makes more sense to secure enpoints with a different role
list.addAll(Arrays.asList(getEndpointPaths(true)));
return list.toArray(new String[list.size()]); return list.toArray(new String[list.size()]);
} }
@ -166,8 +165,30 @@ public class SecurityAutoConfiguration {
@Override @Override
public void configure(WebSecurityBuilder builder) throws Exception { public void configure(WebSecurityBuilder builder) throws Exception {
builder.ignoring().antMatchers(this.security.getIgnored()) IgnoredRequestRegistry ignoring = builder.ignoring();
.antMatchers(this.endpoints.getOpenPaths()); ignoring.antMatchers(this.security.getIgnored());
ignoring.antMatchers(getEndpointPaths(false));
if (this.errorController != null) {
ignoring.antMatchers(this.errorController.getErrorPath());
}
}
private String[] getEndpointPaths(boolean secure) {
if (this.endpointHandlerMapping == null) {
return NO_PATHS;
}
// FIXME this will still open up paths on the server when a management port is
// being used.
List<Endpoint<?>> endpoints = this.endpointHandlerMapping.getEndpoints();
List<String> paths = new ArrayList<String>(endpoints.size());
for (Endpoint<?> endpoint : endpoints) {
if (endpoint.isSensitive() == secure) {
paths.add(endpoint.getPath());
}
}
return paths.toArray(new String[paths.size()]);
} }
@Override @Override

@ -1,74 +0,0 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Servlet;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.actuate.endpoint.trace.WebRequestLoggingFilter;
import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
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
public class TraceFilterConfiguration {
@Autowired(required = false)
private TraceRepository traceRepository = new InMemoryTraceRepository();
@ConditionalOnMissingBean(TraceRepository.class)
@Configuration
protected static class TraceRepositoryConfiguration {
@Bean
public TraceRepository traceRepository() {
return new InMemoryTraceRepository();
}
}
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
protected static class WebRequestLoggingFilterConfiguration {
@Autowired
private TraceRepository traceRepository;
@Value("${management.dump_requests:false}")
private boolean dumpRequests;
@Bean
public WebRequestLoggingFilter webRequestLoggingFilter(BeanFactory beanFactory) {
WebRequestLoggingFilter filter = new WebRequestLoggingFilter(
this.traceRepository);
filter.setDumpRequests(this.dumpRequests);
return filter;
}
}
}

@ -16,29 +16,25 @@
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import javax.servlet.Servlet; import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.bootstrap.actuate.endpoint.beans.BeansEndpoint;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for /beans endpoint. * {@link EnableAutoConfiguration Auto-configuration} for {@link TraceRepository tracing}.
* *
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) public class TraceRepositoryAutoConfiguration {
@ConditionalOnMissingBean({ BeansEndpoint.class })
public class BeansConfiguration {
@ConditionalOnMissingBean
@Bean @Bean
public BeansEndpoint beansEndpoint() { public TraceRepository traceRepository() {
return new BeansEndpoint(); return new InMemoryTraceRepository();
} }
} }

@ -13,37 +13,42 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.endpoint.trace.TraceEndpoints; import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.actuate.trace.TraceRepository; import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.bootstrap.actuate.trace.WebRequestTraceFilter;
import org.springframework.bootstrap.context.annotation.AutoConfigureAfter;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; 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.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for /trace endpoint. * {@link EnableAutoConfiguration Auto-configuration} for {@link WebRequestTraceFilter
* tracing}.
* *
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnMissingBean({ TraceEndpoints.class }) @AutoConfigureAfter(TraceRepositoryAutoConfiguration.class)
public class TraceConfiguration { public class TraceWebFilterAutoConfiguration {
@Autowired @Autowired
private TraceRepository traceRepository; private TraceRepository traceRepository;
@Value("${management.dump_requests:false}")
private boolean dumpRequests;
@Bean @Bean
public TraceEndpoints traceEndpoint() { public WebRequestTraceFilter webRequestLoggingFilter(BeanFactory beanFactory) {
return new TraceEndpoints(this.traceRepository); WebRequestTraceFilter filter = new WebRequestTraceFilter(this.traceRepository);
filter.setDumpRequests(this.dumpRequests);
return filter;
} }
} }

@ -0,0 +1,71 @@
/*
* 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.actuate.endpoint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.springframework.http.MediaType;
/**
* Abstract base for {@link Endpoint} implementations.
*
* @author Phillip Webb
*/
public abstract class AbstractEndpoint<T> implements Endpoint<T> {
private static final MediaType[] NO_MEDIA_TYPES = new MediaType[0];
@NotNull
@Pattern(regexp = "/[^/]*", message = "Path must start with /")
private String path;
private boolean sensitive;
public AbstractEndpoint(String path) {
this(path, true);
}
public AbstractEndpoint(String path, boolean sensitive) {
this.path = path;
this.sensitive = sensitive;
}
@Override
public String getPath() {
return this.path;
}
public void setPath(String path) {
this.path = path;
}
@Override
public boolean isSensitive() {
return this.sensitive;
}
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
@Override
public MediaType[] getProduces() {
return NO_MEDIA_TYPES;
}
}

@ -14,22 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.endpoint;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/** /**
* Convenient collector for all the management endpoints (stuff that goes in the * Tagging interface used to indicate that {@link Endpoint} that performs some action.
* management context whether it is a child context or not). * Allows mappings to refine the types of request supported.
* *
* @author Dave Syer * @author Phillip Webb
*/ */
@Configuration public interface ActionEndpoint<T> extends Endpoint<T> {
@ConditionalOnManagementContext
@Import({ MetricsConfiguration.class, HealthConfiguration.class,
ShutdownConfiguration.class, TraceConfiguration.class, BeansConfiguration.class,
EnvConfiguration.class })
public class ManagementEndpointsRegistration {
} }

@ -14,32 +14,34 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.beans; package org.springframework.bootstrap.actuate.endpoint;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.LiveBeansView; import org.springframework.context.support.LiveBeansView;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* Exposes JSON view of Spring beans. If the {@link Environment} contains a key setting * Exposes JSON view of Spring beans. If the {@link Environment} contains a key setting
* the {@link LiveBeansView#MBEAN_DOMAIN_PROPERTY_NAME} then all application contexts in * the {@link LiveBeansView#MBEAN_DOMAIN_PROPERTY_NAME} then all application contexts in
* the JVM will be shown (and the corresponding MBeans will be registered per the standard * the JVM will be shown (and the corresponding MBeans will be registered per the standard
* behaviour of LiveBeansView). Otherwise only the current application context. * behavior of LiveBeansView). Otherwise only the current application context.
* *
* @author Dave Syer * @author Dave Syer
*/ */
@Controller @ConfigurationProperties(name = "endpoints.beans", ignoreUnknownFields = false)
public class BeansEndpoint implements ApplicationContextAware { public class BeansEndpoint extends AbstractEndpoint<String> implements
ApplicationContextAware {
private LiveBeansView liveBeansView = new LiveBeansView(); private LiveBeansView liveBeansView = new LiveBeansView();
public BeansEndpoint() {
super("/beans");
}
@Override @Override
public void setApplicationContext(ApplicationContext context) throws BeansException { public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context.getEnvironment() if (context.getEnvironment()
@ -48,9 +50,13 @@ public class BeansEndpoint implements ApplicationContextAware {
} }
} }
@RequestMapping(value = "${endpoints.beans.path:/beans}", produces = "application/json") @Override
@ResponseBody public MediaType[] getProduces() {
public String error(HttpServletRequest request) { return new MediaType[] { MediaType.APPLICATION_JSON };
}
@Override
public String invoke() {
return this.liveBeansView.getSnapshotAsJson(); return this.liveBeansView.getSnapshotAsJson();
} }
} }

@ -14,44 +14,32 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.trace; package org.springframework.bootstrap.actuate.endpoint;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo; import java.lang.management.ThreadInfo;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.springframework.bootstrap.actuate.trace.Trace; import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* {@link Endpoint} to expose thread info.
*
* @author Dave Syer * @author Dave Syer
*/ */
@Controller @ConfigurationProperties(name = "endpoints.dump", ignoreUnknownFields = false)
public class TraceEndpoints { public class DumpEndpoint extends AbstractEndpoint<List<ThreadInfo>> {
private TraceRepository tracer;
/** /**
* @param tracer * Create a new {@link DumpEndpoint} instance.
*/ */
public TraceEndpoints(TraceRepository tracer) { public DumpEndpoint() {
super(); super("/dump");
this.tracer = tracer;
}
@RequestMapping("${endpoints.trace.path:/trace}")
@ResponseBody
public List<Trace> trace() {
return this.tracer.traces();
} }
@RequestMapping("${endpoints.dump.path:/dump}") @Override
@ResponseBody public List<ThreadInfo> invoke() {
public List<ThreadInfo> dump() {
return Arrays.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, return Arrays.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true,
true)); true));
} }

@ -0,0 +1,53 @@
/*
* 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.actuate.endpoint;
import org.springframework.http.MediaType;
/**
* An endpoint that can be used to expose useful information to operations. Usually
* exposed via Spring MVC but could also be exposed using some other technique.
*
* @author Phillip Webb
* @author Dave Syer
*/
public interface Endpoint<T> {
/**
* Returns the path of the endpoint. Must start with '/' and should not include
* wildcards.
*/
String getPath();
/**
* Returns if the endpoint is sensitive, i.e. may return data that the average user
* should not see. Mappings can use this as a security hint.
*/
boolean isSensitive();
/**
* Returns the {@link MediaType}s that this endpoint produces or {@code null}.
*/
MediaType[] getProduces();
/**
* Called to invoke the endpoint.
* @return the results of the invocation
*/
T invoke();
}

@ -14,36 +14,42 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.env; package org.springframework.bootstrap.actuate.endpoint;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Controller; import org.springframework.core.env.StandardEnvironment;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information.
*
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
@Controller @ConfigurationProperties(name = "endpoints.env", ignoreUnknownFields = false)
public class EnvEndpoint { public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> implements
EnvironmentAware {
private final ConfigurableEnvironment environment; private Environment environment;
public EnvEndpoint(ConfigurableEnvironment environment) { /**
this.environment = environment; * Create a new {@link EnvironmentEndpoint} instance.
*/
public EnvironmentEndpoint() {
super("/env");
} }
@RequestMapping("${endpoints.metrics.path:/env}") @Override
@ResponseBody public Map<String, Object> invoke() {
public Map<String, Object> env() {
Map<String, Object> result = new LinkedHashMap<String, Object>(); Map<String, Object> result = new LinkedHashMap<String, Object>();
for (PropertySource<?> source : this.environment.getPropertySources()) { for (PropertySource<?> source : getPropertySources()) {
if (source instanceof EnumerablePropertySource) { if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source; EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
Map<String, Object> map = new LinkedHashMap<String, Object>(); Map<String, Object> map = new LinkedHashMap<String, Object>();
@ -56,12 +62,12 @@ public class EnvEndpoint {
return result; return result;
} }
@RequestMapping("${endpoints.metrics.path:/env}/{name:[a-zA-Z0-9._-]+}") private Iterable<PropertySource<?>> getPropertySources() {
@ResponseBody if (this.environment != null
public Map<String, Object> env(@PathVariable String name) { && this.environment instanceof ConfigurableEnvironment) {
Map<String, Object> result = new LinkedHashMap<String, Object>(); return ((ConfigurableEnvironment) this.environment).getPropertySources();
result.put(name, sanitize(name, this.environment.getProperty(name))); }
return result; return new StandardEnvironment().getPropertySources();
} }
private Object sanitize(String name, Object object) { private Object sanitize(String name, Object object) {
@ -72,4 +78,9 @@ public class EnvEndpoint {
return object; return object;
} }
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
} }

@ -14,31 +14,35 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.health; package org.springframework.bootstrap.actuate.endpoint;
import org.springframework.stereotype.Controller; import org.springframework.bootstrap.actuate.health.HealthIndicator;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.util.Assert;
/** /**
* {@link Endpoint} to expose application health.
*
* @author Dave Syer * @author Dave Syer
*/ */
@Controller @ConfigurationProperties(name = "endpoints.health", ignoreUnknownFields = false)
public class HealthEndpoint<T> { public class HealthEndpoint<T> extends AbstractEndpoint<T> {
private HealthIndicator<? extends T> indicator; private HealthIndicator<? extends T> indicator;
/** /**
* @param indicator * Create a new {@link HealthIndicator} instance.
*
* @param indicator the health indicator
*/ */
public HealthEndpoint(HealthIndicator<? extends T> indicator) { public HealthEndpoint(HealthIndicator<? extends T> indicator) {
super(); super("/health", false);
Assert.notNull(indicator, "Indicator must not be null");
this.indicator = indicator; this.indicator = indicator;
} }
@RequestMapping("${endpoints.health.path:/health}") @Override
@ResponseBody public T invoke() {
public T health() {
return this.indicator.health(); return this.indicator.health();
} }

@ -0,0 +1,58 @@
/*
* 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.actuate.endpoint;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose arbitrary application information.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.info", ignoreUnknownFields = false)
public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {
private Map<String, ? extends Object> info;
/**
* Create a new {@link InfoEndpoint} instance.
*
* @param info the info to expose
*/
public InfoEndpoint(Map<String, ? extends Object> info) {
super("/info", true);
Assert.notNull(info, "Info must not be null");
this.info = info;
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> info = new LinkedHashMap<String, Object>(this.info);
info.putAll(getAdditionalInfo());
return info;
}
protected Map<String, Object> getAdditionalInfo() {
return Collections.emptyMap();
}
}

@ -14,34 +14,38 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.metrics; package org.springframework.bootstrap.actuate.endpoint;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.bootstrap.actuate.metrics.Metric; import org.springframework.bootstrap.actuate.metrics.Metric;
import org.springframework.stereotype.Controller; import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* {@link Endpoint} to expose {@link PublicMetrics}.
*
* @author Dave Syer * @author Dave Syer
*/ */
@Controller @ConfigurationProperties(name = "endpoints.metrics", ignoreUnknownFields = false)
public class MetricsEndpoint { public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
private PublicMetrics metrics; private PublicMetrics metrics;
/** /**
* @param metrics * Create a new {@link MetricsEndpoint} instance.
*
* @param metrics the metrics to expose
*/ */
public MetricsEndpoint(PublicMetrics metrics) { public MetricsEndpoint(PublicMetrics metrics) {
super("/metrics");
Assert.notNull(metrics, "Metrics must not be null");
this.metrics = metrics; this.metrics = metrics;
} }
@RequestMapping("${endpoints.metrics.path:/metrics}") @Override
@ResponseBody public Map<String, Object> invoke() {
public Map<String, Object> metrics() {
Map<String, Object> result = new LinkedHashMap<String, Object>(); Map<String, Object> result = new LinkedHashMap<String, Object>();
for (Metric metric : this.metrics.metrics()) { for (Metric metric : this.metrics.metrics()) {
result.put(metric.getName(), metric.getValue()); result.put(metric.getName(), metric.getValue());

@ -14,14 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.metrics; package org.springframework.bootstrap.actuate.endpoint;
import java.util.Collection; import java.util.Collection;
import org.springframework.bootstrap.actuate.metrics.Metric; import org.springframework.bootstrap.actuate.metrics.Metric;
/** /**
* Interface to expose specific {@link Metric}s via a {@link MetricsEndpoint}.
*
* @author Dave Syer * @author Dave Syer
* @see VanillaPublicMetrics
*/ */
public interface PublicMetrics { public interface PublicMetrics {

@ -0,0 +1,81 @@
/*
* 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.actuate.endpoint;
import java.util.Collections;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
/**
* {@link ActionEndpoint} to shutdown the {@link ApplicationContext}.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.shutdown", ignoreUnknownFields = false)
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> implements
ApplicationContextAware, ActionEndpoint<Map<String, Object>> {
private ConfigurableApplicationContext context;
@Autowired(required = false)
private ManagementServerProperties configuration = new ManagementServerProperties();
/**
* Create a new {@link ShutdownEndpoint} instance.
*/
public ShutdownEndpoint() {
super("/shutdown");
}
@Override
public Map<String, Object> invoke() {
if (this.configuration == null || !this.configuration.isAllowShutdown()
|| this.context == null) {
return Collections.<String, Object> singletonMap("message",
"Shutdown not enabled, sorry.");
}
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
}
ShutdownEndpoint.this.context.close();
}
}).start();
return Collections.<String, Object> singletonMap("message",
"Shutting down, bye...");
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context instanceof ConfigurableApplicationContext) {
this.context = (ConfigurableApplicationContext) context;
}
}
}

@ -0,0 +1,51 @@
/*
* 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.actuate.endpoint;
import java.util.List;
import org.springframework.bootstrap.actuate.trace.Trace;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose {@link Trace} information.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.trace", ignoreUnknownFields = false)
public class TraceEndpoint extends AbstractEndpoint<List<Trace>> {
private TraceRepository repository;
/**
* Create a new {@link TraceEndpoint} instance.
*
* @param repository the trace repository
*/
public TraceEndpoint(TraceRepository repository) {
super("/trace");
Assert.notNull(repository, "Repository must not be null");
this.repository = repository;
}
@Override
public List<Trace> invoke() {
return this.repository.findAll();
}
}

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.metrics; package org.springframework.bootstrap.actuate.endpoint;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -24,6 +24,9 @@ import org.springframework.bootstrap.actuate.metrics.MetricRepository;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Default implementation of {@link PublicMetrics} that exposes all metrics from the
* {@link MetricRepository} along with memory information.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class VanillaPublicMetrics implements PublicMetrics { public class VanillaPublicMetrics implements PublicMetrics {
@ -31,7 +34,7 @@ public class VanillaPublicMetrics implements PublicMetrics {
private MetricRepository metricRepository; private MetricRepository metricRepository;
public VanillaPublicMetrics(MetricRepository metricRepository) { public VanillaPublicMetrics(MetricRepository metricRepository) {
Assert.notNull(metricRepository, "A MetricRepository must be provided"); Assert.notNull(metricRepository, "MetricRepository must not be null");
this.metricRepository = metricRepository; this.metricRepository = metricRepository;
} }

@ -1,53 +0,0 @@
/*
* 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.actuate.endpoint.info;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
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 InfoEndpoint {
private Map<String, Object> info;
/**
* @param info
*/
public InfoEndpoint(Map<String, Object> info) {
this.info = new LinkedHashMap<String, Object>(info);
this.info.putAll(getAdditionalInfo());
}
@RequestMapping("${endpoints.info.path:/info}")
@ResponseBody
public Map<String, Object> info() {
return this.info;
}
protected Map<String, Object> getAdditionalInfo() {
return Collections.emptyMap();
}
}

@ -0,0 +1,225 @@
/*
* 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.actuate.endpoint.mvc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
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.bootstrap.actuate.endpoint.Endpoint;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* MVC {@link HandlerAdapter} for {@link Endpoint}s. Similar in may respects to
* {@link AbstractMessageConverterMethodProcessor} but not tied to annotated methods.
*
* @author Phillip Webb
* @see EndpointHandlerMapping
*/
public class EndpointHandlerAdapter implements HandlerAdapter {
private static final Log logger = LogFactory.getLog(EndpointHandlerAdapter.class);
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
private List<HttpMessageConverter<?>> messageConverters;
private List<MediaType> allSupportedMediaTypes;
public EndpointHandlerAdapter() {
WebMvcConfigurationSupportConventions conventions = new WebMvcConfigurationSupportConventions();
setMessageConverters(conventions.getDefaultHttpMessageConverters());
}
@Override
public boolean supports(Object handler) {
return handler instanceof Endpoint;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
handle(request, response, (Endpoint<?>) handler);
return null;
}
@SuppressWarnings("unchecked")
private void handle(HttpServletRequest request, HttpServletResponse response,
Endpoint<?> endpoint) throws Exception {
Object result = endpoint.invoke();
Class<?> resultClass = result.getClass();
List<MediaType> mediaTypes = getMediaTypes(request, endpoint, resultClass);
MediaType selectedMediaType = selectMediaType(mediaTypes);
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
try {
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(resultClass, selectedMediaType)) {
((HttpMessageConverter<Object>) messageConverter).write(result,
selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + result + "] as \""
+ selectedMediaType + "\" using [" + messageConverter
+ "]");
}
return;
}
}
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
} finally {
outputMessage.close();
}
}
private List<MediaType> getMediaTypes(HttpServletRequest request,
Endpoint<?> endpoint, Class<?> resultClass)
throws HttpMediaTypeNotAcceptableException {
List<MediaType> requested = getAcceptableMediaTypes(request);
List<MediaType> producible = getProducibleMediaTypes(endpoint, resultClass);
Set<MediaType> compatible = new LinkedHashSet<MediaType>();
for (MediaType r : requested) {
for (MediaType p : producible) {
if (r.isCompatibleWith(p)) {
compatible.add(getMostSpecificMediaType(r, p));
}
}
}
if (compatible.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(producible);
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatible);
MediaType.sortBySpecificityAndQuality(mediaTypes);
return mediaTypes;
}
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = this.contentNegotiationManager
.resolveMediaTypes(new ServletWebRequest(request));
return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL)
: mediaTypes;
}
private List<MediaType> getProducibleMediaTypes(Endpoint<?> endpoint,
Class<?> returnValueClass) {
MediaType[] mediaTypes = endpoint.getProduces();
if (mediaTypes != null && mediaTypes.length != 0) {
return Arrays.asList(mediaTypes);
}
if (this.allSupportedMediaTypes.isEmpty()) {
return Collections.singletonList(MediaType.ALL);
}
List<MediaType> result = new ArrayList<MediaType>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produceType) {
produceType = produceType.copyQualityValue(acceptType);
return MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceType) <= 0 ? acceptType
: produceType;
}
private MediaType selectMediaType(List<MediaType> mediaTypes) {
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL)
|| mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
return selectedMediaType;
}
public void setContentNegotiationManager(
ContentNegotiationManager contentNegotiationManager) {
this.contentNegotiationManager = contentNegotiationManager;
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
this.allSupportedMediaTypes = new ArrayList<MediaType>(allSupportedMediaTypes);
MediaType.sortBySpecificity(this.allSupportedMediaTypes);
}
/**
* Default conventions, taken from {@link WebMvcConfigurationSupport} with a few minor
* tweaks.
*/
private static class WebMvcConfigurationSupportConventions extends
WebMvcConfigurationSupport {
public List<HttpMessageConverter<?>> getDefaultHttpMessageConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
addDefaultHttpMessageConverters(converters);
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
jacksonConverter.getObjectMapper().disable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
return converters;
}
}
}

@ -0,0 +1,133 @@
/*
* 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.actuate.endpoint.mvc;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.bootstrap.actuate.endpoint.ActionEndpoint;
import org.springframework.bootstrap.actuate.endpoint.Endpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
/**
* {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getPath()}.
* Standard {@link Endpoint}s are mapped to GET requests, {@link ActionEndpoint}s are
* mapped to POST requests.
*
* @author Phillip Webb
* @see EndpointHandlerAdapter
*/
public class EndpointHandlerMapping extends AbstractUrlHandlerMapping implements
InitializingBean, ApplicationContextAware {
private List<Endpoint<?>> endpoints;
private String prefix = "";
private boolean disabled = false;
/**
* Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s will be
* detected from the {@link ApplicationContext}.
*/
public EndpointHandlerMapping() {
setOrder(HIGHEST_PRECEDENCE);
}
/**
* Create a new {@link EndpointHandlerMapping} with the specified endpoints.
* @param endpoints the endpoints
*/
public EndpointHandlerMapping(Collection<? extends Endpoint<?>> endpoints) {
Assert.notNull(endpoints, "Endpoints must not be null");
this.endpoints = new ArrayList<Endpoint<?>>(endpoints);
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.endpoints == null) {
this.endpoints = findEndpointBeans();
}
if (!this.disabled) {
for (Endpoint<?> endpoint : this.endpoints) {
registerHandler(this.prefix + endpoint.getPath(), endpoint);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private List<Endpoint<?>> findEndpointBeans() {
return new ArrayList(BeanFactoryUtils.beansOfTypeIncludingAncestors(
getApplicationContext(), Endpoint.class).values());
}
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request)
throws Exception {
Object handler = super.lookupHandler(urlPath, request);
if (handler != null) {
Object endpoint = (handler instanceof HandlerExecutionChain ? ((HandlerExecutionChain) handler)
.getHandler() : handler);
String method = (endpoint instanceof ActionEndpoint<?> ? "POST" : "GET");
if (request.getMethod().equals(method)) {
return endpoint;
}
}
return null;
}
/**
* @param prefix the prefix to set
*/
public void setPrefix(String prefix) {
Assert.isTrue("".equals(prefix) || StringUtils.startsWithIgnoreCase(prefix, "/"),
"prefix must start with '/'");
this.prefix = prefix;
}
/**
* Sets if this mapping is disabled.
*/
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
/**
* Returns if this mapping is disabled.
*/
public boolean isDisabled() {
return this.disabled;
}
/**
* Return the endpoints
*/
public List<Endpoint<?>> getEndpoints() {
return Collections.unmodifiableList(this.endpoints);
}
}

@ -1,98 +0,0 @@
/*
* 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.actuate.endpoint.shutdown;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.support.ServletRequestHandledEvent;
/**
* @author Dave Syer
*/
@Controller
public class ShutdownEndpoint implements ApplicationContextAware,
ApplicationListener<ServletRequestHandledEvent> {
private static Log logger = LogFactory.getLog(ShutdownEndpoint.class);
private ConfigurableApplicationContext context;
@Autowired
private ManagementServerProperties configuration = new ManagementServerProperties();
private boolean shuttingDown = false;
@RequestMapping(value = "${endpoints.shutdown.path:/shutdown}", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> shutdown() {
if (this.configuration.isAllowShutdown()) {
this.shuttingDown = true;
return Collections.<String, Object> singletonMap("message",
"Shutting down, bye...");
} else {
return Collections.<String, Object> singletonMap("message",
"Shutdown not enabled, sorry.");
}
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context instanceof ConfigurableApplicationContext) {
this.context = (ConfigurableApplicationContext) context;
}
}
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
if (this.context != null && this.configuration.isAllowShutdown()
&& this.shuttingDown) {
new Thread(new Runnable() {
@Override
public void run() {
logger.info("Shutting down Spring in response to admin request");
ConfigurableApplicationContext context = ShutdownEndpoint.this.context;
ApplicationContext parent = context.getParent();
context.close();
if (parent != null
&& parent instanceof ConfigurableApplicationContext) {
context = (ConfigurableApplicationContext) parent;
context.close();
parent = context.getParent();
}
}
}).start();
}
}
}

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.fixme;
import java.io.IOException; import java.io.IOException;
@ -28,8 +28,8 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.actuate.endpoint.error.ErrorEndpoint;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties; import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.actuate.web.BasicErrorController;
import org.springframework.bootstrap.context.annotation.ConditionalOnBean; import org.springframework.bootstrap.context.annotation.ConditionalOnBean;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory;
@ -43,7 +43,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@ -58,6 +57,8 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Import(ManagementSecurityConfiguration.class) @Import(ManagementSecurityConfiguration.class)
public class ManagementServerConfiguration { public class ManagementServerConfiguration {
// FIXME delete when security works
@Bean @Bean
public DispatcherServlet dispatcherServlet() { public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet(); return new DispatcherServlet();
@ -70,8 +71,8 @@ public class ManagementServerConfiguration {
} }
@Bean @Bean
public ErrorEndpoint errorEndpoint() { public BasicErrorController errorEndpoint() {
return new ErrorEndpoint(); return new BasicErrorController();
} }
@Bean @Bean
@ -90,7 +91,7 @@ public class ManagementServerConfiguration {
return new JettyEmbeddedServletContainerFactory(); return new JettyEmbeddedServletContainerFactory();
} }
@Component @Configuration
protected static class ServerCustomizationConfiguration implements protected static class ServerCustomizationConfiguration implements
EmbeddedServletContainerCustomizer { EmbeddedServletContainerCustomizer {

@ -14,10 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.health; package org.springframework.bootstrap.actuate.health;
/** /**
* Strategy interface used to provide an indication of application health.
*
* @author Dave Syer * @author Dave Syer
* @see VanillaHealthIndicator
*/ */
public interface HealthIndicator<T> { public interface HealthIndicator<T> {

@ -14,9 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.health; package org.springframework.bootstrap.actuate.health;
/** /**
* Default implementation of {@link HealthIndicator} that simply returns "ok".
*
* @author Dave Syer * @author Dave Syer
*/ */
public class VanillaHealthIndicator implements HealthIndicator<String> { public class VanillaHealthIndicator implements HealthIndicator<String> {

@ -16,12 +16,29 @@
package org.springframework.bootstrap.actuate.metrics; package org.springframework.bootstrap.actuate.metrics;
/**
* A service that can be used to increment, decrement and reset a {@link Metric}.
*
* @author Dave Syer
*/
public interface CounterService { public interface CounterService {
/**
* Increment the specified metric by 1.
* @param metricName the name of the metric
*/
void increment(String metricName); void increment(String metricName);
/**
* Decrement the specified metric by 1.
* @param metricName the name of the metric
*/
void decrement(String metricName); void decrement(String metricName);
/**
* Reset the specified metric to 0.
* @param metricName the name of the metric
*/
void reset(String metricName); void reset(String metricName);
} }

@ -19,33 +19,36 @@ package org.springframework.bootstrap.actuate.metrics;
import java.util.Date; import java.util.Date;
/** /**
* Default implementation of {@link CounterService}.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class DefaultCounterService implements CounterService { public class DefaultCounterService implements CounterService {
private MetricRepository counterRepository; private MetricRepository repository;
/** /**
* @param counterRepository * Create a {@link DefaultCounterService} instance.
* @param repository the underlying repository used to manage metrics
*/ */
public DefaultCounterService(MetricRepository counterRepository) { public DefaultCounterService(MetricRepository repository) {
super(); super();
this.counterRepository = counterRepository; this.repository = repository;
} }
@Override @Override
public void increment(String metricName) { public void increment(String metricName) {
this.counterRepository.increment(wrap(metricName), 1, new Date()); this.repository.increment(wrap(metricName), 1, new Date());
} }
@Override @Override
public void decrement(String metricName) { public void decrement(String metricName) {
this.counterRepository.increment(wrap(metricName), -1, new Date()); this.repository.increment(wrap(metricName), -1, new Date());
} }
@Override @Override
public void reset(String metricName) { public void reset(String metricName) {
this.counterRepository.set(wrap(metricName), 0, new Date()); this.repository.set(wrap(metricName), 0, new Date());
} }
private String wrap(String metricName) { private String wrap(String metricName) {

@ -19,6 +19,8 @@ package org.springframework.bootstrap.actuate.metrics;
import java.util.Date; import java.util.Date;
/** /**
* Default implementation of {@link GaugeService}.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class DefaultGaugeService implements GaugeService { public class DefaultGaugeService implements GaugeService {

@ -16,8 +16,18 @@
package org.springframework.bootstrap.actuate.metrics; package org.springframework.bootstrap.actuate.metrics;
/**
* A service that can be used to manage a {@link Metric} as a gauge.
*
* @author Dave Syer
*/
public interface GaugeService { public interface GaugeService {
/**
* Set the specified metric value
* @param metricName the metric to set
* @param value the value of the metric
*/
void set(String metricName, double value); void set(String metricName, double value);
} }

@ -31,6 +31,7 @@ public class InMemoryMetricRepository implements MetricRepository {
@Override @Override
public void increment(String metricName, int amount, Date timestamp) { public void increment(String metricName, int amount, Date timestamp) {
// FIXME this might not be thread safe
Measurement current = this.metrics.get(metricName); Measurement current = this.metrics.get(metricName);
if (current != null) { if (current != null) {
Metric metric = current.getMetric(); Metric metric = current.getMetric();

@ -18,10 +18,14 @@ package org.springframework.bootstrap.actuate.metrics;
import java.util.Date; import java.util.Date;
import org.springframework.util.ObjectUtils;
/** /**
* A {@link Metric} at a given point in time.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class Measurement { public final class Measurement {
private Date timestamp; private Date timestamp;
@ -42,39 +46,34 @@ public class Measurement {
@Override @Override
public String toString() { public String toString() {
return "Measurement [dateTime=" + this.timestamp + ", metric=" + this.metric + "]"; return "Measurement [dateTime=" + this.timestamp + ", metric=" + this.metric
+ "]";
} }
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result result = prime * result + ObjectUtils.nullSafeHashCode(this.timestamp);
+ ((this.timestamp == null) ? 0 : this.timestamp.hashCode()); result = prime * result + ObjectUtils.nullSafeHashCode(this.metric);
result = prime * result + ((this.metric == null) ? 0 : this.metric.hashCode());
return result; return result;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj) {
return true; return true;
if (obj == null) }
return false; if (obj == null) {
if (getClass() != obj.getClass())
return false;
Measurement other = (Measurement) obj;
if (this.timestamp == null) {
if (other.timestamp != null)
return false;
} else if (!this.timestamp.equals(other.timestamp))
return false;
if (this.metric == null) {
if (other.metric != null)
return false;
} else if (!this.metric.equals(other.metric))
return false; return false;
return true; }
if (getClass() == obj.getClass()) {
Measurement other = (Measurement) obj;
boolean result = ObjectUtils.nullSafeEquals(this.timestamp, other.timestamp);
result &= ObjectUtils.nullSafeEquals(this.metric, other.metric);
return result;
}
return super.equals(obj);
} }
} }

@ -16,33 +16,63 @@
package org.springframework.bootstrap.actuate.metrics; package org.springframework.bootstrap.actuate.metrics;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/** /**
* Immutable class that can be used to hold any arbitrary system measurement value. For
* example a metric might record the number of active connections.
*
* @author Dave Syer * @author Dave Syer
* @see MetricRepository
* @see CounterService
*/ */
public class Metric { public final class Metric {
private final String name; private final String name;
private final double value; private final double value;
/**
* Create a new {@link Metric} instance.
* @param name the name of the metric
* @param value the value of the metric
*/
public Metric(String name, double value) { public Metric(String name, double value) {
super(); super();
Assert.notNull(name, "Name must not be null");
this.name = name; this.name = name;
this.value = value; this.value = value;
} }
/**
* Returns the name of the metric.
*/
public String getName() { public String getName() {
return this.name; return this.name;
} }
/**
* Returns the value of the metric.
*/
public double getValue() { public double getValue() {
return this.value; return this.value;
} }
/**
* Create a new {@link Metric} with an incremented value.
* @param amount the amount that the new metric will differ from this one
* @return a new {@link Metric} instance
*/
public Metric increment(int amount) { public Metric increment(int amount) {
return new Metric(this.name, new Double(((int) this.value) + amount)); return new Metric(this.name, new Double(((int) this.value) + amount));
} }
/**
* Create a new {@link Metric} with a different value.
* @param value the value of the new metric
* @return a new {@link Metric} instance
*/
public Metric set(double value) { public Metric set(double value) {
return new Metric(this.name, value); return new Metric(this.name, value);
} }
@ -54,32 +84,30 @@ public class Metric {
@Override @Override
public int hashCode() { public int hashCode() {
int valueHashCode = ObjectUtils.hashCode(this.value);
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
long temp; result = prime * result + (valueHashCode ^ (valueHashCode >>> 32));
temp = Double.doubleToLongBits(this.value);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result; return result;
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj) {
return true; return true;
if (obj == null) }
return false; if (obj == null) {
if (getClass() != obj.getClass())
return false;
Metric other = (Metric) obj;
if (this.name == null) {
if (other.name != null)
return false;
} else if (!this.name.equals(other.name))
return false;
if (Double.doubleToLongBits(this.value) != Double.doubleToLongBits(other.value))
return false; return false;
return true; }
if (getClass() == obj.getClass()) {
Metric other = (Metric) obj;
boolean result = ObjectUtils.nullSafeEquals(this.name, other.name);
result &= Double.doubleToLongBits(this.value) == Double
.doubleToLongBits(other.value);
return result;
}
return super.equals(obj);
} }
} }

@ -20,10 +20,18 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
/** /**
* A Repository used to manage {@link Metric}s.
*
* @author Dave Syer * @author Dave Syer
*/ */
public interface MetricRepository { public interface MetricRepository {
// FIXME perhaps revisit this, there is no way to get timestamps
// could also simply, leaving increment to counter service
// Perhaps findAll, findOne should return Measurements
// put(String name, Callback -> process(Metric)
void increment(String metricName, int amount, Date timestamp); void increment(String metricName, int amount, Date timestamp);
void set(String metricName, double value, Date timestamp); void set(String metricName, double value, Date timestamp);

@ -1,130 +0,0 @@
/*
* 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.actuate.properties;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
/**
* Externalized configuration for endpoints (e.g. paths)
*
* @author Dave Syer
*
*/
@ConfigurationProperties(name = "endpoints", ignoreUnknownFields = false)
public class EndpointsProperties {
@Valid
private Endpoint info = new Endpoint("/info");
@Valid
private Endpoint metrics = new Endpoint("/metrics");
@Valid
private Endpoint health = new Endpoint("/health");
@Valid
private Endpoint error = new Endpoint("/error");
@Valid
private Endpoint shutdown = new Endpoint("/shutdown");
@Valid
private Endpoint trace = new Endpoint("/trace");
@Valid
private Endpoint dump = new Endpoint("/dump");
@Valid
private Endpoint beans = new Endpoint("/beans");
@Valid
private Endpoint env = new Endpoint("/env");
public Endpoint getInfo() {
return this.info;
}
public Endpoint getMetrics() {
return this.metrics;
}
public Endpoint getHealth() {
return this.health;
}
public Endpoint getError() {
return this.error;
}
public Endpoint getShutdown() {
return this.shutdown;
}
public Endpoint getTrace() {
return this.trace;
}
public Endpoint getDump() {
return this.dump;
}
public Endpoint getBeans() {
return this.beans;
}
public Endpoint getEnv() {
return this.env;
}
public static class Endpoint {
@NotNull
@Pattern(regexp = "/[^/]*", message = "Path must start with /")
private String path;
public Endpoint() {
}
public Endpoint(String path) {
super();
this.path = path;
}
public String getPath() {
return this.path;
}
public void setPath(String path) {
this.path = path;
}
}
public String[] getSecurePaths() {
return new String[] { getMetrics().getPath(), getBeans().getPath(),
getDump().getPath(), getShutdown().getPath(), getTrace().getPath(),
getEnv().getPath() };
}
public String[] getOpenPaths() {
return new String[] { getHealth().getPath(), getInfo().getPath(),
getError().getPath() };
}
}

@ -20,19 +20,19 @@ import java.net.InetAddress;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties; import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.bootstrap.properties.ServerProperties;
/** /**
* Properties for the management server (e.g. port and path settings). * Properties for the management server (e.g. port and path settings).
* *
* @author Dave Syer * @author Dave Syer
* @see ServerProperties
*/ */
@ConfigurationProperties(name = "management", ignoreUnknownFields = false) @ConfigurationProperties(name = "management", ignoreUnknownFields = false)
public class ManagementServerProperties { public class ManagementServerProperties {
@Value("${server.port:8080}") private Integer port;
private int port = 8080;
private InetAddress address; private InetAddress address;
@ -49,11 +49,20 @@ public class ManagementServerProperties {
this.allowShutdown = allowShutdown; this.allowShutdown = allowShutdown;
} }
public int getPort() { /**
* Returns the management port or {@code null} if the
* {@link ServerProperties#getPort() server port} should be used.
* @see #setPort(Integer)
*/
public Integer getPort() {
return this.port; return this.port;
} }
public void setPort(int port) { /**
* Sets the port of the management server, use {@code null} if the
* {@link ServerProperties#getPort() server port} should be used. To disable use 0.
*/
public void setPort(Integer port) {
this.port = port; this.port = port;
} }

@ -29,6 +29,9 @@ import org.springframework.security.authentication.event.AbstractAuthenticationF
import org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent; import org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent;
/** /**
* {@link ApplicationListener} expose Spring Security {@link AbstractAuthenticationEvent
* authentication events} as {@link AuditEvent}s.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class AuthenticationAuditListener implements public class AuthenticationAuditListener implements
@ -43,30 +46,40 @@ public class AuthenticationAuditListener implements
@Override @Override
public void onApplicationEvent(AbstractAuthenticationEvent event) { public void onApplicationEvent(AbstractAuthenticationEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
if (event instanceof AbstractAuthenticationFailureEvent) { if (event instanceof AbstractAuthenticationFailureEvent) {
data.put("type", ((AbstractAuthenticationFailureEvent) event).getException() onAuthenticationFailureEvent((AbstractAuthenticationFailureEvent) event);
.getClass().getName());
data.put("message", ((AbstractAuthenticationFailureEvent) event)
.getException().getMessage());
publish(new AuditEvent(event.getAuthentication().getName(),
"AUTHENTICATION_FAILURE", data));
} else if (event instanceof AuthenticationSwitchUserEvent) { } else if (event instanceof AuthenticationSwitchUserEvent) {
if (event.getAuthentication().getDetails() != null) { onAuthenticationSwitchUserEvent((AuthenticationSwitchUserEvent) event);
data.put("details", event.getAuthentication().getDetails());
}
data.put("target", ((AuthenticationSwitchUserEvent) event).getTargetUser()
.getUsername());
publish(new AuditEvent(event.getAuthentication().getName(),
"AUTHENTICATION_SWITCH", data));
} else { } else {
if (event.getAuthentication().getDetails() != null) { onAuthenticationEvent(event);
data.put("details", event.getAuthentication().getDetails()); }
} }
publish(new AuditEvent(event.getAuthentication().getName(),
"AUTHENTICATION_SUCCESS", data)); private void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("type", event.getException().getClass().getName());
data.put("message", event.getException().getMessage());
publish(new AuditEvent(event.getAuthentication().getName(),
"AUTHENTICATION_FAILURE", data));
}
private void onAuthenticationSwitchUserEvent(AuthenticationSwitchUserEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
if (event.getAuthentication().getDetails() != null) {
data.put("details", event.getAuthentication().getDetails());
}
data.put("target", event.getTargetUser().getUsername());
publish(new AuditEvent(event.getAuthentication().getName(),
"AUTHENTICATION_SWITCH", data));
}
private void onAuthenticationEvent(AbstractAuthenticationEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
if (event.getAuthentication().getDetails() != null) {
data.put("details", event.getAuthentication().getDetails());
} }
publish(new AuditEvent(event.getAuthentication().getName(),
"AUTHENTICATION_SUCCESS", data));
} }
private void publish(AuditEvent event) { private void publish(AuditEvent event) {

@ -29,6 +29,9 @@ import org.springframework.security.access.event.AuthenticationCredentialsNotFou
import org.springframework.security.access.event.AuthorizationFailureEvent; import org.springframework.security.access.event.AuthorizationFailureEvent;
/** /**
* {@link ApplicationListener} expose Spring Security {@link AbstractAuthorizationEvent
* authorization events} as {@link AuditEvent}s.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class AuthorizationAuditListener implements public class AuthorizationAuditListener implements
@ -43,23 +46,29 @@ public class AuthorizationAuditListener implements
@Override @Override
public void onApplicationEvent(AbstractAuthorizationEvent event) { public void onApplicationEvent(AbstractAuthorizationEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
if (event instanceof AuthenticationCredentialsNotFoundEvent) { if (event instanceof AuthenticationCredentialsNotFoundEvent) {
data.put("type", ((AuthenticationCredentialsNotFoundEvent) event) onAuthenticationCredentialsNotFoundEvent((AuthenticationCredentialsNotFoundEvent) event);
.getCredentialsNotFoundException().getClass().getName());
data.put("message", ((AuthenticationCredentialsNotFoundEvent) event)
.getCredentialsNotFoundException().getMessage());
publish(new AuditEvent("<unknown>", "AUTHENTICATION_FAILURE", data));
} else if (event instanceof AuthorizationFailureEvent) { } else if (event instanceof AuthorizationFailureEvent) {
data.put("type", ((AuthorizationFailureEvent) event) onAuthorizationFailureEvent((AuthorizationFailureEvent) event);
.getAccessDeniedException().getClass().getName());
data.put("message", ((AuthorizationFailureEvent) event)
.getAccessDeniedException().getMessage());
publish(new AuditEvent(((AuthorizationFailureEvent) event)
.getAuthentication().getName(), "AUTHORIZATION_FAILURE", data));
} }
} }
private void onAuthenticationCredentialsNotFoundEvent(
AuthenticationCredentialsNotFoundEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("type", event.getCredentialsNotFoundException().getClass().getName());
data.put("message", event.getCredentialsNotFoundException().getMessage());
publish(new AuditEvent("<unknown>", "AUTHENTICATION_FAILURE", data));
}
private void onAuthorizationFailureEvent(AuthorizationFailureEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
data.put("type", event.getAccessDeniedException().getClass().getName());
data.put("message", event.getAccessDeniedException().getMessage());
publish(new AuditEvent(event.getAuthentication().getName(),
"AUTHORIZATION_FAILURE", data));
}
private void publish(AuditEvent event) { private void publish(AuditEvent event) {
if (this.publisher != null) { if (this.publisher != null) {
this.publisher.publishEvent(new AuditApplicationEvent(event)); this.publisher.publishEvent(new AuditApplicationEvent(event));

@ -23,6 +23,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* In-memory implementation of {@link TraceRepository}.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class InMemoryTraceRepository implements TraceRepository { public class InMemoryTraceRepository implements TraceRepository {
@ -39,7 +41,7 @@ public class InMemoryTraceRepository implements TraceRepository {
} }
@Override @Override
public List<Trace> traces() { public List<Trace> findAll() {
synchronized (this.traces) { synchronized (this.traces) {
return Collections.unmodifiableList(this.traces); return Collections.unmodifiableList(this.traces);
} }

@ -19,10 +19,15 @@ package org.springframework.bootstrap.actuate.trace;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import org.springframework.util.Assert;
/** /**
* A value object representing a trace event: at a particular time with a simple (map)
* information. Can be used for analyzing contextual information such as HTTP headers.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class Trace { public final class Trace {
private Date timestamp; private Date timestamp;
@ -30,6 +35,8 @@ public class Trace {
public Trace(Date timestamp, Map<String, Object> info) { public Trace(Date timestamp, Map<String, Object> info) {
super(); super();
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(info, "Info must not be null");
this.timestamp = timestamp; this.timestamp = timestamp;
this.info = info; this.info = info;
} }

@ -20,15 +20,21 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* A repository for traces. Traces are simple documents (maps) with a timestamp, and can * A repository for {@link Trace}s.
* be used for analysing contextual information like HTTP headers.
* *
* @author Dave Syer * @author Dave Syer
*/ */
public interface TraceRepository { public interface TraceRepository {
List<Trace> traces(); /**
* Find all {@link Trace} objects contained in the repository.
*/
List<Trace> findAll();
void add(Map<String, Object> trace); /**
* Add a new {@link Trace} object at the current time.
* @param traceInfo trace information
*/
void add(Map<String, Object> traceInfo);
} }

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.trace; package org.springframework.bootstrap.actuate.trace;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@ -34,18 +34,19 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
/** /**
* Servlet {@link Filter} that logs all requests to a {@link TraceRepository}.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class WebRequestLoggingFilter implements Filter, Ordered { public class WebRequestTraceFilter implements Filter, Ordered {
final Log logger = LogFactory.getLog(WebRequestLoggingFilter.class); final Log logger = LogFactory.getLog(WebRequestTraceFilter.class);
private boolean dumpRequests = false; private boolean dumpRequests = false;
@ -58,7 +59,7 @@ public class WebRequestLoggingFilter implements Filter, Ordered {
/** /**
* @param traceRepository * @param traceRepository
*/ */
public WebRequestLoggingFilter(TraceRepository traceRepository) { public WebRequestTraceFilter(TraceRepository traceRepository) {
this.traceRepository = traceRepository; this.traceRepository = traceRepository;
} }
@ -88,14 +89,15 @@ public class WebRequestLoggingFilter implements Filter, Ordered {
HttpServletResponse response = (HttpServletResponse) res; HttpServletResponse response = (HttpServletResponse) res;
Map<String, Object> trace = getTrace(request); Map<String, Object> trace = getTrace(request);
@SuppressWarnings("unchecked")
Map<String, Object> headers = (Map<String, Object>) trace.get("headers");
this.traceRepository.add(trace); this.traceRepository.add(trace);
if (this.logger.isTraceEnabled()) { if (this.logger.isTraceEnabled()) {
this.logger.trace("Processing request " + request.getMethod() + " " this.logger.trace("Processing request " + request.getMethod() + " "
+ request.getRequestURI()); + request.getRequestURI());
if (this.dumpRequests) { if (this.dumpRequests) {
try { try {
@SuppressWarnings("unchecked")
Map<String, Object> headers = (Map<String, Object>) trace
.get("headers");
this.logger.trace("Headers: " this.logger.trace("Headers: "
+ this.objectMapper.writeValueAsString(headers)); + this.objectMapper.writeValueAsString(headers));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.endpoint.error; package org.springframework.bootstrap.actuate.web;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -35,7 +36,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
/** /**
* Basic fallback global error endpoint, rendering servlet container error codes and * Basic global error {@link Controller}, rendering servlet container error codes and
* messages where available. More specific errors can be handled either using Spring MVC * messages where available. More specific errors can be handled either using Spring MVC
* abstractions (e.g. {@code @ExceptionHandler}) or by adding servlet * abstractions (e.g. {@code @ExceptionHandler}) or by adding servlet
* {@link AbstractEmbeddedServletContainerFactory#setErrorPages(java.util.Set) container * {@link AbstractEmbeddedServletContainerFactory#setErrorPages(java.util.Set) container
@ -44,17 +45,25 @@ import org.springframework.web.servlet.ModelAndView;
* @author Dave Syer * @author Dave Syer
*/ */
@Controller @Controller
public class ErrorEndpoint { public class BasicErrorController implements ErrorController {
private Log logger = LogFactory.getLog(ErrorEndpoint.class); private Log logger = LogFactory.getLog(BasicErrorController.class);
@RequestMapping(value = "${endpoints.error.path:/error}", produces = "text/html") @Value("${error.path:/error}")
private String errorPath;
@Override
public String getErrorPath() {
return this.errorPath;
}
@RequestMapping(value = "${error.path:/error}", produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request) { public ModelAndView errorHtml(HttpServletRequest request) {
Map<String, Object> map = error(request); Map<String, Object> map = error(request);
return new ModelAndView("error", map); return new ModelAndView("error", map);
} }
@RequestMapping(value = "${endpoints.error.path:/error}") @RequestMapping(value = "${error.path:/error}")
@ResponseBody @ResponseBody
public Map<String, Object> error(HttpServletRequest request) { public Map<String, Object> error(HttpServletRequest request) {
Map<String, Object> map = new LinkedHashMap<String, Object>(); Map<String, Object> map = new LinkedHashMap<String, Object>();

@ -0,0 +1,31 @@
/*
* 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.actuate.web;
import org.springframework.stereotype.Controller;
/**
* Marker interface used to indicate that a {@link Controller @Controller} is used to
* render errors.
*
* @author Phillip Webb
*/
public interface ErrorController {
public String getErrorPath();
}

@ -1,4 +1,11 @@
org.springframework.bootstrap.context.annotation.EnableAutoConfiguration=\ org.springframework.bootstrap.context.annotation.EnableAutoConfiguration=\
org.springframework.bootstrap.actuate.autoconfigure.ActuatorAutoConfiguration,\ org.springframework.bootstrap.actuate.autoconfigure.AuditAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.EndpointAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.EndpointWebMvcAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.ErrorMvcAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.MetricFilterAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.SecurityAutoConfiguration,\ org.springframework.bootstrap.actuate.autoconfigure.SecurityAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.ManagementAutoConfiguration org.springframework.bootstrap.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\
org.springframework.bootstrap.actuate.autoconfigure.TraceWebFilterAutoConfiguration

@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.audit; package org.springframework.bootstrap.actuate.audit;
import java.util.Collections; import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
/** /**
* @author Dave Syer * Tests for {@link AuditEvent}.
* *
* @author Dave Syer
*/ */
public class AuditEventTests { public class AuditEventTests {

@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.audit; package org.springframework.bootstrap.actuate.audit;
import java.util.Date; import java.util.Date;
import org.junit.Test; import org.junit.Test;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.bootstrap.actuate.audit.InMemoryAuditEventRepository;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
* @author Dave Syer * Tests for {@link InMemoryAuditEventRepository}.
* *
* @author Dave Syer
*/ */
public class InMemoryAuditEventRepositoryTests { public class InMemoryAuditEventRepositoryTests {

@ -13,28 +13,32 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.audit.listener;
package org.springframework.bootstrap.actuate.autoconfigure; import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.bootstrap.actuate.audit.AuditEventRepository; import org.springframework.bootstrap.actuate.audit.AuditEventRepository;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/** /**
* @author Dave Syer * Tests for {@link AuditListener}.
*
* @author Phillip Webb
*/ */
public class AuditConfigurationTests { public class AuditListenerTests {
private AnnotationConfigApplicationContext context;
@Test @Test
public void testTraceConfiguration() throws Exception { public void testStoredEvents() {
this.context = new AnnotationConfigApplicationContext(); AuditEventRepository repository = mock(AuditEventRepository.class);
this.context.register(AuditConfiguration.class); AuditEvent event = new AuditEvent("principal", "type",
this.context.refresh(); Collections.<String, Object> emptyMap());
assertNotNull(this.context.getBean(AuditEventRepository.class)); AuditListener listener = new AuditListener(repository);
listener.onApplicationEvent(new AuditApplicationEvent(event));
verify(repository).add(event);
} }
} }

@ -1,42 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class ActuatorWebConfigurationTests {
private AnnotationConfigWebApplicationContext context;
@Test
public void testWebConfiguration() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(ActuatorWebConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(WebMvcConfigurationSupport.class));
}
}

@ -0,0 +1,73 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.audit.AuditEventRepository;
import org.springframework.bootstrap.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.bootstrap.actuate.security.AuthenticationAuditListener;
import org.springframework.bootstrap.actuate.security.AuthorizationAuditListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link AuditAutoConfiguration}.
*
* @author Dave Syer
*/
public class AuditAutoConfigurationTests {
private AnnotationConfigApplicationContext context;
@Test
public void testTraceConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(AuditAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(AuditEventRepository.class));
assertNotNull(this.context.getBean(AuthenticationAuditListener.class));
assertNotNull(this.context.getBean(AuthorizationAuditListener.class));
}
@Test
public void ownAutoRepository() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(Config.class, AuditAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBean(AuditEventRepository.class),
instanceOf(TestAuditEventRepository.class));
}
@Configuration
public static class Config {
@Bean
public TestAuditEventRepository testAuditEventRepository() {
return new TestAuditEventRepository();
}
}
public static class TestAuditEventRepository extends InMemoryAuditEventRepository {
}
}

@ -13,12 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.bootstrap.actuate.TestUtils; import org.springframework.bootstrap.actuate.TestUtils;
import org.springframework.bootstrap.actuate.endpoint.info.InfoEndpoint; import org.springframework.bootstrap.actuate.endpoint.BeansEndpoint;
import org.springframework.bootstrap.actuate.endpoint.DumpEndpoint;
import org.springframework.bootstrap.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.bootstrap.actuate.endpoint.HealthEndpoint;
import org.springframework.bootstrap.actuate.endpoint.InfoEndpoint;
import org.springframework.bootstrap.actuate.endpoint.MetricsEndpoint;
import org.springframework.bootstrap.actuate.endpoint.ShutdownEndpoint;
import org.springframework.bootstrap.actuate.endpoint.TraceEndpoint;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -26,22 +33,44 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
/** /**
* Tests for {@link EndpointAutoConfiguration}.
*
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
public class InfoConfigurationTests { public class EndpointAutoConfigurationTests {
private AnnotationConfigApplicationContext context; private AnnotationConfigApplicationContext context;
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(EndpointAutoConfiguration.class);
this.context.refresh();
}
@Test
public void endpoints() throws Exception {
assertNotNull(this.context.getBean(BeansEndpoint.class));
assertNotNull(this.context.getBean(DumpEndpoint.class));
assertNotNull(this.context.getBean(EnvironmentEndpoint.class));
assertNotNull(this.context.getBean(HealthEndpoint.class));
assertNotNull(this.context.getBean(InfoEndpoint.class));
assertNotNull(this.context.getBean(MetricsEndpoint.class));
assertNotNull(this.context.getBean(ShutdownEndpoint.class));
assertNotNull(this.context.getBean(TraceEndpoint.class));
}
@Test @Test
public void testInfoEndpointConfiguration() throws Exception { public void testInfoEndpointConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
TestUtils.addEnviroment(this.context, "info.foo:bar"); TestUtils.addEnviroment(this.context, "info.foo:bar");
this.context.register(InfoConfiguration.class); this.context.register(EndpointAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class);
assertNotNull(endpoint); assertNotNull(endpoint);
assertNotNull(endpoint.info().get("git")); assertNotNull(endpoint.invoke().get("git"));
assertEquals("bar", endpoint.info().get("foo")); assertEquals("bar", endpoint.invoke().get("foo"));
} }
@Test @Test
@ -49,11 +78,10 @@ public class InfoConfigurationTests {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
TestUtils.addEnviroment(this.context, TestUtils.addEnviroment(this.context,
"spring.git.properties:classpath:nonexistent"); "spring.git.properties:classpath:nonexistent");
this.context.register(InfoConfiguration.class); this.context.register(EndpointAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class); InfoEndpoint endpoint = this.context.getBean(InfoEndpoint.class);
assertNotNull(endpoint); assertNotNull(endpoint);
assertNull(endpoint.info().get("git")); assertNull(endpoint.invoke().get("git"));
} }
} }

@ -0,0 +1,236 @@
/*
* 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.actuate.autoconfigure;
import java.io.FileNotFoundException;
import java.net.ConnectException;
import java.net.URI;
import java.nio.charset.Charset;
import org.junit.After;
import org.junit.Test;
import org.springframework.bootstrap.actuate.TestUtils;
import org.springframework.bootstrap.actuate.endpoint.AbstractEndpoint;
import org.springframework.bootstrap.actuate.endpoint.Endpoint;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.bootstrap.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.bootstrap.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.bootstrap.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.bootstrap.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link EndpointWebMvcAutoConfiguration}.
*
* @author Phillip Webb
*/
public class EndpointWebMvcAutoConfigurationTests {
private AnnotationConfigEmbeddedWebApplicationContext applicationContext = new AnnotationConfigEmbeddedWebApplicationContext();
@After
public void close() {
try {
this.applicationContext.close();
} catch (Exception ex) {
}
}
@Test
public void onSamePort() throws Exception {
this.applicationContext.register(RootConfig.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertContent("/controller", 8080, "controlleroutput");
assertContent("/endpoint", 8080, "endpointoutput");
assertContent("/controller", 8081, null);
assertContent("/endpoint", 8081, null);
this.applicationContext.close();
assertAllClosed();
}
@Test
public void onDifferentPort() throws Exception {
this.applicationContext.register(RootConfig.class, DifferentPortConfig.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertContent("/controller", 8080, "controlleroutput");
assertContent("/endpoint", 8080, null);
assertContent("/controller", 8081, null);
assertContent("/endpoint", 8081, "endpointoutput");
this.applicationContext.close();
assertAllClosed();
}
@Test
public void disabled() throws Exception {
this.applicationContext.register(RootConfig.class, DisableConfig.class,
PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertContent("/controller", 8080, "controlleroutput");
assertContent("/endpoint", 8080, null);
assertContent("/controller", 8081, null);
assertContent("/endpoint", 8081, null);
this.applicationContext.close();
assertAllClosed();
}
@Test
public void specificPortsViaProperties() throws Exception {
TestUtils.addEnviroment(this.applicationContext, "server.port:7070",
"management.port:7071");
this.applicationContext.register(RootConfig.class,
PropertyPlaceholderAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertContent("/controller", 7070, "controlleroutput");
assertContent("/endpoint", 7070, null);
assertContent("/controller", 7071, null);
assertContent("/endpoint", 7071, "endpointoutput");
this.applicationContext.close();
assertAllClosed();
}
@Test
public void contextPath() throws Exception {
TestUtils.addEnviroment(this.applicationContext, "management.contextPath:/test");
this.applicationContext.register(RootConfig.class,
PropertyPlaceholderAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertContent("/controller", 8080, "controlleroutput");
assertContent("/test/endpoint", 8080, "endpointoutput");
this.applicationContext.close();
assertAllClosed();
}
private void assertAllClosed() throws Exception {
assertContent("/controller", 8080, null);
assertContent("/endpoint", 8080, null);
assertContent("/controller", 8081, null);
assertContent("/endpoint", 8081, null);
}
public void assertContent(String url, int port, Object expected) throws Exception {
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
ClientHttpRequest request = clientHttpRequestFactory.createRequest(new URI(
"http://localhost:" + port + url), HttpMethod.GET);
try {
ClientHttpResponse response = request.execute();
try {
String actual = StreamUtils.copyToString(response.getBody(),
Charset.forName("UTF-8"));
assertThat(actual, equalTo(expected));
} finally {
response.close();
}
} catch (Exception ex) {
if (expected == null) {
if (ConnectException.class.isInstance(ex)
|| FileNotFoundException.class.isInstance(ex)) {
return;
}
}
throw ex;
}
}
@Configuration
public static class RootConfig {
@Bean
public TestController testController() {
return new TestController();
}
@Bean
public Endpoint<String> testEndpoint() {
return new AbstractEndpoint<String>("/endpoint", false) {
@Override
public String invoke() {
return "endpointoutput";
}
};
}
}
@Controller
public static class TestController {
@RequestMapping("/controller")
@ResponseBody
public String requestMappedMethod() {
return "controlleroutput";
}
}
@Configuration
public static class DifferentPortConfig {
@Bean
public ManagementServerProperties managementServerProperties() {
ManagementServerProperties properties = new ManagementServerProperties();
properties.setPort(8081);
return properties;
}
}
@Configuration
public static class DisableConfig {
@Bean
public ManagementServerProperties managementServerProperties() {
ManagementServerProperties properties = new ManagementServerProperties();
properties.setPort(0);
return properties;
}
}
}

@ -1,51 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.bootstrap.actuate.autoconfigure.ActuatorAutoConfiguration.ServerPropertiesConfiguration;
import org.springframework.bootstrap.actuate.endpoint.error.ErrorEndpoint;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class ErrorConfigurationTests {
private AnnotationConfigApplicationContext context;
@Test
public void testErrorEndpointConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(ErrorConfiguration.class,
ServerPropertiesConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(ErrorEndpoint.class));
ConfigurableEmbeddedServletContainerFactory factory = Mockito
.mock(ConfigurableEmbeddedServletContainerFactory.class);
this.context.getBean(EmbeddedServletContainerCustomizer.class).customize(factory);
Mockito.verify(factory).addErrorPages(Mockito.any(ErrorPage.class));
}
}

@ -1,169 +0,0 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.junit.After;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.bootstrap.actuate.TestUtils;
import org.springframework.bootstrap.actuate.endpoint.health.HealthEndpoint;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.bootstrap.autoconfigure.web.ServerPropertiesConfiguration;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerException;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.ServletContextInitializer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockServletContext;
import org.springframework.stereotype.Controller;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class ManagementConfigurationTests {
private AnnotationConfigApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void testManagementConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(MetricRepositoryConfiguration.class,
TraceFilterConfiguration.class, ServerPropertiesConfiguration.class,
ActuatorAutoConfiguration.ServerPropertiesConfiguration.class,
ManagementAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(HealthEndpoint.class));
}
@Test
public void testSuppressManagementConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
TestUtils.addEnviroment(this.context, "management.port:0");
this.context.register(MetricRepositoryConfiguration.class,
TraceFilterConfiguration.class, ServerPropertiesConfiguration.class,
ActuatorAutoConfiguration.ServerPropertiesConfiguration.class,
ManagementAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertEquals(0, this.context.getBeanNamesForType(HealthEndpoint.class).length);
}
@Test
public void testManagementConfigurationExtensions() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(MetricRepositoryConfiguration.class,
TraceFilterConfiguration.class, ServerPropertiesConfiguration.class,
ActuatorAutoConfiguration.ServerPropertiesConfiguration.class,
ManagementAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, NewEndpoint.class);
this.context.refresh();
assertNotNull(this.context.getBean(NewEndpoint.class));
}
@Test
public void testManagementConfigurationExtensionsOrderDependence() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(NewEndpoint.class, MetricRepositoryConfiguration.class,
TraceFilterConfiguration.class, ServerPropertiesConfiguration.class,
ActuatorAutoConfiguration.ServerPropertiesConfiguration.class,
ManagementAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(NewEndpoint.class));
}
@Test
public void testChildContextCreated() throws Exception {
this.context = new AnnotationConfigApplicationContext();
TestUtils.addEnviroment(this.context, "server.port:7000", "management.port:7001");
this.context.register(ParentContext.class, MetricRepositoryConfiguration.class,
TraceFilterConfiguration.class, ServerPropertiesConfiguration.class,
ActuatorAutoConfiguration.ServerPropertiesConfiguration.class,
ManagementAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, NewEndpoint.class);
this.context.refresh();
assertEquals(0, this.context.getBeanNamesForType(HealthEndpoint.class).length);
assertEquals(0, this.context.getBeanNamesForType(NewEndpoint.class).length);
}
@Configuration
protected static class ParentContext {
@Bean
public EmbeddedServletContainerFactory factory() {
return new EmbeddedServletContainerFactory() {
@Override
public EmbeddedServletContainer getEmbdeddedServletContainer(
ServletContextInitializer... initializers) {
ServletContext servletContext = new MockServletContext() {
@Override
public Dynamic addServlet(String servletName, Servlet servlet) {
return Mockito.mock(Dynamic.class);
}
@Override
public javax.servlet.FilterRegistration.Dynamic addFilter(
String filterName, Filter filter) {
// TODO: remove this when @ConditionalOnBean works
return Mockito
.mock(javax.servlet.FilterRegistration.Dynamic.class);
}
};
for (ServletContextInitializer initializer : initializers) {
try {
initializer.onStartup(servletContext);
} catch (ServletException ex) {
throw new IllegalStateException(ex);
}
}
return new EmbeddedServletContainer() {
@Override
public void stop() throws EmbeddedServletContainerException {
}
};
}
};
}
}
@Controller
@ConditionalOnManagementContext
protected static class NewEndpoint {
}
}

@ -1,57 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.bootstrap.actuate.autoconfigure.ActuatorAutoConfiguration.ServerPropertiesConfiguration;
import org.springframework.bootstrap.actuate.endpoint.error.ErrorEndpoint;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class ManagementServerConfigurationTests {
private AnnotationConfigWebApplicationContext context;
@Test
public void testWebConfiguration() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(ManagementServerConfiguration.class,
ServerPropertiesConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(WebMvcConfigurationSupport.class));
assertNotNull(this.context.getBean(ErrorEndpoint.class));
ConfigurableEmbeddedServletContainerFactory factory = Mockito
.mock(ConfigurableEmbeddedServletContainerFactory.class);
this.context.getBean(EmbeddedServletContainerCustomizer.class).customize(factory);
Mockito.verify(factory).addErrorPages(Mockito.any(ErrorPage.class));
Mockito.verify(factory).setPort(8080);
}
}

@ -0,0 +1,65 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link ManagementServerPropertiesAutoConfiguration}.
*
* @author Phillip Webb
*/
public class ManagementServerPropertiesAutoConfigurationTests {
@Test
public void defaultManagementServerProperties() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
ManagementServerPropertiesAutoConfiguration.class);
assertThat(context.getBean(ManagementServerProperties.class).getPort(),
nullValue());
context.close();
}
@Test
public void definedManagementServerProperties() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
Config.class, ManagementServerPropertiesAutoConfiguration.class);
assertThat(context.getBean(ManagementServerProperties.class).getPort(),
equalTo(Integer.valueOf(123)));
context.close();
}
@Configuration
public static class Config {
@Bean
public ManagementServerProperties managementServerProperties() {
ManagementServerProperties properties = new ManagementServerProperties();
properties.setPort(123);
return properties;
}
}
}

@ -0,0 +1,93 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.bootstrap.actuate.metrics.CounterService;
import org.springframework.bootstrap.actuate.metrics.GaugeService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link MetricFilterAutoConfiguration}.
*
* @author Phillip Webb
*/
public class MetricFilterAutoConfigurationTests {
@Test
public void recordsHttpInteractions() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
Config.class, MetricFilterAutoConfiguration.class);
Filter filter = context.getBean(Filter.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET",
"/test/path");
final MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
willAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
response.setStatus(200);
return null;
}
}).given(chain).doFilter(request, response);
filter.doFilter(request, response, chain);
verify(context.getBean(CounterService.class)).increment("status.200.test.path");
verify(context.getBean(GaugeService.class)).set(eq("response.test.path"),
anyDouble());
context.close();
}
@Test
public void skipsFilterIfMissingServices() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MetricFilterAutoConfiguration.class);
assertThat(context.getBeansOfType(Filter.class).size(), equalTo(0));
context.close();
}
@Configuration
public static class Config {
@Bean
public CounterService counterService() {
return mock(CounterService.class);
}
@Bean
public GaugeService gaugeService() {
return mock(GaugeService.class);
}
}
}

@ -1,51 +0,0 @@
/*
* 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.actuate.autoconfigure;
import javax.servlet.Filter;
import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class MetricFilterConfigurationTests {
private AnnotationConfigWebApplicationContext context;
@Test
public void testMetricFilterConfiguration() throws Exception {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
// Order is important
this.context.register(MetricRepositoryConfiguration.class,
MetricFilterConfiguration.class);
this.context.refresh();
Filter filter = this.context.getBean(Filter.class);
assertNotNull(filter);
filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(),
new MockFilterChain());
}
}

@ -0,0 +1,72 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.metrics.CounterService;
import org.springframework.bootstrap.actuate.metrics.DefaultCounterService;
import org.springframework.bootstrap.actuate.metrics.DefaultGaugeService;
import org.springframework.bootstrap.actuate.metrics.GaugeService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link MetricRepositoryAutoConfiguration}.
*
* @author Phillip Webb
*/
public class MetricRepositoryAutoConfigurationTests {
@Test
public void createServices() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MetricRepositoryAutoConfiguration.class);
assertNotNull(context.getBean(DefaultGaugeService.class));
assertNotNull(context.getBean(DefaultCounterService.class));
context.close();
}
@Test
public void skipsIfBeansExist() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
Config.class, MetricRepositoryAutoConfiguration.class);
assertThat(context.getBeansOfType(DefaultGaugeService.class).size(), equalTo(0));
assertThat(context.getBeansOfType(DefaultCounterService.class).size(), equalTo(0));
context.close();
}
@Configuration
public static class Config {
@Bean
public GaugeService gaugeService() {
return mock(GaugeService.class);
}
@Bean
public CounterService counterService() {
return mock(CounterService.class);
}
}
}

@ -1,40 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.metrics.MetricRepository;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class MetricRepositoryConfigurationTests {
private AnnotationConfigApplicationContext context;
@Test
public void testTraceConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(MetricRepositoryConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(MetricRepository.class));
}
}

@ -1,41 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.endpoint.metrics.MetricsEndpoint;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class MetricsConfigurationTests {
private AnnotationConfigApplicationContext context;
@Test
public void testTraceConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(MetricRepositoryConfiguration.class,
MetricsConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(MetricsEndpoint.class));
}
}

@ -17,7 +17,6 @@
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import org.junit.Test; import org.junit.Test;
import org.springframework.bootstrap.actuate.properties.EndpointsProperties;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -32,9 +31,11 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
/** /**
* Tests for {@link SecurityAutoConfiguration}.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class SecurityConfigurationTests { public class SecurityAutoConfigurationTests {
private AnnotationConfigWebApplicationContext context; private AnnotationConfigWebApplicationContext context;
@ -42,7 +43,8 @@ public class SecurityConfigurationTests {
public void testWebConfiguration() throws Exception { public void testWebConfiguration() throws Exception {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class, EndpointsProperties.class, this.context.register(SecurityAutoConfiguration.class,
EndpointAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class); PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertNotNull(this.context.getBean(AuthenticationManager.class)); assertNotNull(this.context.getBean(AuthenticationManager.class));
@ -53,7 +55,8 @@ public class SecurityConfigurationTests {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext()); this.context.setServletContext(new MockServletContext());
this.context.register(TestConfiguration.class, SecurityAutoConfiguration.class, this.context.register(TestConfiguration.class, SecurityAutoConfiguration.class,
EndpointsProperties.class, PropertyPlaceholderAutoConfiguration.class); EndpointAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
assertEquals(this.context.getBean(TestConfiguration.class).authenticationManager, assertEquals(this.context.getBean(TestConfiguration.class).authenticationManager,
this.context.getBean(AuthenticationManager.class)); this.context.getBean(AuthenticationManager.class));

@ -1,43 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.autoconfigure.ActuatorAutoConfiguration.ServerPropertiesConfiguration;
import org.springframework.bootstrap.actuate.endpoint.shutdown.ShutdownEndpoint;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class ShutdownConfigurationTests {
private AnnotationConfigApplicationContext context;
@Test
public void testEndpointConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(ShutdownConfiguration.class,
ServerPropertiesConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(ShutdownEndpoint.class));
}
}

@ -1,42 +0,0 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
*/
public class TraceFilterConfigurationTests {
private AnnotationConfigApplicationContext context;
@Test
public void testTraceConfiguration() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(PropertyPlaceholderAutoConfiguration.class,
TraceFilterConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(TraceRepository.class));
}
}

@ -0,0 +1,66 @@
/*
* 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.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link TraceRepositoryAutoConfiguration}.
*
* @author Phillip Webb
*/
public class TraceRepositoryAutoConfigurationTests {
@Test
public void configuresInMemoryTraceRepository() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TraceRepositoryAutoConfiguration.class);
assertNotNull(context.getBean(InMemoryTraceRepository.class));
context.close();
}
@Test
public void skipsIfRepositoryExists() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
Config.class, TraceRepositoryAutoConfiguration.class);
assertThat(context.getBeansOfType(InMemoryTraceRepository.class).size(),
equalTo(0));
assertThat(context.getBeansOfType(TraceRepository.class).size(), equalTo(1));
context.close();
}
@Configuration
public static class Config {
@Bean
public TraceRepository traceRepository() {
return mock(TraceRepository.class);
}
}
}

@ -17,25 +17,27 @@
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.autoconfigure;
import org.junit.Test; import org.junit.Test;
import org.springframework.bootstrap.actuate.endpoint.trace.TraceEndpoints; import org.springframework.bootstrap.actuate.trace.WebRequestTraceFilter;
import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.bootstrap.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
/** /**
* @author Dave Syer * Tests for {@link TraceWebFilterAutoConfiguration}.
*
* @author Phillip Webb
*/ */
public class TraceConfigurationTests { public class TraceWebFilterAutoConfigurationTest {
private AnnotationConfigApplicationContext context;
@Test @Test
public void testEndpointConfiguration() throws Exception { public void configureFilter() {
this.context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
this.context.register(TraceFilterConfiguration.class, TraceConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class); TraceRepositoryAutoConfiguration.class,
this.context.refresh(); TraceWebFilterAutoConfiguration.class);
assertNotNull(this.context.getBean(TraceEndpoints.class)); assertNotNull(context.getBean(WebRequestTraceFilter.class));
context.close();
} }
} }

@ -0,0 +1,111 @@
/*
* 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.actuate.endpoint;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.bootstrap.actuate.TestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.http.MediaType;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Abstract base class for endpoint tests.
*
* @author Phillip Webb
*/
public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
protected AnnotationConfigApplicationContext context;
private final Class<?> configClass;
private final Class<?> type;
private final String path;
private final boolean sensitive;
private final String property;
private MediaType[] produces;
public AbstractEndpointTests(Class<?> configClass, Class<?> type, String path,
boolean sensitive, String property, MediaType... produces) {
this.configClass = configClass;
this.type = type;
this.path = path;
this.sensitive = sensitive;
this.property = property;
this.produces = produces;
}
@Before
public void setup() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(this.configClass);
this.context.refresh();
}
@Test
public void producesMediaType() {
assertThat(getEndpointBean().getProduces(), equalTo(this.produces));
}
@Test
public void getPath() throws Exception {
assertThat(getEndpointBean().getPath(), equalTo(this.path));
}
@Test
public void isSensitive() throws Exception {
assertThat(getEndpointBean().isSensitive(), equalTo(this.sensitive));
}
@Test
public void pathOverride() throws Exception {
this.context = new AnnotationConfigApplicationContext();
TestUtils.addEnviroment(this.context, this.property + ".path:/mypath");
this.context.register(this.configClass);
this.context.refresh();
assertThat(getEndpointBean().getPath(), equalTo("/mypath"));
}
@Test
public void isSensitiveOverride() throws Exception {
this.context = new AnnotationConfigApplicationContext();
PropertySource<?> propertySource = new MapPropertySource("test",
Collections.<String, Object> singletonMap(this.property + ".sensitive",
String.valueOf(!this.sensitive)));
this.context.getEnvironment().getPropertySources().addFirst(propertySource);
this.context.register(this.configClass);
this.context.refresh();
assertThat(getEndpointBean().isSensitive(), equalTo(!this.sensitive));
}
@SuppressWarnings("unchecked")
protected T getEndpointBean() {
return (T) this.context.getBean(this.type);
}
}

@ -0,0 +1,55 @@
/*
* 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.actuate.endpoint;
import org.junit.Test;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link BeansEndpoint}.
*
* @author Phillip Webb
*/
public class BeansEndpointTests extends AbstractEndpointTests<BeansEndpoint> {
public BeansEndpointTests() {
super(Config.class, BeansEndpoint.class, "/beans", true, "endpoints.beans",
MediaType.APPLICATION_JSON);
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke(), containsString("\"bean\": \"endpoint\""));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public BeansEndpoint endpoint() {
return new BeansEndpoint();
}
}
}

@ -0,0 +1,57 @@
/*
* 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.actuate.endpoint;
import java.lang.management.ThreadInfo;
import java.util.List;
import org.junit.Test;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link DumpEndpoint}.
*
* @author Phillip Webb
*/
public class DumpEndpointTests extends AbstractEndpointTests<DumpEndpoint> {
public DumpEndpointTests() {
super(Config.class, DumpEndpoint.class, "/dump", true, "endpoints.dump");
}
@Test
public void invoke() throws Exception {
List<ThreadInfo> threadInfo = getEndpointBean().invoke();
assertThat(threadInfo.size(), greaterThan(0));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public DumpEndpoint endpoint() {
return new DumpEndpoint();
}
}
}

@ -0,0 +1,53 @@
/*
* 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.actuate.endpoint;
import org.junit.Test;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link EnvironmentEndpoint}.
*
* @author Phillip Webb
*/
public class EnvironmentEndpointTests extends AbstractEndpointTests<EnvironmentEndpoint> {
public EnvironmentEndpointTests() {
super(Config.class, EnvironmentEndpoint.class, "/env", true, "endpoints.env");
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke().size(), greaterThan(0));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public EnvironmentEndpoint endpoint() {
return new EnvironmentEndpoint();
}
}
}

@ -0,0 +1,59 @@
/*
* 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.actuate.endpoint;
import org.junit.Test;
import org.springframework.bootstrap.actuate.health.HealthIndicator;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link HealthEndpoint}.
*
* @author Phillip Webb
*/
public class HealthEndpointTests extends AbstractEndpointTests<HealthEndpoint<String>> {
public HealthEndpointTests() {
super(Config.class, HealthEndpoint.class, "/health", false, "endpoints.health");
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke(), equalTo("fine"));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public HealthEndpoint<String> endpoint() {
return new HealthEndpoint<String>(new HealthIndicator<String>() {
@Override
public String health() {
return "fine";
}
});
}
}
}

@ -0,0 +1,55 @@
/*
* 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.actuate.endpoint;
import java.util.Collections;
import org.junit.Test;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link InfoEndpoint}.
*
* @author Phillip Webb
*/
public class InfoEndpointTests extends AbstractEndpointTests<InfoEndpoint> {
public InfoEndpointTests() {
super(Config.class, InfoEndpoint.class, "/info", true, "endpoints.info");
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke().get("a"), equalTo((Object) "b"));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public InfoEndpoint endpoint() {
return new InfoEndpoint(Collections.singletonMap("a", "b"));
}
}
}

@ -0,0 +1,64 @@
/*
* 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.actuate.endpoint;
import java.util.Collection;
import java.util.Collections;
import org.junit.Test;
import org.springframework.bootstrap.actuate.metrics.Metric;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link MetricsEndpoint}.
*
* @author Phillip Webb
*/
public class MetricsEndpointTests extends AbstractEndpointTests<MetricsEndpoint> {
public MetricsEndpointTests() {
super(Config.class, MetricsEndpoint.class, "/metrics", true, "endpoints.metrics");
}
@Test
public void invoke() throws Exception {
assertThat(getEndpointBean().invoke().get("a"), equalTo((Object) 0.5));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public MetricsEndpoint endpoint() {
final Metric metric = new Metric("a", 0.5f);
PublicMetrics metrics = new PublicMetrics() {
@Override
public Collection<Metric> metrics() {
return Collections.singleton(metric);
}
};
return new MetricsEndpoint(metrics);
}
}
}

@ -0,0 +1,68 @@
/*
* 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.actuate.endpoint;
import org.junit.Test;
import org.springframework.bootstrap.actuate.properties.ManagementServerProperties;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link ShutdownEndpoint}.
*
* @author Phillip Webb
*/
public class ShutdownEndpointTests extends AbstractEndpointTests<ShutdownEndpoint> {
public ShutdownEndpointTests() {
super(Config.class, ShutdownEndpoint.class, "/shutdown", true,
"endpoints.shutdown");
}
@Test
public void invoke() throws Exception {
assertThat((String) getEndpointBean().invoke().get("message"),
startsWith("Shutting down"));
assertTrue(this.context.isActive());
Thread.sleep(600);
assertFalse(this.context.isActive());
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public ManagementServerProperties managementServerProperties() {
ManagementServerProperties properties = new ManagementServerProperties();
properties.setAllowShutdown(true);
return properties;
}
@Bean
public ShutdownEndpoint endpoint() {
return new ShutdownEndpoint();
}
}
}

@ -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.actuate.endpoint;
import java.util.Collections;
import org.junit.Test;
import org.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
import org.springframework.bootstrap.actuate.trace.Trace;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link TraceEndpoint}.
*
* @author Phillip Webb
*/
public class TraceEndpointTests extends AbstractEndpointTests<TraceEndpoint> {
public TraceEndpointTests() {
super(Config.class, TraceEndpoint.class, "/trace", true, "endpoints.trace");
}
@Test
public void invoke() throws Exception {
Trace trace = getEndpointBean().invoke().get(0);
assertThat(trace.getInfo().get("a"), equalTo((Object) "b"));
}
@Configuration
@EnableConfigurationProperties
public static class Config {
@Bean
public TraceEndpoint endpoint() {
TraceRepository repository = new InMemoryTraceRepository();
repository.add(Collections.<String, Object> singletonMap("a", "b"));
return new TraceEndpoint(repository);
}
}
}

@ -0,0 +1,51 @@
/*
* 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.actuate.endpoint;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.bootstrap.actuate.metrics.InMemoryMetricRepository;
import org.springframework.bootstrap.actuate.metrics.Metric;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link VanillaPublicMetrics}.
*
* @author Phillip Webb
*/
public class VanillaPublicMetricsTests {
@Test
public void testMetrics() throws Exception {
InMemoryMetricRepository repository = new InMemoryMetricRepository();
repository.set("a", 0.5, new Date());
VanillaPublicMetrics publicMetrics = new VanillaPublicMetrics(repository);
Map<String, Metric> results = new HashMap<String, Metric>();
for (Metric metric : publicMetrics.metrics()) {
results.put(metric.getName(), metric);
}
assertTrue(results.containsKey("mem"));
assertTrue(results.containsKey("mem.free"));
assertThat(results.get("a").getValue(), equalTo(0.5));
}
}

@ -14,26 +14,30 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.actuate.autoconfigure; package org.springframework.bootstrap.actuate.endpoint.mvc;
import org.junit.Test; import org.junit.Test;
import org.springframework.bootstrap.actuate.endpoint.health.HealthEndpoint; import org.springframework.bootstrap.actuate.endpoint.Endpoint;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
/** /**
* @author Dave Syer * Tests for {@link EndpointHandlerAdapter}.
*
* @author Phillip Webb
*/ */
public class HealthConfigurationTests { public class EndpointHandlerAdapterTests {
private AnnotationConfigApplicationContext context; private EndpointHandlerAdapter adapter = new EndpointHandlerAdapter();
@Test @Test
public void testTraceConfiguration() throws Exception { public void onlySupportsEndpoints() throws Exception {
this.context = new AnnotationConfigApplicationContext(); assertTrue(this.adapter.supports(mock(Endpoint.class)));
this.context.register(HealthConfiguration.class); assertFalse(this.adapter.supports(mock(Object.class)));
this.context.refresh();
assertNotNull(this.context.getBean(HealthEndpoint.class));
} }
// FIXME tests
} }

@ -0,0 +1,122 @@
/*
* 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.actuate.endpoint.mvc;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.bootstrap.actuate.endpoint.AbstractEndpoint;
import org.springframework.bootstrap.actuate.endpoint.ActionEndpoint;
import org.springframework.mock.web.MockHttpServletRequest;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link EndpointHandlerMapping}.
*
* @author Phillip Webb
*/
public class EndpointHandlerMappingTests {
@Test
public void withoutPrefix() throws Exception {
TestEndpoint endpointA = new TestEndpoint("/a");
TestEndpoint endpointB = new TestEndpoint("/b");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(Arrays.asList(
endpointA, endpointB));
mapping.afterPropertiesSet();
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a"))
.getHandler(), equalTo((Object) endpointA));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/b"))
.getHandler(), equalTo((Object) endpointB));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/c")),
nullValue());
}
@Test
public void withPrefix() throws Exception {
TestEndpoint endpointA = new TestEndpoint("/a");
TestEndpoint endpointB = new TestEndpoint("/b");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(Arrays.asList(
endpointA, endpointB));
mapping.setPrefix("/a");
mapping.afterPropertiesSet();
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/a"))
.getHandler(), equalTo((Object) endpointA));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/b"))
.getHandler(), equalTo((Object) endpointB));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a")),
nullValue());
}
@Test
public void onlyGetHttpMethodForNonActionEndpoints() throws Exception {
TestEndpoint endpoint = new TestEndpoint("/a");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(
Arrays.asList(endpoint));
mapping.afterPropertiesSet();
assertNotNull(mapping.getHandler(new MockHttpServletRequest("GET", "/a")));
assertNull(mapping.getHandler(new MockHttpServletRequest("POST", "/a")));
}
@Test
public void onlyPostHttpMethodForActionEndpoints() throws Exception {
TestEndpoint endpoint = new TestActionEndpoint("/a");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(
Arrays.asList(endpoint));
mapping.afterPropertiesSet();
assertNull(mapping.getHandler(new MockHttpServletRequest("GET", "/a")));
assertNotNull(mapping.getHandler(new MockHttpServletRequest("POST", "/a")));
}
@Test
public void disabled() throws Exception {
TestEndpoint endpointA = new TestEndpoint("/a");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(
Arrays.asList(endpointA));
mapping.setDisabled(true);
mapping.afterPropertiesSet();
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a")),
nullValue());
}
private static class TestEndpoint extends AbstractEndpoint<Object> {
public TestEndpoint(String path) {
super(path);
}
@Override
public Object invoke() {
return null;
}
}
private static class TestActionEndpoint extends TestEndpoint implements
ActionEndpoint<Object> {
public TestActionEndpoint(String path) {
super(path);
}
}
}

@ -0,0 +1,40 @@
/*
* 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.actuate.fixme;
/**
* @author Dave Syer
*/
public class ErrorConfigurationTests {
// private AnnotationConfigApplicationContext context;
//
// @Test
// public void testErrorEndpointConfiguration() throws Exception {
// this.context = new AnnotationConfigApplicationContext();
// this.context.register(ErrorConfiguration.class,
// ActuatorServerPropertiesConfiguration.class,
// PropertyPlaceholderAutoConfiguration.class);
// this.context.refresh();
// assertNotNull(this.context.getBean(ErrorEndpoint.class));
// ConfigurableEmbeddedServletContainerFactory factory = Mockito
// .mock(ConfigurableEmbeddedServletContainerFactory.class);
// this.context.getBean(EmbeddedServletContainerCustomizer.class).customize(factory);
// Mockito.verify(factory).addErrorPages(Mockito.any(ErrorPage.class));
// }
}

@ -0,0 +1,37 @@
/*
* 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.actuate.health;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link VanillaHealthIndicator}.
*
* @author Phillip Webb
*/
public class VanillaHealthIndicatorTests {
@Test
public void ok() throws Exception {
VanillaHealthIndicator healthIndicator = new VanillaHealthIndicator();
assertThat(healthIndicator.health(), equalTo("ok"));
}
}

@ -0,0 +1,37 @@
/*
* 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.actuate.metrics;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.fail;
/**
* Tests for {@link DefaultCounterService}.
*/
@Ignore
public class DefaultCounterServiceTests {
// FIXME
@Test
public void test() {
fail("Not yet implemented");
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save