Restructure 'bootstrap' to use 'zero'

pull/7/head
Phillip Webb 12 years ago
parent d039822064
commit 261955c50b

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="12">
<profile kind="CodeFormatterProfile" name="Spring Bootstrap" version="12">
<profile kind="CodeFormatterProfile" name="Spring Zero" version="12">
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>

@ -1,4 +1,4 @@
# Spring Bootstrap Actuator
# Spring Zero Actuator
Minimum fuss for getting applications up and running in production,
and in other environments. There is a strong emphasis on implementing
@ -23,8 +23,8 @@ RESTful web services but many features are more generic than that.
For a quick introduction and to get started quickly with a new
project, carry on reading. For more in depth coverage of the features
of Spring Bootstrap Actuator, go to the
[Feature Guide](https://github.com/SpringSource/spring-bootstrap/tree/master/spring-bootstrap-actuator/docs/Features.md).
of Spring Zero Actuator, go to the
[Feature Guide](https://github.com/SpringSource/spring-bootstrap/tree/master/spring-zero-actuator/docs/Features.md).
# Getting Started
@ -47,18 +47,18 @@ If you are using Maven create a really simple `pom.xml` with 2 dependencies:
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-applications</artifactId>
<groupId>org.springframework.zero</groupId>
<artifactId>spring-zero-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-web-application</artifactId>
<groupId>org.springframework.zero</groupId>
<artifactId>spring-zero-web-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.bootstrap</groupId>
<artifactId>spring-bootstrap-service</artifactId>
<groupId>org.springframework.zero</groupId>
<artifactId>spring-zero-service</artifactId>
</dependency>
</dependencies>
<build>
@ -72,7 +72,7 @@ If you are using Maven create a really simple `pom.xml` with 2 dependencies:
</project>
If you like Gradle, that's fine, and you will know what to do with
those dependencies. The first dependency adds Spring Bootstrap auto
those dependencies. The first dependency adds Spring Zero auto
configuration and the Jetty container to your application, and the
second one adds some more opinionated stuff like the default
management endpoints. If you prefer Tomcat you can just add the
@ -128,7 +128,7 @@ endpoint. An endpoint can be implemented as a Spring MVC
}
You can launch that straight away using the Spring Bootstrap CLI
You can launch that straight away using the Spring Zero CLI
(without the `@EnableAutoConfiguration` and even without the import
statements that your IDE will add if you are using one), or you can
use the main method to launch it from your project jar. Just add a
@ -167,7 +167,7 @@ on a class and run it.
## Externalizing configuration
Spring Bootstrap likes you to externalize your configuration so you
Spring Zero likes you to externalize your configuration so you
can work with the same application code in different environments. To
get started with this you create a file in the root of your classpath
(`src/main/resources` if using Maven) - if you like YAML you can call
@ -187,7 +187,7 @@ or if you like Java `Properties` files, you can call it
management.port: 9001
logging.file: target/log.out
Those examples are properties that Spring Bootstrap itself binds to
Those examples are properties that Spring Zero itself binds to
out of the box, so if you make that change and run the app again, you
will find the home page on port 9000 instead of 8080:

@ -1,12 +1,12 @@
# Spring Bootstrap Actuator Feature Guide
# Spring Zero Actuator Feature Guide
Here are some (most, hopefully all) the features of Spring Bootstrap
Here are some (most, hopefully all) the features of Spring Zero
Actuator with some commentary to help you start using them. We
recommend you first build a project with the Actuator (e.g. the
getting started project from the main README), and then try each
feature in turn there.
TODO: some of these are features of Spring Bootstrap (or
TODO: some of these are features of Spring Zero (or
`SpringApplication`) not the Actuator.
TODO: group things together and break them out into separate files.
@ -33,7 +33,7 @@ The default value comes after the first colon (":").
## Externalized Configuration
In addition to command line option arguments, Spring Bootstrap will
In addition to command line option arguments, Spring Zero will
pick up a file called `application.properties` in the root of your
classpath (if there is one) and add those properties to the Spring
`Environment`. The search path for `application.properties` is
@ -49,7 +49,7 @@ previously defined values (e.g. from System properties), e.g.
app.name: MyApp
app.description: ${app.name} is a Cool New App
Spring Bootstrap also binds the properties to any bean in your
Spring Zero also binds the properties to any bean in your
application context whose type is `@ConfigurationProperties`. The
Actuator provides some of those beans out of the box, so you can
easily customize server and management properties (ports etc.),
@ -63,7 +63,7 @@ configuration and make it only available in certain environments. Any
`@Component` that is marked with `@Profile` will only be loaded in the
profile specified by the latter annotation.
Spring Bootstrap takes it a stage further. If you include in your
Spring Zero takes it a stage further. If you include in your
`application.properties` a value for a property named
`spring.active.profiles` then those profiles will be active by
default. E.g.
@ -72,7 +72,7 @@ default. E.g.
## Profile-dependent configuration
Spring Bootstrap loads additional properties files if there are active
Spring Zero loads additional properties files if there are active
profiles using a naming convention `application-{profile}.properties`.
Property values from those files override trhe default ones.
@ -98,7 +98,7 @@ to one of your `@Configuration` (or `@Component`) classes. Then you can
in any of your component classes to grab that configuration and use it.
Spring Bootstrap uses some relaxed rules for binding `Environment`
Spring Zero uses some relaxed rules for binding `Environment`
properties to `@ConfigurationProperties` beans, so there doesn't need
to be an exact match between the `Environment` property name and the
bean property name. Common examples where this is useful include
@ -137,7 +137,7 @@ compiler or IDE.
YAML is a superset of JSON, and as such is a very convenient format
for specifying hierarchical configuration data, such as that supported
by Spring Bootstrap Actuator. If you prefer to use
by Spring Zero Actuator. If you prefer to use
[YAML](http://yaml.org) instead of Properties files you just need to
include a file called `application.yml` in the root of your classpath
@ -217,7 +217,7 @@ properties in the application properties (see
* To enable the Tomcat access log valve (very common in production environments)
More fine-grained control of the Tomcat container is available if you
need it. Instead of letting Spring Bootstrap create the container for
need it. Instead of letting Spring Zero create the container for
you, just create a bean of type
`TomcatEmbeddedServletContainerFactory` and override one of its
methods, or inject some customizations, e.g.
@ -261,7 +261,7 @@ this.
## Customizing Logging
Spring Bootstrap uses SLF4J for logging, but leaves the implementation
Spring Zero uses SLF4J for logging, but leaves the implementation
open. The Starter projects and the Actuator use JDK native logging by
default, purely because it is always available. A default
configuration file is provided for JDK logging, and also for log4j and
@ -290,12 +290,12 @@ from the Spring `Environment` to System properties:
|PID |PID | The current process ID is discovered if possible and not already provided |
All the logging systems supported can consult System properties when
parsing their configuration files. See the defailt configurations in
`spring-bootstrap.jar` for examples.
parsing their configuration files. See the default configurations in
`spring-zero-core.jar` for examples.
## Application Context Initializers
To add additional application context initializers to the Bootstrap
To add additional application context initializers to the Zero
startup process, add a comma-delimited list of class names to the
`Environment` property `context.initializer.classes` (can be specified
via `application.properties`).

@ -1,137 +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.audit;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
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
* agent carried out an action of a particular type. This object records the details of
* 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
* @see AuditEventRepository
*/
public class AuditEvent implements Serializable {
private final Date timestamp;
private final String principal;
private final String type;
private final Map<String, Object> data;
/**
* Create a new audit event for the current time.
* @param principal The user principal responsible
* @param type the event type
* @param data The event data
*/
public AuditEvent(String principal, String type, Map<String, Object> data) {
this(new Date(), principal, type, data);
}
/**
* 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, String... data) {
this(new Date(), principal, type, convert(data));
}
/**
* 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,
Map<String, Object> data) {
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(type, "Type must not be null");
this.timestamp = timestamp;
this.principal = principal;
this.type = type;
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() {
return this.timestamp;
}
/**
* Returns the user principal responsible for the event or {@code null}.
*/
public String getPrincipal() {
return this.principal;
}
/**
* Returns the type of event.
*/
public String getType() {
return this.type;
}
/**
* Returns the event data.
*/
public Map<String, Object> getData() {
return this.data;
}
@Override
public String toString() {
return "AuditEvent [timestamp=" + this.timestamp + ", principal="
+ this.principal + ", type=" + this.type + ", data=" + this.data + "]";
}
}

@ -1,45 +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.audit;
import java.util.Date;
import java.util.List;
/**
* Repository for {@link AuditEvent}s.
*
* @author Dave Syer
*/
public interface AuditEventRepository {
/**
* Find audit events relating to the specified principal since the time provided.
*
* @param principal the principal name to search for
* @param after timestamp of earliest result required
* @return audit events relating to the principal
*/
List<AuditEvent> find(String principal, Date after);
/**
* Log an event.
*
* @param event the audit event to log
*/
void add(AuditEvent event);
}

@ -1,69 +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.audit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* In-memory {@link AuditEventRepository} implementation.
*
* @author Dave Syer
*/
public class InMemoryAuditEventRepository implements AuditEventRepository {
private int capacity = 100;
private Map<String, List<AuditEvent>> events = new HashMap<String, List<AuditEvent>>();
/**
* @param capacity the capacity to set
*/
public void setCapacity(int capacity) {
this.capacity = capacity;
}
@Override
public List<AuditEvent> find(String principal, Date after) {
synchronized (this.events) {
return Collections.unmodifiableList(getEvents(principal));
}
}
private List<AuditEvent> getEvents(String principal) {
if (!this.events.containsKey(principal)) {
this.events.put(principal, new ArrayList<AuditEvent>());
}
return this.events.get(principal);
}
@Override
public void add(AuditEvent event) {
synchronized (this.events) {
List<AuditEvent> list = getEvents(event.getPrincipal());
while (list.size() >= this.capacity) {
list.remove(0);
}
list.add(event);
}
}
}

@ -1,80 +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.audit.listener;
import java.util.Date;
import java.util.Map;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.util.Assert;
/**
* Spring {@link ApplicationEvent} to encapsulate {@link AuditEvent}s.
*
* @author Dave Syer
*/
public class AuditApplicationEvent extends ApplicationEvent {
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
*/
public AuditApplicationEvent(AuditEvent auditEvent) {
super(auditEvent);
Assert.notNull(auditEvent, "AuditEvent must not be null");
this.auditEvent = auditEvent;
}
/**
* @return the audit event
*/
public AuditEvent getAuditEvent() {
return this.auditEvent;
}
}

@ -1,47 +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.audit.listener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.bootstrap.actuate.audit.AuditEventRepository;
import org.springframework.context.ApplicationListener;
/**
* {@link ApplicationListener} that listens for {@link AuditEvent}s and stores them in a
* {@link AuditEventRepository}.
*
* @author Dave Syer
*/
public class AuditListener implements ApplicationListener<AuditApplicationEvent> {
private static Log logger = LogFactory.getLog(AuditListener.class);
private final AuditEventRepository auditEventRepository;
public AuditListener(AuditEventRepository auditEventRepository) {
this.auditEventRepository = auditEventRepository;
}
@Override
public void onApplicationEvent(AuditApplicationEvent event) {
logger.info(event.getAuditEvent());
this.auditEventRepository.add(event.getAuditEvent());
}
}

@ -1,68 +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.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.bootstrap.actuate.audit.AuditEventRepository;
import org.springframework.bootstrap.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.bootstrap.actuate.audit.listener.AuditListener;
import org.springframework.bootstrap.actuate.security.AuthenticationAuditListener;
import org.springframework.bootstrap.actuate.security.AuthorizationAuditListener;
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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link AuditEvent}s.
*
* @author Dave Syer
*/
@Configuration
public class AuditAutoConfiguration {
@Autowired(required = false)
private AuditEventRepository auditEventRepository = new InMemoryAuditEventRepository();
@Bean
public AuditListener auditListener() throws Exception {
return new AuditListener(this.auditEventRepository);
}
@Bean
@ConditionalOnClass(name = "org.springframework.security.authentication.event.AbstractAuthenticationEvent")
public AuthenticationAuditListener authenticationAuditListener() throws Exception {
return new AuthenticationAuditListener();
}
@Bean
@ConditionalOnClass(name = "org.springframework.security.access.event.AbstractAuthorizationEvent")
public AuthorizationAuditListener authorizationAuditListener() throws Exception {
return new AuthorizationAuditListener();
}
@ConditionalOnMissingBean(AuditEventRepository.class)
protected static class AuditEventRepositoryConfiguration {
@Bean
public AuditEventRepository auditEventRepository() throws Exception {
return new InMemoryAuditEventRepository();
}
}
}

@ -1,203 +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.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.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.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for common management
* {@link Endpoint}s.
*
* @author Dave Syer
* @author Phillip Webb
*/
@Configuration
public class EndpointAutoConfiguration {
@Autowired(required = false)
private HealthIndicator<? extends Object> healthIndicator = new VanillaHealthIndicator();
@Autowired
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
@ConditionalOnMissingBean
public InfoEndpoint infoEndpoint() throws Exception {
LinkedHashMap<String, Object> info = new LinkedHashMap<String, Object>();
info.putAll(this.properties.infoMap());
GitInfo gitInfo = this.properties.gitInfo();
if (gitInfo.getBranch() != null) {
info.put("git", gitInfo);
}
return new InfoEndpoint(info);
}
@Bean
@ConditionalOnMissingBean
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);
}
@Bean
@ConditionalOnMissingBean
public DumpEndpoint dumpEndpoint() {
return new DumpEndpoint();
}
@Bean
@ConditionalOnMissingBean
public ShutdownEndpoint shutdownEndpoint() {
return new ShutdownEndpoint();
}
@Configuration
protected static class InfoPropertiesConfiguration {
@Autowired
private ConfigurableEnvironment environment = new StandardEnvironment();
@Value("${spring.git.properties:classpath:git.properties}")
private Resource gitProperties;
public GitInfo gitInfo() throws Exception {
PropertiesConfigurationFactory<GitInfo> factory = new PropertiesConfigurationFactory<GitInfo>(
new GitInfo());
factory.setTargetName("git");
Properties properties = new Properties();
if (this.gitProperties.exists()) {
properties = PropertiesLoaderUtils.loadProperties(this.gitProperties);
}
factory.setProperties(properties);
return factory.getObject();
}
public Map<String, Object> infoMap() throws Exception {
PropertiesConfigurationFactory<Map<String, Object>> factory = new PropertiesConfigurationFactory<Map<String, Object>>(
new LinkedHashMap<String, Object>());
factory.setTargetName("info");
factory.setPropertySources(this.environment.getPropertySources());
return factory.getObject();
}
}
public static class GitInfo {
private String branch;
private Commit commit = new Commit();
public String getBranch() {
return this.branch;
}
public void setBranch(String branch) {
this.branch = branch;
}
public Commit getCommit() {
return this.commit;
}
public static class Commit {
private String id;
private String time;
public String getId() {
return this.id == null ? "" : (this.id.length() > 7 ? this.id.substring(
0, 7) : this.id);
}
public void setId(String id) {
this.id = id;
}
public String getTime() {
return this.time;
}
public void setTime(String time) {
this.time = time;
}
}
}
}

@ -1,163 +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.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;
}
};
}

@ -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.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.configuration.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,56 +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.Value;
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.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
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.Bean;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} to render errors via a MVC error
* controller.
*
* @author Dave Syer
*/
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
public class ErrorMvcAutoConfiguration implements EmbeddedServletContainerCustomizer {
@Value("${error.path:/error}")
private String errorPath = "/error";
@Bean
@ConditionalOnMissingBean(ErrorController.class)
public BasicErrorController basicErrorController() {
return new BasicErrorController();
}
@Override
public void customize(ConfigurableEmbeddedServletContainerFactory factory) {
factory.addErrorPages(new ErrorPage(this.errorPath));
}
}

@ -1,44 +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.ManagementServerProperties;
import org.springframework.bootstrap.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.bootstrap.context.annotation.AutoConfigureAfter;
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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the
* {@link ManagementServerPropertiesAutoConfiguration} bean.
*
* @author Dave Syer
*/
@Configuration
@AutoConfigureAfter(ServerPropertiesAutoConfiguration.class)
@EnableConfigurationProperties
public class ManagementServerPropertiesAutoConfiguration {
@Bean(name = "org.springframework.bootstrap.actuate.properties.ManagementServerProperties")
@ConditionalOnMissingBean
public ManagementServerProperties serverProperties() {
return new ManagementServerProperties();
}
}

@ -1,131 +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.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.actuate.metrics.CounterService;
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.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.util.UrlPathHelper;
/**
* {@link EnableAutoConfiguration Auto-configuration} that records Servlet interactions
* with a {@link CounterService} and {@link GaugeService}.
*
* @author Dave Syer
* @author Phillip Webb
*/
@Configuration
@ConditionalOnBean({ CounterService.class, GaugeService.class })
@ConditionalOnClass({ Servlet.class })
@AutoConfigureAfter(MetricRepositoryAutoConfiguration.class)
public class MetricFilterAutoConfiguration {
private static final int UNDEFINED_HTTP_STATUS = 999;
@Autowired
private CounterService counterService;
@Autowired
private GaugeService gaugeService;
@Bean
public Filter metricFilter() {
return new MetricsFilter();
}
/**
* Filter that counts requests and measures processing times.
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
private final class MetricsFilter extends GenericFilterBean {
// FIXME parameterize the order (ideally it runs before any other filter)
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if ((request instanceof HttpServletRequest)
&& (response instanceof HttpServletResponse)) {
doFilter((HttpServletRequest) request, (HttpServletResponse) response,
chain);
} else {
chain.doFilter(request, response);
}
}
public void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
UrlPathHelper helper = new UrlPathHelper();
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 int getStatus(HttpServletResponse response) {
try {
return response.getStatus();
} catch (Exception e) {
return UNDEFINED_HTTP_STATUS;
}
}
private String getKey(String string) {
// graphite compatible metric names
String value = string.replace("/", ".");
value = value.replace("..", ".");
if (value.endsWith(".")) {
value = value + "root";
}
if (value.startsWith("_")) {
value = value.substring(1);
}
return value;
}
}
}

@ -1,56 +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.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.bootstrap.actuate.metrics.InMemoryMetricRepository;
import org.springframework.bootstrap.actuate.metrics.MetricRepository;
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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for metrics services.
*
* @author Dave Syer
*/
@Configuration
public class MetricRepositoryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CounterService counterService() {
return new DefaultCounterService(metricRepository());
}
@Bean
@ConditionalOnMissingBean
public GaugeService gaugeService() {
return new DefaultGaugeService(metricRepository());
}
@Bean
@ConditionalOnMissingBean
protected MetricRepository metricRepository() {
return new InMemoryMetricRepository();
}
}

@ -1,219 +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 java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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.web.ErrorController;
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.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
/**
* {@link EnableAutoConfiguration Auto-configuration} for security of a web application or
* service. By default everything is secured with HTTP Basic authentication except the
* {@link SecurityProperties#getIgnored() explicitly ignored} paths (defaults to
* <code>&#47;css&#47;**, &#47;js&#47;**, &#47;images&#47;**, &#47;**&#47;favicon.ico</code>
* ). 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
* defaults). The user details for authentication are just placeholders
* <code>(username=user,
* password=password)</code> but can easily be customized by providing a bean definition
* of type {@link AuthenticationManager}. Also provides audit logging of authentication
* events.
*
* <p>
* The framework {@link Endpoint}s (used to expose application information to operations)
* include a {@link Endpoint#isSensitive() sensitive} configuration option which will be
* used as a security hint by the filter created here.
*
* <p>
* Some common simple customizations:
* <ul>
* <li>Switch off security completely and permanently: remove Spring Security from the
* classpath or {@link EnableAutoConfiguration#exclude() exclude} this configuration.</li>
* <li>Switch off security temporarily (e.g. for a dev environment): set
* <code>security.basic.enabled: false</code></li>
* <li>Customize the user details: add an AuthenticationManager bean</li>
* <li>Add form login for user facing resources: add a
* {@link WebSecurityConfigurerAdapter} and use {@link HttpSecurity#formLogin()}</li>
* </ul>
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass({ EnableWebSecurity.class })
@EnableWebSecurity
@EnableConfigurationProperties
public class SecurityAutoConfiguration {
@Bean(name = "org.springframework.bootstrap.actuate.properties.SecurityProperties")
@ConditionalOnMissingBean
public SecurityProperties securityProperties() {
return new SecurityProperties();
}
@Bean
@ConditionalOnMissingBean
public AuthenticationEventPublisher authenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher();
}
@Bean
@ConditionalOnMissingBean({ BoostrapWebSecurityConfigurerAdapter.class })
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
return new BoostrapWebSecurityConfigurerAdapter();
}
// Give user-supplied filters a chance to be last in line
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class BoostrapWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
private static final String[] NO_PATHS = new String[0];
@Autowired
private SecurityProperties security;
@Autowired(required = false)
private EndpointHandlerMapping endpointHandlerMapping;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
@Autowired(required = false)
private ErrorController errorController;
@Override
protected void configure(HttpSecurity http) throws Exception {
if (this.security.isRequireSsl()) {
http.requiresChannel().anyRequest().requiresSecure();
}
if (this.security.getBasic().isEnabled()) {
String[] paths = getSecurePaths();
http.exceptionHandling().authenticationEntryPoint(entryPoint()).and()
.requestMatchers().antMatchers(paths);
http.httpBasic().and().anonymous().disable();
http.authorizeUrls().anyRequest()
.hasRole(this.security.getBasic().getRole());
}
// No cookies for service endpoints by default
http.sessionManagement().sessionCreationPolicy(this.security.getSessions());
}
private String[] getSecurePaths() {
List<String> list = new ArrayList<String>();
for (String path : this.security.getBasic().getPath()) {
path = (path == null ? "" : path.trim());
if (path.equals("/**")) {
return new String[] { path };
}
if (!path.equals("")) {
list.add(path);
}
}
// FIXME makes more sense to secure endpoints with a different role
list.addAll(Arrays.asList(getEndpointPaths(true)));
return list.toArray(new String[list.size()]);
}
private AuthenticationEntryPoint entryPoint() {
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName(this.security.getBasic().getRealm());
return entryPoint;
}
@Override
public void configure(WebSecurity builder) throws Exception {
IgnoredRequestConfigurer ignoring = builder.ignoring();
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
protected AuthenticationManager authenticationManager() throws Exception {
AuthenticationManager manager = super.authenticationManager();
if (manager instanceof ProviderManager) {
((ProviderManager) manager)
.setAuthenticationEventPublisher(this.authenticationEventPublisher);
}
return manager;
}
}
@ConditionalOnMissingBean(AuthenticationManager.class)
@Configuration
public static class AuthenticationManagerConfiguration {
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder().inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and().and()
.build();
}
}
}

@ -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.springframework.bootstrap.actuate.trace.InMemoryTraceRepository;
import org.springframework.bootstrap.actuate.trace.TraceRepository;
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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link TraceRepository tracing}.
*
* @author Dave Syer
*/
@Configuration
public class TraceRepositoryAutoConfiguration {
@ConditionalOnMissingBean
@Bean
public TraceRepository traceRepository() {
return new InMemoryTraceRepository();
}
}

@ -1,54 +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.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.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link WebRequestTraceFilter
* tracing}.
*
* @author Dave Syer
*/
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@AutoConfigureAfter(TraceRepositoryAutoConfiguration.class)
public class TraceWebFilterAutoConfiguration {
@Autowired
private TraceRepository traceRepository;
@Value("${management.dump_requests:false}")
private boolean dumpRequests;
@Bean
public WebRequestTraceFilter webRequestLoggingFilter(BeanFactory beanFactory) {
WebRequestTraceFilter filter = new WebRequestTraceFilter(this.traceRepository);
filter.setDumpRequests(this.dumpRequests);
return filter;
}
}

@ -1,71 +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;
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;
}
}

@ -1,27 +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;
/**
* Tagging interface used to indicate that {@link Endpoint} that performs some action.
* Allows mappings to refine the types of request supported.
*
* @author Phillip Webb
*/
public interface ActionEndpoint<T> extends Endpoint<T> {
}

@ -1,62 +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;
import org.springframework.beans.BeansException;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.LiveBeansView;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
/**
* 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 JVM will be shown (and the corresponding MBeans will be registered per the standard
* behavior of LiveBeansView). Otherwise only the current application context.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.beans", ignoreUnknownFields = false)
public class BeansEndpoint extends AbstractEndpoint<String> implements
ApplicationContextAware {
private LiveBeansView liveBeansView = new LiveBeansView();
public BeansEndpoint() {
super("/beans");
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context.getEnvironment()
.getProperty(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME) == null) {
this.liveBeansView.setApplicationContext(context);
}
}
@Override
public MediaType[] getProduces() {
return new MediaType[] { MediaType.APPLICATION_JSON };
}
@Override
public String invoke() {
return this.liveBeansView.getSnapshotAsJson();
}
}

@ -1,47 +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;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.List;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
/**
* {@link Endpoint} to expose thread info.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.dump", ignoreUnknownFields = false)
public class DumpEndpoint extends AbstractEndpoint<List<ThreadInfo>> {
/**
* Create a new {@link DumpEndpoint} instance.
*/
public DumpEndpoint() {
super("/dump");
}
@Override
public List<ThreadInfo> invoke() {
return Arrays.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true,
true));
}
}

@ -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;
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();
}

@ -1,86 +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;
import java.util.LinkedHashMap;
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.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
/**
* {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information.
*
* @author Dave Syer
* @author Phillip Webb
*/
@ConfigurationProperties(name = "endpoints.env", ignoreUnknownFields = false)
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> implements
EnvironmentAware {
private Environment environment;
/**
* Create a new {@link EnvironmentEndpoint} instance.
*/
public EnvironmentEndpoint() {
super("/env");
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
for (PropertySource<?> source : getPropertySources()) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
Map<String, Object> map = new LinkedHashMap<String, Object>();
for (String name : enumerable.getPropertyNames()) {
map.put(name, sanitize(name, enumerable.getProperty(name)));
}
result.put(source.getName(), map);
}
}
return result;
}
private Iterable<PropertySource<?>> getPropertySources() {
if (this.environment != null
&& this.environment instanceof ConfigurableEnvironment) {
return ((ConfigurableEnvironment) this.environment).getPropertySources();
}
return new StandardEnvironment().getPropertySources();
}
private Object sanitize(String name, Object object) {
if (name.toLowerCase().endsWith("password")
|| name.toLowerCase().endsWith("secret")) {
return object == null ? null : "******";
}
return object;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

@ -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.endpoint;
import org.springframework.bootstrap.actuate.health.HealthIndicator;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose application health.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.health", ignoreUnknownFields = false)
public class HealthEndpoint<T> extends AbstractEndpoint<T> {
private HealthIndicator<? extends T> indicator;
/**
* Create a new {@link HealthIndicator} instance.
*
* @param indicator the health indicator
*/
public HealthEndpoint(HealthIndicator<? extends T> indicator) {
super("/health", false);
Assert.notNull(indicator, "Indicator must not be null");
this.indicator = indicator;
}
@Override
public T invoke() {
return this.indicator.health();
}
}

@ -1,58 +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;
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();
}
}

@ -1,56 +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;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.bootstrap.actuate.metrics.Metric;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.util.Assert;
/**
* {@link Endpoint} to expose {@link PublicMetrics}.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.metrics", ignoreUnknownFields = false)
public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
private PublicMetrics metrics;
/**
* Create a new {@link MetricsEndpoint} instance.
*
* @param metrics the metrics to expose
*/
public MetricsEndpoint(PublicMetrics metrics) {
super("/metrics");
Assert.notNull(metrics, "Metrics must not be null");
this.metrics = metrics;
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
for (Metric metric : this.metrics.metrics()) {
result.put(metric.getName(), metric.getValue());
}
return result;
}
}

@ -1,36 +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;
import java.util.Collection;
import org.springframework.bootstrap.actuate.metrics.Metric;
/**
* Interface to expose specific {@link Metric}s via a {@link MetricsEndpoint}.
*
* @author Dave Syer
* @see VanillaPublicMetrics
*/
public interface PublicMetrics {
/**
* @return an indication of current state through metrics
*/
Collection<Metric> metrics();
}

@ -1,81 +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;
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;
}
}
}

@ -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.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();
}
}

@ -1,52 +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;
import java.util.Collection;
import java.util.LinkedHashSet;
import org.springframework.bootstrap.actuate.metrics.Metric;
import org.springframework.bootstrap.actuate.metrics.MetricRepository;
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
*/
public class VanillaPublicMetrics implements PublicMetrics {
private MetricRepository metricRepository;
public VanillaPublicMetrics(MetricRepository metricRepository) {
Assert.notNull(metricRepository, "MetricRepository must not be null");
this.metricRepository = metricRepository;
}
@Override
public Collection<Metric> metrics() {
Collection<Metric> result = new LinkedHashSet<Metric>(
this.metricRepository.findAll());
result.add(new Metric("mem", new Long(Runtime.getRuntime().totalMemory()) / 1024));
result.add(new Metric("mem.free",
new Long(Runtime.getRuntime().freeMemory()) / 1024));
result.add(new Metric("processors", Runtime.getRuntime().availableProcessors()));
return result;
}
}

@ -1,225 +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.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;
}
}
}

@ -1,133 +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.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,141 +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.fixme;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.ConditionalOnClass;
import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* Configuration for creating a new container (e.g. tomcat) for the management endpoints.
*
* @author Dave Syer
*/
@Configuration
@EnableWebMvc
@Import(ManagementSecurityConfiguration.class)
public class ManagementServerConfiguration {
// FIXME delete when security works
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
ApplicationContext context) {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public BasicErrorController errorEndpoint() {
return new BasicErrorController();
}
@Bean
@ConditionalOnBean(TomcatEmbeddedServletContainerFactory.class)
public EmbeddedServletContainerFactory tomcatContainer(
HierarchicalBeanFactory beanFactory) {
TomcatEmbeddedServletContainerFactory factory = beanFactory
.getParentBeanFactory().getBean(
TomcatEmbeddedServletContainerFactory.class);
return factory.getChildContextFactory("Management");
}
@Bean
@ConditionalOnBean(JettyEmbeddedServletContainerFactory.class)
public EmbeddedServletContainerFactory jettyContainer() {
return new JettyEmbeddedServletContainerFactory();
}
@Configuration
protected static class ServerCustomizationConfiguration implements
EmbeddedServletContainerCustomizer {
@Value("${endpoints.error.path:/error}")
private String errorPath = "/error";
@Autowired
private ApplicationContext beanFactory;
@Override
public void customize(ConfigurableEmbeddedServletContainerFactory factory) {
ManagementServerProperties configuration = this.beanFactory
.getBean(ManagementServerProperties.class);
factory.setPort(configuration.getPort());
factory.setAddress(configuration.getAddress());
factory.setContextPath(configuration.getContextPath());
factory.addErrorPages(new ErrorPage(this.errorPath));
}
}
}
@Configuration
@ConditionalOnClass(name = {
"org.springframework.security.config.annotation.web.EnableWebSecurity",
"javax.servlet.Filter" })
class ManagementSecurityConfiguration {
@Bean
// TODO: enable and get rid of the empty filter when @ConditionalOnBean works
// @ConditionalOnBean(name = "springSecurityFilterChain")
public Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) {
BeanFactory parent = beanFactory.getParentBeanFactory();
if (parent != null && parent.containsBean("springSecurityFilterChain")) {
return parent.getBean("springSecurityFilterChain", Filter.class);
}
return new GenericFilterBean() {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
};
}
}

@ -1,32 +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.health;
/**
* Strategy interface used to provide an indication of application health.
*
* @author Dave Syer
* @see VanillaHealthIndicator
*/
public interface HealthIndicator<T> {
/**
* @return an indication of health
*/
T health();
}

@ -1,31 +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.health;
/**
* Default implementation of {@link HealthIndicator} that simply returns "ok".
*
* @author Dave Syer
*/
public class VanillaHealthIndicator implements HealthIndicator<String> {
@Override
public String health() {
return "ok";
}
}

@ -1,44 +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.metrics;
/**
* A service that can be used to increment, decrement and reset a {@link Metric}.
*
* @author Dave Syer
*/
public interface CounterService {
/**
* Increment the specified metric by 1.
* @param metricName the name of the metric
*/
void increment(String metricName);
/**
* Decrement the specified metric by 1.
* @param metricName the name of the metric
*/
void decrement(String metricName);
/**
* Reset the specified metric to 0.
* @param metricName the name of the metric
*/
void reset(String metricName);
}

@ -1,62 +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.metrics;
import java.util.Date;
/**
* Default implementation of {@link CounterService}.
*
* @author Dave Syer
*/
public class DefaultCounterService implements CounterService {
private MetricRepository repository;
/**
* Create a {@link DefaultCounterService} instance.
* @param repository the underlying repository used to manage metrics
*/
public DefaultCounterService(MetricRepository repository) {
super();
this.repository = repository;
}
@Override
public void increment(String metricName) {
this.repository.increment(wrap(metricName), 1, new Date());
}
@Override
public void decrement(String metricName) {
this.repository.increment(wrap(metricName), -1, new Date());
}
@Override
public void reset(String metricName) {
this.repository.set(wrap(metricName), 0, new Date());
}
private String wrap(String metricName) {
if (metricName.startsWith("counter")) {
return metricName;
} else {
return "counter." + metricName;
}
}
}

@ -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.metrics;
import java.util.Date;
/**
* Default implementation of {@link GaugeService}.
*
* @author Dave Syer
*/
public class DefaultGaugeService implements GaugeService {
private MetricRepository metricRepository;
/**
* @param counterRepository
*/
public DefaultGaugeService(MetricRepository counterRepository) {
super();
this.metricRepository = counterRepository;
}
@Override
public void set(String metricName, double value) {
this.metricRepository.set(wrap(metricName), value, new Date());
}
private String wrap(String metricName) {
if (metricName.startsWith("gauge")) {
return metricName;
} else {
return "gauge." + metricName;
}
}
}

@ -1,33 +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.metrics;
/**
* A service that can be used to manage a {@link Metric} as a gauge.
*
* @author Dave Syer
*/
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);
}

@ -1,81 +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.metrics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author Dave Syer
*/
public class InMemoryMetricRepository implements MetricRepository {
private ConcurrentMap<String, Measurement> metrics = new ConcurrentHashMap<String, Measurement>();
@Override
public void increment(String metricName, int amount, Date timestamp) {
// FIXME this might not be thread safe
Measurement current = this.metrics.get(metricName);
if (current != null) {
Metric metric = current.getMetric();
this.metrics.replace(metricName, current,
new Measurement(timestamp, metric.increment(amount)));
} else {
this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric(
metricName, amount)));
}
}
@Override
public void set(String metricName, double value, Date timestamp) {
Measurement current = this.metrics.get(metricName);
if (current != null) {
Metric metric = current.getMetric();
this.metrics.replace(metricName, current,
new Measurement(timestamp, metric.set(value)));
} else {
this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric(
metricName, value)));
}
}
@Override
public void delete(String metricName) {
this.metrics.remove(metricName);
}
@Override
public Metric findOne(String metricName) {
if (this.metrics.containsKey(metricName)) {
return this.metrics.get(metricName).getMetric();
}
return new Metric(metricName, 0);
}
@Override
public Collection<Metric> findAll() {
ArrayList<Metric> result = new ArrayList<Metric>();
for (Measurement measurement : this.metrics.values()) {
result.add(measurement.getMetric());
}
return result;
}
}

@ -1,79 +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.metrics;
import java.util.Date;
import org.springframework.util.ObjectUtils;
/**
* A {@link Metric} at a given point in time.
*
* @author Dave Syer
*/
public final class Measurement {
private Date timestamp;
private Metric metric;
public Measurement(Date timestamp, Metric metric) {
this.timestamp = timestamp;
this.metric = metric;
}
public Date getTimestamp() {
return this.timestamp;
}
public Metric getMetric() {
return this.metric;
}
@Override
public String toString() {
return "Measurement [dateTime=" + this.timestamp + ", metric=" + this.metric
+ "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.timestamp);
result = prime * result + ObjectUtils.nullSafeHashCode(this.metric);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
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);
}
}

@ -1,113 +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.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
* @see MetricRepository
* @see CounterService
*/
public final class Metric {
private final String name;
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) {
super();
Assert.notNull(name, "Name must not be null");
this.name = name;
this.value = value;
}
/**
* Returns the name of the metric.
*/
public String getName() {
return this.name;
}
/**
* Returns the value of the metric.
*/
public double getValue() {
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) {
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) {
return new Metric(this.name, value);
}
@Override
public String toString() {
return "Metric [name=" + this.name + ", value=" + this.value + "]";
}
@Override
public int hashCode() {
int valueHashCode = ObjectUtils.hashCode(this.value);
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
result = prime * result + (valueHashCode ^ (valueHashCode >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
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);
}
}

@ -1,45 +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.metrics;
import java.util.Collection;
import java.util.Date;
/**
* A Repository used to manage {@link Metric}s.
*
* @author Dave Syer
*/
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 set(String metricName, double value, Date timestamp);
void delete(String metricName);
Metric findOne(String metricName);
Collection<Metric> findAll();
}

@ -1,85 +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 java.net.InetAddress;
import javax.validation.constraints.NotNull;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.bootstrap.properties.ServerProperties;
/**
* Properties for the management server (e.g. port and path settings).
*
* @author Dave Syer
* @see ServerProperties
*/
@ConfigurationProperties(name = "management", ignoreUnknownFields = false)
public class ManagementServerProperties {
private Integer port;
private InetAddress address;
@NotNull
private String contextPath = "";
private boolean allowShutdown = false;
public boolean isAllowShutdown() {
return this.allowShutdown;
}
public void setAllowShutdown(boolean allowShutdown) {
this.allowShutdown = allowShutdown;
}
/**
* 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;
}
/**
* 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;
}
public InetAddress getAddress() {
return this.address;
}
public void setAddress(InetAddress address) {
this.address = address;
}
public String getContextPath() {
return this.contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
}

@ -1,115 +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 org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.security.config.annotation.web.configurers.SessionCreationPolicy;
/**
* Properties for the security aspects of an application.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "security", ignoreUnknownFields = false)
public class SecurityProperties {
private boolean requireSsl;
private Basic basic = new Basic();
private SessionCreationPolicy sessions = SessionCreationPolicy.stateless;
private String[] ignored = new String[] { "/css/**", "/js/**", "/images/**",
"/**/favicon.ico" };
public SessionCreationPolicy getSessions() {
return this.sessions;
}
public void setSessions(SessionCreationPolicy sessions) {
this.sessions = sessions;
}
public Basic getBasic() {
return this.basic;
}
public void setBasic(Basic basic) {
this.basic = basic;
}
public boolean isRequireSsl() {
return this.requireSsl;
}
public void setRequireSsl(boolean requireSsl) {
this.requireSsl = requireSsl;
}
public void setIgnored(String... ignored) {
this.ignored = ignored;
}
public String[] getIgnored() {
return this.ignored;
}
public static class Basic {
private boolean enabled = true;
private String realm = "Spring";
private String[] path = new String[] { "/**" };
private String role = "USER";
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getRealm() {
return this.realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public String[] getPath() {
return this.path;
}
public void setPath(String... paths) {
this.path = paths;
}
public String getRole() {
return this.role;
}
public void setRole(String role) {
this.role = role;
}
}
}

@ -1,91 +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.security;
import java.util.HashMap;
import java.util.Map;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.bootstrap.actuate.audit.listener.AuditApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent;
/**
* {@link ApplicationListener} expose Spring Security {@link AbstractAuthenticationEvent
* authentication events} as {@link AuditEvent}s.
*
* @author Dave Syer
*/
public class AuthenticationAuditListener implements
ApplicationListener<AbstractAuthenticationEvent>, ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void onApplicationEvent(AbstractAuthenticationEvent event) {
if (event instanceof AbstractAuthenticationFailureEvent) {
onAuthenticationFailureEvent((AbstractAuthenticationFailureEvent) event);
} else if (event instanceof AuthenticationSwitchUserEvent) {
onAuthenticationSwitchUserEvent((AuthenticationSwitchUserEvent) event);
} else {
onAuthenticationEvent(event);
}
}
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) {
if (this.publisher != null) {
this.publisher.publishEvent(new AuditApplicationEvent(event));
}
}
}

@ -1,78 +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.security;
import java.util.HashMap;
import java.util.Map;
import org.springframework.bootstrap.actuate.audit.AuditEvent;
import org.springframework.bootstrap.actuate.audit.listener.AuditApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.security.access.event.AbstractAuthorizationEvent;
import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent;
import org.springframework.security.access.event.AuthorizationFailureEvent;
/**
* {@link ApplicationListener} expose Spring Security {@link AbstractAuthorizationEvent
* authorization events} as {@link AuditEvent}s.
*
* @author Dave Syer
*/
public class AuthorizationAuditListener implements
ApplicationListener<AbstractAuthorizationEvent>, ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void onApplicationEvent(AbstractAuthorizationEvent event) {
if (event instanceof AuthenticationCredentialsNotFoundEvent) {
onAuthenticationCredentialsNotFoundEvent((AuthenticationCredentialsNotFoundEvent) event);
} else if (event instanceof AuthorizationFailureEvent) {
onAuthorizationFailureEvent((AuthorizationFailureEvent) event);
}
}
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) {
if (this.publisher != null) {
this.publisher.publishEvent(new AuditApplicationEvent(event));
}
}
}

@ -1,61 +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.trace;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* In-memory implementation of {@link TraceRepository}.
*
* @author Dave Syer
*/
public class InMemoryTraceRepository implements TraceRepository {
private int capacity = 100;
private List<Trace> traces = new ArrayList<Trace>();
/**
* @param capacity the capacity to set
*/
public void setCapacity(int capacity) {
this.capacity = capacity;
}
@Override
public List<Trace> findAll() {
synchronized (this.traces) {
return Collections.unmodifiableList(this.traces);
}
}
@Override
public void add(Map<String, Object> map) {
Trace trace = new Trace(new Date(), map);
synchronized (this.traces) {
while (this.traces.size() >= this.capacity) {
this.traces.remove(0);
}
this.traces.add(trace);
}
}
}

@ -1,52 +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.trace;
import java.util.Date;
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
*/
public final class Trace {
private Date timestamp;
private Map<String, Object> info;
public Trace(Date timestamp, Map<String, Object> info) {
super();
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(info, "Info must not be null");
this.timestamp = timestamp;
this.info = info;
}
public Date getTimestamp() {
return this.timestamp;
}
public Map<String, Object> getInfo() {
return this.info;
}
}

@ -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.trace;
import java.util.List;
import java.util.Map;
/**
* A repository for {@link Trace}s.
*
* @author Dave Syer
*/
public interface TraceRepository {
/**
* Find all {@link Trace} objects contained in the repository.
*/
List<Trace> findAll();
/**
* Add a new {@link Trace} object at the current time.
* @param traceInfo trace information
*/
void add(Map<String, Object> traceInfo);
}

@ -1,145 +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.trace;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Servlet {@link Filter} that logs all requests to a {@link TraceRepository}.
*
* @author Dave Syer
*/
public class WebRequestTraceFilter implements Filter, Ordered {
final Log logger = LogFactory.getLog(WebRequestTraceFilter.class);
private boolean dumpRequests = false;
private final TraceRepository traceRepository;
private int order = Integer.MAX_VALUE;
private ObjectMapper objectMapper = new ObjectMapper();
/**
* @param traceRepository
*/
public WebRequestTraceFilter(TraceRepository traceRepository) {
this.traceRepository = traceRepository;
}
/**
* @param order the order to set
*/
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
/**
* Debugging feature. If enabled, and trace logging is enabled then web request
* headers will be logged.
*/
public void setDumpRequests(boolean dumpRequests) {
this.dumpRequests = dumpRequests;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
Map<String, Object> trace = getTrace(request);
this.traceRepository.add(trace);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Processing request " + request.getMethod() + " "
+ request.getRequestURI());
if (this.dumpRequests) {
try {
@SuppressWarnings("unchecked")
Map<String, Object> headers = (Map<String, Object>) trace
.get("headers");
this.logger.trace("Headers: "
+ this.objectMapper.writeValueAsString(headers));
} catch (JsonProcessingException e) {
throw new IllegalStateException("Cannot create JSON", e);
}
}
}
chain.doFilter(request, response);
}
protected Map<String, Object> getTrace(HttpServletRequest request) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
List<String> values = Collections.list(request.getHeaders(name));
Object value = values;
if (values.size() == 1) {
value = values.get(0);
} else if (values.isEmpty()) {
value = "";
}
map.put(name, value);
}
Map<String, Object> trace = new LinkedHashMap<String, Object>();
trace.put("method", request.getMethod());
trace.put("path", request.getRequestURI());
trace.put("headers", map);
return trace;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}

@ -1,110 +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.web;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
/**
* Basic global error {@link Controller}, rendering servlet container error codes and
* messages where available. More specific errors can be handled either using Spring MVC
* abstractions (e.g. {@code @ExceptionHandler}) or by adding servlet
* {@link AbstractEmbeddedServletContainerFactory#setErrorPages(java.util.Set) container
* error pages}.
*
* @author Dave Syer
*/
@Controller
public class BasicErrorController implements ErrorController {
private Log logger = LogFactory.getLog(BasicErrorController.class);
@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) {
Map<String, Object> map = error(request);
return new ModelAndView("error", map);
}
@RequestMapping(value = "${error.path:/error}")
@ResponseBody
public Map<String, Object> error(HttpServletRequest request) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("timestamp", new Date());
try {
Throwable error = (Throwable) request
.getAttribute("javax.servlet.error.exception");
Object obj = request.getAttribute("javax.servlet.error.status_code");
int status = 999;
if (obj != null) {
status = (Integer) obj;
map.put("error", HttpStatus.valueOf(status).getReasonPhrase());
} else {
map.put("error", "None");
}
map.put("status", status);
if (error != null) {
while (error instanceof ServletException) {
error = ((ServletException) error).getCause();
}
map.put("exception", error.getClass().getName());
map.put("message", error.getMessage());
String trace = request.getParameter("trace");
if (trace != null && !"false".equals(trace.toLowerCase())) {
StringWriter stackTrace = new StringWriter();
error.printStackTrace(new PrintWriter(stackTrace));
stackTrace.flush();
map.put("trace", stackTrace.toString());
}
this.logger.error(error);
} else {
Object message = request.getAttribute("javax.servlet.error.message");
map.put("message", message == null ? "No message available" : message);
}
return map;
} catch (Exception e) {
map.put("error", e.getClass().getName());
map.put("message", e.getMessage());
this.logger.error(e);
return map;
}
}
}

@ -1,31 +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.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();
}

@ -0,0 +1,137 @@
/*
* 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.zero.actuate.audit;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
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
* agent carried out an action of a particular type. This object records the details of
* 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
* @see AuditEventRepository
*/
public class AuditEvent implements Serializable {
private final Date timestamp;
private final String principal;
private final String type;
private final Map<String, Object> data;
/**
* Create a new audit event for the current time.
* @param principal The user principal responsible
* @param type the event type
* @param data The event data
*/
public AuditEvent(String principal, String type, Map<String, Object> data) {
this(new Date(), principal, type, data);
}
/**
* 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, String... data) {
this(new Date(), principal, type, convert(data));
}
/**
* 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,
Map<String, Object> data) {
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(type, "Type must not be null");
this.timestamp = timestamp;
this.principal = principal;
this.type = type;
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() {
return this.timestamp;
}
/**
* Returns the user principal responsible for the event or {@code null}.
*/
public String getPrincipal() {
return this.principal;
}
/**
* Returns the type of event.
*/
public String getType() {
return this.type;
}
/**
* Returns the event data.
*/
public Map<String, Object> getData() {
return this.data;
}
@Override
public String toString() {
return "AuditEvent [timestamp=" + this.timestamp + ", principal="
+ this.principal + ", type=" + this.type + ", data=" + this.data + "]";
}
}

@ -0,0 +1,45 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.zero.actuate.audit;
import java.util.Date;
import java.util.List;
/**
* Repository for {@link AuditEvent}s.
*
* @author Dave Syer
*/
public interface AuditEventRepository {
/**
* Find audit events relating to the specified principal since the time provided.
*
* @param principal the principal name to search for
* @param after timestamp of earliest result required
* @return audit events relating to the principal
*/
List<AuditEvent> find(String principal, Date after);
/**
* Log an event.
*
* @param event the audit event to log
*/
void add(AuditEvent event);
}

@ -0,0 +1,69 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.zero.actuate.audit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* In-memory {@link AuditEventRepository} implementation.
*
* @author Dave Syer
*/
public class InMemoryAuditEventRepository implements AuditEventRepository {
private int capacity = 100;
private Map<String, List<AuditEvent>> events = new HashMap<String, List<AuditEvent>>();
/**
* @param capacity the capacity to set
*/
public void setCapacity(int capacity) {
this.capacity = capacity;
}
@Override
public List<AuditEvent> find(String principal, Date after) {
synchronized (this.events) {
return Collections.unmodifiableList(getEvents(principal));
}
}
private List<AuditEvent> getEvents(String principal) {
if (!this.events.containsKey(principal)) {
this.events.put(principal, new ArrayList<AuditEvent>());
}
return this.events.get(principal);
}
@Override
public void add(AuditEvent event) {
synchronized (this.events) {
List<AuditEvent> list = getEvents(event.getPrincipal());
while (list.size() >= this.capacity) {
list.remove(0);
}
list.add(event);
}
}
}

@ -0,0 +1,80 @@
/*
* 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.zero.actuate.audit.listener;
import java.util.Date;
import java.util.Map;
import org.springframework.context.ApplicationEvent;
import org.springframework.util.Assert;
import org.springframework.zero.actuate.audit.AuditEvent;
/**
* Spring {@link ApplicationEvent} to encapsulate {@link AuditEvent}s.
*
* @author Dave Syer
*/
public class AuditApplicationEvent extends ApplicationEvent {
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
*/
public AuditApplicationEvent(AuditEvent auditEvent) {
super(auditEvent);
Assert.notNull(auditEvent, "AuditEvent must not be null");
this.auditEvent = auditEvent;
}
/**
* @return the audit event
*/
public AuditEvent getAuditEvent() {
return this.auditEvent;
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.zero.actuate.audit.listener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.zero.actuate.audit.AuditEvent;
import org.springframework.zero.actuate.audit.AuditEventRepository;
/**
* {@link ApplicationListener} that listens for {@link AuditEvent}s and stores them in a
* {@link AuditEventRepository}.
*
* @author Dave Syer
*/
public class AuditListener implements ApplicationListener<AuditApplicationEvent> {
private static Log logger = LogFactory.getLog(AuditListener.class);
private final AuditEventRepository auditEventRepository;
public AuditListener(AuditEventRepository auditEventRepository) {
this.auditEventRepository = auditEventRepository;
}
@Override
public void onApplicationEvent(AuditApplicationEvent event) {
logger.info(event.getAuditEvent());
this.auditEventRepository.add(event.getAuditEvent());
}
}

@ -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.zero.actuate.autoconfigure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.zero.actuate.audit.AuditEvent;
import org.springframework.zero.actuate.audit.AuditEventRepository;
import org.springframework.zero.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.zero.actuate.audit.listener.AuditListener;
import org.springframework.zero.actuate.security.AuthenticationAuditListener;
import org.springframework.zero.actuate.security.AuthorizationAuditListener;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link AuditEvent}s.
*
* @author Dave Syer
*/
@Configuration
public class AuditAutoConfiguration {
@Autowired(required = false)
private AuditEventRepository auditEventRepository = new InMemoryAuditEventRepository();
@Bean
public AuditListener auditListener() throws Exception {
return new AuditListener(this.auditEventRepository);
}
@Bean
@ConditionalOnClass(name = "org.springframework.security.authentication.event.AbstractAuthenticationEvent")
public AuthenticationAuditListener authenticationAuditListener() throws Exception {
return new AuthenticationAuditListener();
}
@Bean
@ConditionalOnClass(name = "org.springframework.security.access.event.AbstractAuthorizationEvent")
public AuthorizationAuditListener authorizationAuditListener() throws Exception {
return new AuthorizationAuditListener();
}
@ConditionalOnMissingBean(AuditEventRepository.class)
protected static class AuditEventRepositoryConfiguration {
@Bean
public AuditEventRepository auditEventRepository() throws Exception {
return new InMemoryAuditEventRepository();
}
}
}

@ -0,0 +1,203 @@
/*
* 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.zero.actuate.autoconfigure;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.zero.actuate.endpoint.BeansEndpoint;
import org.springframework.zero.actuate.endpoint.DumpEndpoint;
import org.springframework.zero.actuate.endpoint.Endpoint;
import org.springframework.zero.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.zero.actuate.endpoint.HealthEndpoint;
import org.springframework.zero.actuate.endpoint.InfoEndpoint;
import org.springframework.zero.actuate.endpoint.MetricsEndpoint;
import org.springframework.zero.actuate.endpoint.PublicMetrics;
import org.springframework.zero.actuate.endpoint.ShutdownEndpoint;
import org.springframework.zero.actuate.endpoint.TraceEndpoint;
import org.springframework.zero.actuate.endpoint.VanillaPublicMetrics;
import org.springframework.zero.actuate.health.HealthIndicator;
import org.springframework.zero.actuate.health.VanillaHealthIndicator;
import org.springframework.zero.actuate.metrics.InMemoryMetricRepository;
import org.springframework.zero.actuate.metrics.MetricRepository;
import org.springframework.zero.actuate.trace.InMemoryTraceRepository;
import org.springframework.zero.actuate.trace.TraceRepository;
import org.springframework.zero.bind.PropertiesConfigurationFactory;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for common management
* {@link Endpoint}s.
*
* @author Dave Syer
* @author Phillip Webb
*/
@Configuration
public class EndpointAutoConfiguration {
@Autowired(required = false)
private HealthIndicator<? extends Object> healthIndicator = new VanillaHealthIndicator();
@Autowired
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
@ConditionalOnMissingBean
public InfoEndpoint infoEndpoint() throws Exception {
LinkedHashMap<String, Object> info = new LinkedHashMap<String, Object>();
info.putAll(this.properties.infoMap());
GitInfo gitInfo = this.properties.gitInfo();
if (gitInfo.getBranch() != null) {
info.put("git", gitInfo);
}
return new InfoEndpoint(info);
}
@Bean
@ConditionalOnMissingBean
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);
}
@Bean
@ConditionalOnMissingBean
public DumpEndpoint dumpEndpoint() {
return new DumpEndpoint();
}
@Bean
@ConditionalOnMissingBean
public ShutdownEndpoint shutdownEndpoint() {
return new ShutdownEndpoint();
}
@Configuration
protected static class InfoPropertiesConfiguration {
@Autowired
private ConfigurableEnvironment environment = new StandardEnvironment();
@Value("${spring.git.properties:classpath:git.properties}")
private Resource gitProperties;
public GitInfo gitInfo() throws Exception {
PropertiesConfigurationFactory<GitInfo> factory = new PropertiesConfigurationFactory<GitInfo>(
new GitInfo());
factory.setTargetName("git");
Properties properties = new Properties();
if (this.gitProperties.exists()) {
properties = PropertiesLoaderUtils.loadProperties(this.gitProperties);
}
factory.setProperties(properties);
return factory.getObject();
}
public Map<String, Object> infoMap() throws Exception {
PropertiesConfigurationFactory<Map<String, Object>> factory = new PropertiesConfigurationFactory<Map<String, Object>>(
new LinkedHashMap<String, Object>());
factory.setTargetName("info");
factory.setPropertySources(this.environment.getPropertySources());
return factory.getObject();
}
}
public static class GitInfo {
private String branch;
private Commit commit = new Commit();
public String getBranch() {
return this.branch;
}
public void setBranch(String branch) {
this.branch = branch;
}
public Commit getCommit() {
return this.commit;
}
public static class Commit {
private String id;
private String time;
public String getId() {
return this.id == null ? "" : (this.id.length() > 7 ? this.id.substring(
0, 7) : this.id);
}
public void setId(String id) {
this.id = id;
}
public String getTime() {
return this.time;
}
public void setTime(String time) {
this.time = time;
}
}
}
}

@ -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.zero.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.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;
import org.springframework.zero.actuate.endpoint.Endpoint;
import org.springframework.zero.actuate.endpoint.mvc.EndpointHandlerAdapter;
import org.springframework.zero.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.zero.actuate.properties.ManagementServerProperties;
import org.springframework.zero.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.zero.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.zero.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.zero.context.annotation.AutoConfigureAfter;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
import org.springframework.zero.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.zero.properties.ServerProperties;
/**
* {@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.zero.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.zero.actuate.endpoint.mvc.EndpointHandlerAdapter;
import org.springframework.zero.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.zero.actuate.properties.ManagementServerProperties;
import org.springframework.zero.context.annotation.ConditionalOnBean;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.EmbeddedServletContainer;
import org.springframework.zero.context.embedded.EmbeddedServletContainerCustomizer;
/**
* 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);
}
}
}

@ -0,0 +1,56 @@
/*
* 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.zero.actuate.autoconfigure;
import javax.servlet.Servlet;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.zero.actuate.web.BasicErrorController;
import org.springframework.zero.actuate.web.ErrorController;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
import org.springframework.zero.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.zero.context.embedded.ErrorPage;
/**
* {@link EnableAutoConfiguration Auto-configuration} to render errors via a MVC error
* controller.
*
* @author Dave Syer
*/
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
public class ErrorMvcAutoConfiguration implements EmbeddedServletContainerCustomizer {
@Value("${error.path:/error}")
private String errorPath = "/error";
@Bean
@ConditionalOnMissingBean(ErrorController.class)
public BasicErrorController basicErrorController() {
return new BasicErrorController();
}
@Override
public void customize(ConfigurableEmbeddedServletContainerFactory factory) {
factory.addErrorPages(new ErrorPage(this.errorPath));
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.zero.actuate.autoconfigure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.zero.actuate.properties.ManagementServerProperties;
import org.springframework.zero.autoconfigure.web.ServerPropertiesAutoConfiguration;
import org.springframework.zero.context.annotation.AutoConfigureAfter;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
import org.springframework.zero.context.annotation.EnableConfigurationProperties;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the
* {@link ManagementServerPropertiesAutoConfiguration} bean.
*
* @author Dave Syer
*/
@Configuration
@AutoConfigureAfter(ServerPropertiesAutoConfiguration.class)
@EnableConfigurationProperties
public class ManagementServerPropertiesAutoConfiguration {
@Bean(name = "org.springframework.zero.actuate.properties.ManagementServerProperties")
@ConditionalOnMissingBean
public ManagementServerProperties serverProperties() {
return new ManagementServerProperties();
}
}

@ -0,0 +1,131 @@
/*
* 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.zero.actuate.autoconfigure;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.zero.actuate.metrics.CounterService;
import org.springframework.zero.actuate.metrics.GaugeService;
import org.springframework.zero.context.annotation.AutoConfigureAfter;
import org.springframework.zero.context.annotation.ConditionalOnBean;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} that records Servlet interactions
* with a {@link CounterService} and {@link GaugeService}.
*
* @author Dave Syer
* @author Phillip Webb
*/
@Configuration
@ConditionalOnBean({ CounterService.class, GaugeService.class })
@ConditionalOnClass({ Servlet.class })
@AutoConfigureAfter(MetricRepositoryAutoConfiguration.class)
public class MetricFilterAutoConfiguration {
private static final int UNDEFINED_HTTP_STATUS = 999;
@Autowired
private CounterService counterService;
@Autowired
private GaugeService gaugeService;
@Bean
public Filter metricFilter() {
return new MetricsFilter();
}
/**
* Filter that counts requests and measures processing times.
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
private final class MetricsFilter extends GenericFilterBean {
// FIXME parameterize the order (ideally it runs before any other filter)
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if ((request instanceof HttpServletRequest)
&& (response instanceof HttpServletResponse)) {
doFilter((HttpServletRequest) request, (HttpServletResponse) response,
chain);
} else {
chain.doFilter(request, response);
}
}
public void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
UrlPathHelper helper = new UrlPathHelper();
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 int getStatus(HttpServletResponse response) {
try {
return response.getStatus();
} catch (Exception e) {
return UNDEFINED_HTTP_STATUS;
}
}
private String getKey(String string) {
// graphite compatible metric names
String value = string.replace("/", ".");
value = value.replace("..", ".");
if (value.endsWith(".")) {
value = value + "root";
}
if (value.startsWith("_")) {
value = value.substring(1);
}
return value;
}
}
}

@ -0,0 +1,56 @@
/*
* 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.zero.actuate.autoconfigure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.zero.actuate.metrics.CounterService;
import org.springframework.zero.actuate.metrics.DefaultCounterService;
import org.springframework.zero.actuate.metrics.DefaultGaugeService;
import org.springframework.zero.actuate.metrics.GaugeService;
import org.springframework.zero.actuate.metrics.InMemoryMetricRepository;
import org.springframework.zero.actuate.metrics.MetricRepository;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for metrics services.
*
* @author Dave Syer
*/
@Configuration
public class MetricRepositoryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CounterService counterService() {
return new DefaultCounterService(metricRepository());
}
@Bean
@ConditionalOnMissingBean
public GaugeService gaugeService() {
return new DefaultGaugeService(metricRepository());
}
@Bean
@ConditionalOnMissingBean
protected MetricRepository metricRepository() {
return new InMemoryMetricRepository();
}
}

@ -0,0 +1,219 @@
/*
* 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.zero.actuate.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.zero.actuate.endpoint.Endpoint;
import org.springframework.zero.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.zero.actuate.properties.SecurityProperties;
import org.springframework.zero.actuate.web.ErrorController;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
import org.springframework.zero.context.annotation.EnableConfigurationProperties;
/**
* {@link EnableAutoConfiguration Auto-configuration} for security of a web application or
* service. By default everything is secured with HTTP Basic authentication except the
* {@link SecurityProperties#getIgnored() explicitly ignored} paths (defaults to
* <code>&#47;css&#47;**, &#47;js&#47;**, &#47;images&#47;**, &#47;**&#47;favicon.ico</code>
* ). 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
* defaults). The user details for authentication are just placeholders
* <code>(username=user,
* password=password)</code> but can easily be customized by providing a bean definition
* of type {@link AuthenticationManager}. Also provides audit logging of authentication
* events.
*
* <p>
* The framework {@link Endpoint}s (used to expose application information to operations)
* include a {@link Endpoint#isSensitive() sensitive} configuration option which will be
* used as a security hint by the filter created here.
*
* <p>
* Some common simple customizations:
* <ul>
* <li>Switch off security completely and permanently: remove Spring Security from the
* classpath or {@link EnableAutoConfiguration#exclude() exclude} this configuration.</li>
* <li>Switch off security temporarily (e.g. for a dev environment): set
* <code>security.basic.enabled: false</code></li>
* <li>Customize the user details: add an AuthenticationManager bean</li>
* <li>Add form login for user facing resources: add a
* {@link WebSecurityConfigurerAdapter} and use {@link HttpSecurity#formLogin()}</li>
* </ul>
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass({ EnableWebSecurity.class })
@EnableWebSecurity
@EnableConfigurationProperties
public class SecurityAutoConfiguration {
@Bean(name = "org.springframework.zero.actuate.properties.SecurityProperties")
@ConditionalOnMissingBean
public SecurityProperties securityProperties() {
return new SecurityProperties();
}
@Bean
@ConditionalOnMissingBean
public AuthenticationEventPublisher authenticationEventPublisher() {
return new DefaultAuthenticationEventPublisher();
}
@Bean
@ConditionalOnMissingBean({ BoostrapWebSecurityConfigurerAdapter.class })
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
return new BoostrapWebSecurityConfigurerAdapter();
}
// Give user-supplied filters a chance to be last in line
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class BoostrapWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
private static final String[] NO_PATHS = new String[0];
@Autowired
private SecurityProperties security;
@Autowired(required = false)
private EndpointHandlerMapping endpointHandlerMapping;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
@Autowired(required = false)
private ErrorController errorController;
@Override
protected void configure(HttpSecurity http) throws Exception {
if (this.security.isRequireSsl()) {
http.requiresChannel().anyRequest().requiresSecure();
}
if (this.security.getBasic().isEnabled()) {
String[] paths = getSecurePaths();
http.exceptionHandling().authenticationEntryPoint(entryPoint()).and()
.requestMatchers().antMatchers(paths);
http.httpBasic().and().anonymous().disable();
http.authorizeUrls().anyRequest()
.hasRole(this.security.getBasic().getRole());
}
// No cookies for service endpoints by default
http.sessionManagement().sessionCreationPolicy(this.security.getSessions());
}
private String[] getSecurePaths() {
List<String> list = new ArrayList<String>();
for (String path : this.security.getBasic().getPath()) {
path = (path == null ? "" : path.trim());
if (path.equals("/**")) {
return new String[] { path };
}
if (!path.equals("")) {
list.add(path);
}
}
// FIXME makes more sense to secure endpoints with a different role
list.addAll(Arrays.asList(getEndpointPaths(true)));
return list.toArray(new String[list.size()]);
}
private AuthenticationEntryPoint entryPoint() {
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName(this.security.getBasic().getRealm());
return entryPoint;
}
@Override
public void configure(WebSecurity builder) throws Exception {
IgnoredRequestConfigurer ignoring = builder.ignoring();
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
protected AuthenticationManager authenticationManager() throws Exception {
AuthenticationManager manager = super.authenticationManager();
if (manager instanceof ProviderManager) {
((ProviderManager) manager)
.setAuthenticationEventPublisher(this.authenticationEventPublisher);
}
return manager;
}
}
@ConditionalOnMissingBean(AuthenticationManager.class)
@Configuration
public static class AuthenticationManagerConfiguration {
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder().inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and().and()
.build();
}
}
}

@ -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.zero.actuate.autoconfigure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.zero.actuate.trace.InMemoryTraceRepository;
import org.springframework.zero.actuate.trace.TraceRepository;
import org.springframework.zero.context.annotation.ConditionalOnMissingBean;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link TraceRepository tracing}.
*
* @author Dave Syer
*/
@Configuration
public class TraceRepositoryAutoConfiguration {
@ConditionalOnMissingBean
@Bean
public TraceRepository traceRepository() {
return new InMemoryTraceRepository();
}
}

@ -0,0 +1,54 @@
/*
* 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.zero.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.context.annotation.Bean;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.zero.actuate.trace.TraceRepository;
import org.springframework.zero.actuate.trace.WebRequestTraceFilter;
import org.springframework.zero.context.annotation.AutoConfigureAfter;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.annotation.EnableAutoConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link WebRequestTraceFilter
* tracing}.
*
* @author Dave Syer
*/
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@AutoConfigureAfter(TraceRepositoryAutoConfiguration.class)
public class TraceWebFilterAutoConfiguration {
@Autowired
private TraceRepository traceRepository;
@Value("${management.dump_requests:false}")
private boolean dumpRequests;
@Bean
public WebRequestTraceFilter webRequestLoggingFilter(BeanFactory beanFactory) {
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.zero.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;
}
}

@ -0,0 +1,27 @@
/*
* 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.zero.actuate.endpoint;
/**
* Tagging interface used to indicate that {@link Endpoint} that performs some action.
* Allows mappings to refine the types of request supported.
*
* @author Phillip Webb
*/
public interface ActionEndpoint<T> extends Endpoint<T> {
}

@ -0,0 +1,62 @@
/*
* 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.zero.actuate.endpoint;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.LiveBeansView;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* 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 JVM will be shown (and the corresponding MBeans will be registered per the standard
* behavior of LiveBeansView). Otherwise only the current application context.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.beans", ignoreUnknownFields = false)
public class BeansEndpoint extends AbstractEndpoint<String> implements
ApplicationContextAware {
private LiveBeansView liveBeansView = new LiveBeansView();
public BeansEndpoint() {
super("/beans");
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context.getEnvironment()
.getProperty(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME) == null) {
this.liveBeansView.setApplicationContext(context);
}
}
@Override
public MediaType[] getProduces() {
return new MediaType[] { MediaType.APPLICATION_JSON };
}
@Override
public String invoke() {
return this.liveBeansView.getSnapshotAsJson();
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.zero.actuate.endpoint;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.List;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* {@link Endpoint} to expose thread info.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.dump", ignoreUnknownFields = false)
public class DumpEndpoint extends AbstractEndpoint<List<ThreadInfo>> {
/**
* Create a new {@link DumpEndpoint} instance.
*/
public DumpEndpoint() {
super("/dump");
}
@Override
public List<ThreadInfo> invoke() {
return Arrays.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(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.zero.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();
}

@ -0,0 +1,86 @@
/*
* 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.zero.actuate.endpoint;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information.
*
* @author Dave Syer
* @author Phillip Webb
*/
@ConfigurationProperties(name = "endpoints.env", ignoreUnknownFields = false)
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> implements
EnvironmentAware {
private Environment environment;
/**
* Create a new {@link EnvironmentEndpoint} instance.
*/
public EnvironmentEndpoint() {
super("/env");
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
for (PropertySource<?> source : getPropertySources()) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
Map<String, Object> map = new LinkedHashMap<String, Object>();
for (String name : enumerable.getPropertyNames()) {
map.put(name, sanitize(name, enumerable.getProperty(name)));
}
result.put(source.getName(), map);
}
}
return result;
}
private Iterable<PropertySource<?>> getPropertySources() {
if (this.environment != null
&& this.environment instanceof ConfigurableEnvironment) {
return ((ConfigurableEnvironment) this.environment).getPropertySources();
}
return new StandardEnvironment().getPropertySources();
}
private Object sanitize(String name, Object object) {
if (name.toLowerCase().endsWith("password")
|| name.toLowerCase().endsWith("secret")) {
return object == null ? null : "******";
}
return object;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

@ -0,0 +1,49 @@
/*
* 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.zero.actuate.endpoint;
import org.springframework.util.Assert;
import org.springframework.zero.actuate.health.HealthIndicator;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* {@link Endpoint} to expose application health.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.health", ignoreUnknownFields = false)
public class HealthEndpoint<T> extends AbstractEndpoint<T> {
private HealthIndicator<? extends T> indicator;
/**
* Create a new {@link HealthIndicator} instance.
*
* @param indicator the health indicator
*/
public HealthEndpoint(HealthIndicator<? extends T> indicator) {
super("/health", false);
Assert.notNull(indicator, "Indicator must not be null");
this.indicator = indicator;
}
@Override
public T invoke() {
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.zero.actuate.endpoint;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* {@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();
}
}

@ -0,0 +1,56 @@
/*
* 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.zero.actuate.endpoint;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.zero.actuate.metrics.Metric;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* {@link Endpoint} to expose {@link PublicMetrics}.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.metrics", ignoreUnknownFields = false)
public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
private PublicMetrics metrics;
/**
* Create a new {@link MetricsEndpoint} instance.
*
* @param metrics the metrics to expose
*/
public MetricsEndpoint(PublicMetrics metrics) {
super("/metrics");
Assert.notNull(metrics, "Metrics must not be null");
this.metrics = metrics;
}
@Override
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
for (Metric metric : this.metrics.metrics()) {
result.put(metric.getName(), metric.getValue());
}
return result;
}
}

@ -0,0 +1,36 @@
/*
* 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.zero.actuate.endpoint;
import java.util.Collection;
import org.springframework.zero.actuate.metrics.Metric;
/**
* Interface to expose specific {@link Metric}s via a {@link MetricsEndpoint}.
*
* @author Dave Syer
* @see VanillaPublicMetrics
*/
public interface PublicMetrics {
/**
* @return an indication of current state through metrics
*/
Collection<Metric> metrics();
}

@ -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.zero.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.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.zero.actuate.properties.ManagementServerProperties;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* {@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.zero.actuate.endpoint;
import java.util.List;
import org.springframework.util.Assert;
import org.springframework.zero.actuate.trace.Trace;
import org.springframework.zero.actuate.trace.TraceRepository;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* {@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();
}
}

@ -0,0 +1,52 @@
/*
* 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.zero.actuate.endpoint;
import java.util.Collection;
import java.util.LinkedHashSet;
import org.springframework.util.Assert;
import org.springframework.zero.actuate.metrics.Metric;
import org.springframework.zero.actuate.metrics.MetricRepository;
/**
* Default implementation of {@link PublicMetrics} that exposes all metrics from the
* {@link MetricRepository} along with memory information.
*
* @author Dave Syer
*/
public class VanillaPublicMetrics implements PublicMetrics {
private MetricRepository metricRepository;
public VanillaPublicMetrics(MetricRepository metricRepository) {
Assert.notNull(metricRepository, "MetricRepository must not be null");
this.metricRepository = metricRepository;
}
@Override
public Collection<Metric> metrics() {
Collection<Metric> result = new LinkedHashSet<Metric>(
this.metricRepository.findAll());
result.add(new Metric("mem", new Long(Runtime.getRuntime().totalMemory()) / 1024));
result.add(new Metric("mem.free",
new Long(Runtime.getRuntime().freeMemory()) / 1024));
result.add(new Metric("processors", Runtime.getRuntime().availableProcessors()));
return result;
}
}

@ -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.zero.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.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 org.springframework.zero.actuate.endpoint.Endpoint;
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.zero.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.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;
import org.springframework.zero.actuate.endpoint.ActionEndpoint;
import org.springframework.zero.actuate.endpoint.Endpoint;
/**
* {@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);
}
}

@ -0,0 +1,141 @@
/*
* 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.zero.actuate.fixme;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.zero.actuate.properties.ManagementServerProperties;
import org.springframework.zero.actuate.web.BasicErrorController;
import org.springframework.zero.context.annotation.ConditionalOnBean;
import org.springframework.zero.context.annotation.ConditionalOnClass;
import org.springframework.zero.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.zero.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.ErrorPage;
import org.springframework.zero.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
/**
* Configuration for creating a new container (e.g. tomcat) for the management endpoints.
*
* @author Dave Syer
*/
@Configuration
@EnableWebMvc
@Import(ManagementSecurityConfiguration.class)
public class ManagementServerConfiguration {
// FIXME delete when security works
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
ApplicationContext context) {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
public BasicErrorController errorEndpoint() {
return new BasicErrorController();
}
@Bean
@ConditionalOnBean(TomcatEmbeddedServletContainerFactory.class)
public EmbeddedServletContainerFactory tomcatContainer(
HierarchicalBeanFactory beanFactory) {
TomcatEmbeddedServletContainerFactory factory = beanFactory
.getParentBeanFactory().getBean(
TomcatEmbeddedServletContainerFactory.class);
return factory.getChildContextFactory("Management");
}
@Bean
@ConditionalOnBean(JettyEmbeddedServletContainerFactory.class)
public EmbeddedServletContainerFactory jettyContainer() {
return new JettyEmbeddedServletContainerFactory();
}
@Configuration
protected static class ServerCustomizationConfiguration implements
EmbeddedServletContainerCustomizer {
@Value("${endpoints.error.path:/error}")
private String errorPath = "/error";
@Autowired
private ApplicationContext beanFactory;
@Override
public void customize(ConfigurableEmbeddedServletContainerFactory factory) {
ManagementServerProperties configuration = this.beanFactory
.getBean(ManagementServerProperties.class);
factory.setPort(configuration.getPort());
factory.setAddress(configuration.getAddress());
factory.setContextPath(configuration.getContextPath());
factory.addErrorPages(new ErrorPage(this.errorPath));
}
}
}
@Configuration
@ConditionalOnClass(name = {
"org.springframework.security.config.annotation.web.EnableWebSecurity",
"javax.servlet.Filter" })
class ManagementSecurityConfiguration {
@Bean
// TODO: enable and get rid of the empty filter when @ConditionalOnBean works
// @ConditionalOnBean(name = "springSecurityFilterChain")
public Filter springSecurityFilterChain(HierarchicalBeanFactory beanFactory) {
BeanFactory parent = beanFactory.getParentBeanFactory();
if (parent != null && parent.containsBean("springSecurityFilterChain")) {
return parent.getBean("springSecurityFilterChain", Filter.class);
}
return new GenericFilterBean() {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
};
}
}

@ -0,0 +1,32 @@
/*
* 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.zero.actuate.health;
/**
* Strategy interface used to provide an indication of application health.
*
* @author Dave Syer
* @see VanillaHealthIndicator
*/
public interface HealthIndicator<T> {
/**
* @return an indication of health
*/
T health();
}

@ -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.zero.actuate.health;
/**
* Default implementation of {@link HealthIndicator} that simply returns "ok".
*
* @author Dave Syer
*/
public class VanillaHealthIndicator implements HealthIndicator<String> {
@Override
public String health() {
return "ok";
}
}

@ -0,0 +1,44 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.zero.actuate.metrics;
/**
* A service that can be used to increment, decrement and reset a {@link Metric}.
*
* @author Dave Syer
*/
public interface CounterService {
/**
* Increment the specified metric by 1.
* @param metricName the name of the metric
*/
void increment(String metricName);
/**
* Decrement the specified metric by 1.
* @param metricName the name of the metric
*/
void decrement(String metricName);
/**
* Reset the specified metric to 0.
* @param metricName the name of the metric
*/
void reset(String metricName);
}

@ -0,0 +1,62 @@
/*
* 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.zero.actuate.metrics;
import java.util.Date;
/**
* Default implementation of {@link CounterService}.
*
* @author Dave Syer
*/
public class DefaultCounterService implements CounterService {
private MetricRepository repository;
/**
* Create a {@link DefaultCounterService} instance.
* @param repository the underlying repository used to manage metrics
*/
public DefaultCounterService(MetricRepository repository) {
super();
this.repository = repository;
}
@Override
public void increment(String metricName) {
this.repository.increment(wrap(metricName), 1, new Date());
}
@Override
public void decrement(String metricName) {
this.repository.increment(wrap(metricName), -1, new Date());
}
@Override
public void reset(String metricName) {
this.repository.set(wrap(metricName), 0, new Date());
}
private String wrap(String metricName) {
if (metricName.startsWith("counter")) {
return metricName;
} else {
return "counter." + metricName;
}
}
}

@ -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.zero.actuate.metrics;
import java.util.Date;
/**
* Default implementation of {@link GaugeService}.
*
* @author Dave Syer
*/
public class DefaultGaugeService implements GaugeService {
private MetricRepository metricRepository;
/**
* @param counterRepository
*/
public DefaultGaugeService(MetricRepository counterRepository) {
super();
this.metricRepository = counterRepository;
}
@Override
public void set(String metricName, double value) {
this.metricRepository.set(wrap(metricName), value, new Date());
}
private String wrap(String metricName) {
if (metricName.startsWith("gauge")) {
return metricName;
} else {
return "gauge." + metricName;
}
}
}

@ -0,0 +1,33 @@
/*
* 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.zero.actuate.metrics;
/**
* A service that can be used to manage a {@link Metric} as a gauge.
*
* @author Dave Syer
*/
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);
}

@ -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.zero.actuate.metrics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author Dave Syer
*/
public class InMemoryMetricRepository implements MetricRepository {
private ConcurrentMap<String, Measurement> metrics = new ConcurrentHashMap<String, Measurement>();
@Override
public void increment(String metricName, int amount, Date timestamp) {
// FIXME this might not be thread safe
Measurement current = this.metrics.get(metricName);
if (current != null) {
Metric metric = current.getMetric();
this.metrics.replace(metricName, current,
new Measurement(timestamp, metric.increment(amount)));
} else {
this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric(
metricName, amount)));
}
}
@Override
public void set(String metricName, double value, Date timestamp) {
Measurement current = this.metrics.get(metricName);
if (current != null) {
Metric metric = current.getMetric();
this.metrics.replace(metricName, current,
new Measurement(timestamp, metric.set(value)));
} else {
this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric(
metricName, value)));
}
}
@Override
public void delete(String metricName) {
this.metrics.remove(metricName);
}
@Override
public Metric findOne(String metricName) {
if (this.metrics.containsKey(metricName)) {
return this.metrics.get(metricName).getMetric();
}
return new Metric(metricName, 0);
}
@Override
public Collection<Metric> findAll() {
ArrayList<Metric> result = new ArrayList<Metric>();
for (Measurement measurement : this.metrics.values()) {
result.add(measurement.getMetric());
}
return result;
}
}

@ -0,0 +1,79 @@
/*
* 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.zero.actuate.metrics;
import java.util.Date;
import org.springframework.util.ObjectUtils;
/**
* A {@link Metric} at a given point in time.
*
* @author Dave Syer
*/
public final class Measurement {
private Date timestamp;
private Metric metric;
public Measurement(Date timestamp, Metric metric) {
this.timestamp = timestamp;
this.metric = metric;
}
public Date getTimestamp() {
return this.timestamp;
}
public Metric getMetric() {
return this.metric;
}
@Override
public String toString() {
return "Measurement [dateTime=" + this.timestamp + ", metric=" + this.metric
+ "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.timestamp);
result = prime * result + ObjectUtils.nullSafeHashCode(this.metric);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
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);
}
}

@ -0,0 +1,113 @@
/*
* 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.zero.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
* @see MetricRepository
* @see CounterService
*/
public final class Metric {
private final String name;
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) {
super();
Assert.notNull(name, "Name must not be null");
this.name = name;
this.value = value;
}
/**
* Returns the name of the metric.
*/
public String getName() {
return this.name;
}
/**
* Returns the value of the metric.
*/
public double getValue() {
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) {
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) {
return new Metric(this.name, value);
}
@Override
public String toString() {
return "Metric [name=" + this.name + ", value=" + this.value + "]";
}
@Override
public int hashCode() {
int valueHashCode = ObjectUtils.hashCode(this.value);
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
result = prime * result + (valueHashCode ^ (valueHashCode >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
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);
}
}

@ -0,0 +1,45 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.zero.actuate.metrics;
import java.util.Collection;
import java.util.Date;
/**
* A Repository used to manage {@link Metric}s.
*
* @author Dave Syer
*/
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 set(String metricName, double value, Date timestamp);
void delete(String metricName);
Metric findOne(String metricName);
Collection<Metric> findAll();
}

@ -0,0 +1,85 @@
/*
* 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.zero.actuate.properties;
import java.net.InetAddress;
import javax.validation.constraints.NotNull;
import org.springframework.zero.context.annotation.ConfigurationProperties;
import org.springframework.zero.properties.ServerProperties;
/**
* Properties for the management server (e.g. port and path settings).
*
* @author Dave Syer
* @see ServerProperties
*/
@ConfigurationProperties(name = "management", ignoreUnknownFields = false)
public class ManagementServerProperties {
private Integer port;
private InetAddress address;
@NotNull
private String contextPath = "";
private boolean allowShutdown = false;
public boolean isAllowShutdown() {
return this.allowShutdown;
}
public void setAllowShutdown(boolean allowShutdown) {
this.allowShutdown = allowShutdown;
}
/**
* 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;
}
/**
* 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;
}
public InetAddress getAddress() {
return this.address;
}
public void setAddress(InetAddress address) {
this.address = address;
}
public String getContextPath() {
return this.contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
}

@ -0,0 +1,115 @@
/*
* 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.zero.actuate.properties;
import org.springframework.security.config.annotation.web.configurers.SessionCreationPolicy;
import org.springframework.zero.context.annotation.ConfigurationProperties;
/**
* Properties for the security aspects of an application.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "security", ignoreUnknownFields = false)
public class SecurityProperties {
private boolean requireSsl;
private Basic basic = new Basic();
private SessionCreationPolicy sessions = SessionCreationPolicy.stateless;
private String[] ignored = new String[] { "/css/**", "/js/**", "/images/**",
"/**/favicon.ico" };
public SessionCreationPolicy getSessions() {
return this.sessions;
}
public void setSessions(SessionCreationPolicy sessions) {
this.sessions = sessions;
}
public Basic getBasic() {
return this.basic;
}
public void setBasic(Basic basic) {
this.basic = basic;
}
public boolean isRequireSsl() {
return this.requireSsl;
}
public void setRequireSsl(boolean requireSsl) {
this.requireSsl = requireSsl;
}
public void setIgnored(String... ignored) {
this.ignored = ignored;
}
public String[] getIgnored() {
return this.ignored;
}
public static class Basic {
private boolean enabled = true;
private String realm = "Spring";
private String[] path = new String[] { "/**" };
private String role = "USER";
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getRealm() {
return this.realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public String[] getPath() {
return this.path;
}
public void setPath(String... paths) {
this.path = paths;
}
public String getRole() {
return this.role;
}
public void setRole(String role) {
this.role = role;
}
}
}

@ -0,0 +1,91 @@
/*
* 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.zero.actuate.security;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent;
import org.springframework.zero.actuate.audit.AuditEvent;
import org.springframework.zero.actuate.audit.listener.AuditApplicationEvent;
/**
* {@link ApplicationListener} expose Spring Security {@link AbstractAuthenticationEvent
* authentication events} as {@link AuditEvent}s.
*
* @author Dave Syer
*/
public class AuthenticationAuditListener implements
ApplicationListener<AbstractAuthenticationEvent>, ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void onApplicationEvent(AbstractAuthenticationEvent event) {
if (event instanceof AbstractAuthenticationFailureEvent) {
onAuthenticationFailureEvent((AbstractAuthenticationFailureEvent) event);
} else if (event instanceof AuthenticationSwitchUserEvent) {
onAuthenticationSwitchUserEvent((AuthenticationSwitchUserEvent) event);
} else {
onAuthenticationEvent(event);
}
}
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) {
if (this.publisher != null) {
this.publisher.publishEvent(new AuditApplicationEvent(event));
}
}
}

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

Loading…
Cancel
Save