Relocate projects to spring-boot-project
Move projects to better reflect the way that Spring Boot is released. The following projects are under `spring-boot-project`: - `spring-boot` - `spring-boot-autoconfigure` - `spring-boot-tools` - `spring-boot-starters` - `spring-boot-actuator` - `spring-boot-actuator-autoconfigure` - `spring-boot-test` - `spring-boot-test-autoconfigure` - `spring-boot-devtools` - `spring-boot-cli` - `spring-boot-docs` See gh-9316
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.amqp;
|
||||
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple implementation of a {@link HealthIndicator} returning status information for the
|
||||
* RabbitMQ messaging system.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class RabbitHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
public RabbitHealthIndicator(RabbitTemplate rabbitTemplate) {
|
||||
Assert.notNull(rabbitTemplate, "RabbitTemplate must not be null");
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
builder.up().withDetail("version", getVersion());
|
||||
}
|
||||
|
||||
private String getVersion() {
|
||||
return this.rabbitTemplate.execute((channel) -> channel.getConnection()
|
||||
.getServerProperties().get("version").toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support for AMQP and RabbitMQ.
|
||||
*/
|
||||
package org.springframework.boot.actuate.amqp;
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
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 Spring's {@link ApplicationEventPublisher} (usually obtained by
|
||||
* implementing {@link ApplicationEventPublisherAware}) to publish AuditApplicationEvents
|
||||
* (wrappers for AuditEvent).
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @see AuditEventRepository
|
||||
*/
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
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(principal, "Principal 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<>();
|
||||
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.
|
||||
* @return the time stamp
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
public Date getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user principal responsible for the event.
|
||||
* @return the principal
|
||||
*/
|
||||
public String getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of event.
|
||||
* @return the event type
|
||||
*/
|
||||
public String getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event data.
|
||||
* @return 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,66 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Repository for {@link AuditEvent}s.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public interface AuditEventRepository {
|
||||
|
||||
/**
|
||||
* Log an event.
|
||||
* @param event the audit event to log
|
||||
*/
|
||||
void add(AuditEvent event);
|
||||
|
||||
/**
|
||||
* Find audit events since the time provided.
|
||||
* @param after timestamp of earliest result required (or {@code null} if
|
||||
* unrestricted)
|
||||
* @return audit events
|
||||
* @since 1.4.0
|
||||
*/
|
||||
List<AuditEvent> find(Date after);
|
||||
|
||||
/**
|
||||
* Find audit events relating to the specified principal since the time provided.
|
||||
* @param principal the principal name to search for (or {@code null} if unrestricted)
|
||||
* @param after timestamp of earliest result required (or {@code null} if
|
||||
* unrestricted)
|
||||
* @return audit events relating to the principal
|
||||
*/
|
||||
List<AuditEvent> find(String principal, Date after);
|
||||
|
||||
/**
|
||||
* Find audit events of specified type relating to the specified principal since the
|
||||
* time provided.
|
||||
* @param principal the principal name to search for (or {@code null} if unrestricted)
|
||||
* @param after timestamp of earliest result required (or {@code null} if
|
||||
* unrestricted)
|
||||
* @param type the event type to search for (or {@code null} if unrestricted)
|
||||
* @return audit events of specified type relating to the principal
|
||||
* @since 1.4.0
|
||||
*/
|
||||
List<AuditEvent> find(String principal, Date after, String type);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Endpoint} to expose audit events.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Endpoint(id = "auditevents")
|
||||
public class AuditEventsEndpoint {
|
||||
|
||||
private final AuditEventRepository auditEventRepository;
|
||||
|
||||
public AuditEventsEndpoint(AuditEventRepository auditEventRepository) {
|
||||
Assert.notNull(auditEventRepository, "AuditEventRepository must not be null");
|
||||
this.auditEventRepository = auditEventRepository;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public AuditEventsDescriptor eventsWithPrincipalDateAfterAndType(String principal,
|
||||
Date after, String type) {
|
||||
return new AuditEventsDescriptor(
|
||||
this.auditEventRepository.find(principal, after, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an application's {@link AuditEvent audit events}. Primarily
|
||||
* intended for serialization to JSON.
|
||||
*/
|
||||
public static final class AuditEventsDescriptor {
|
||||
|
||||
private final List<AuditEvent> events;
|
||||
|
||||
private AuditEventsDescriptor(List<AuditEvent> events) {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public List<AuditEvent> getEvents() {
|
||||
return this.events;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpointExtension;
|
||||
|
||||
/**
|
||||
* JMX-specific extension of the {@link AuditEventsEndpoint}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@JmxEndpointExtension(endpoint = AuditEventsEndpoint.class)
|
||||
public class AuditEventsJmxEndpointExtension {
|
||||
|
||||
private final AuditEventsEndpoint delegate;
|
||||
|
||||
public AuditEventsJmxEndpointExtension(AuditEventsEndpoint delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public AuditEventsDescriptor eventsWithDateAfter(Date dateAfter) {
|
||||
return this.delegate.eventsWithPrincipalDateAfterAndType(null, dateAfter, null);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public AuditEventsDescriptor eventsWithPrincipalAndDateAfter(String principal,
|
||||
Date dateAfter) {
|
||||
return this.delegate.eventsWithPrincipalDateAfterAndType(principal, dateAfter,
|
||||
null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link WebEndpointExtension} for the {@link AuditEventsEndpoint}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@WebEndpointExtension(endpoint = AuditEventsEndpoint.class)
|
||||
public class AuditEventsWebEndpointExtension {
|
||||
|
||||
private final AuditEventsEndpoint delegate;
|
||||
|
||||
public AuditEventsWebEndpointExtension(AuditEventsEndpoint delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<AuditEventsDescriptor> eventsWithPrincipalDateAfterAndType(
|
||||
@Nullable String principal, Date after, @Nullable String type) {
|
||||
AuditEventsDescriptor auditEvents = this.delegate
|
||||
.eventsWithPrincipalDateAfterAndType(principal, after, type);
|
||||
return new WebEndpointResponse<>(auditEvents);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* In-memory {@link AuditEventRepository} implementation.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public class InMemoryAuditEventRepository implements AuditEventRepository {
|
||||
|
||||
private static final int DEFAULT_CAPACITY = 1000;
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
/**
|
||||
* Circular buffer of the event with tail pointing to the last element.
|
||||
*/
|
||||
private AuditEvent[] events;
|
||||
|
||||
private volatile int tail = -1;
|
||||
|
||||
public InMemoryAuditEventRepository() {
|
||||
this(DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
public InMemoryAuditEventRepository(int capacity) {
|
||||
this.events = new AuditEvent[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the capacity of this event repository.
|
||||
* @param capacity the capacity
|
||||
*/
|
||||
public void setCapacity(int capacity) {
|
||||
synchronized (this.monitor) {
|
||||
this.events = new AuditEvent[capacity];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(AuditEvent event) {
|
||||
Assert.notNull(event, "AuditEvent must not be null");
|
||||
synchronized (this.monitor) {
|
||||
this.tail = (this.tail + 1) % this.events.length;
|
||||
this.events[this.tail] = event;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditEvent> find(Date after) {
|
||||
return find(null, after, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditEvent> find(String principal, Date after) {
|
||||
return find(principal, after, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuditEvent> find(String principal, Date after, String type) {
|
||||
LinkedList<AuditEvent> events = new LinkedList<>();
|
||||
synchronized (this.monitor) {
|
||||
for (int i = 0; i < this.events.length; i++) {
|
||||
AuditEvent event = resolveTailEvent(i);
|
||||
if (event != null && isMatch(principal, after, type, event)) {
|
||||
events.addFirst(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private boolean isMatch(String principal, Date after, String type, AuditEvent event) {
|
||||
boolean match = true;
|
||||
match = match && (principal == null || event.getPrincipal().equals(principal));
|
||||
match = match && (after == null || event.getTimestamp().compareTo(after) >= 0);
|
||||
match = match && (type == null || event.getType().equals(type));
|
||||
return match;
|
||||
}
|
||||
|
||||
private AuditEvent resolveTailEvent(int offset) {
|
||||
int index = ((this.tail + this.events.length - offset) % this.events.length);
|
||||
return this.events[index];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit.listener;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
/**
|
||||
* Abstract {@link ApplicationListener} to handle {@link AuditApplicationEvent}s.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class AbstractAuditListener
|
||||
implements ApplicationListener<AuditApplicationEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AuditApplicationEvent event) {
|
||||
onAuditEvent(event.getAuditEvent());
|
||||
}
|
||||
|
||||
protected abstract void onAuditEvent(AuditEvent event);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit.listener;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.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 final AuditEvent auditEvent;
|
||||
|
||||
/**
|
||||
* Create a new {@link AuditApplicationEvent} that wraps a newly created
|
||||
* {@link AuditEvent}.
|
||||
* @param principal the principal
|
||||
* @param type the event type
|
||||
* @param data the event data
|
||||
* @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}.
|
||||
* @param principal the principal
|
||||
* @param type the event type
|
||||
* @param data the event data
|
||||
* @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}.
|
||||
* @param timestamp the time stamp
|
||||
* @param principal the principal
|
||||
* @param type the event type
|
||||
* @param data the event data
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audit event.
|
||||
* @return the audit event
|
||||
*/
|
||||
public AuditEvent getAuditEvent() {
|
||||
return this.auditEvent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.audit.listener;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.boot.actuate.audit.AuditEventRepository;
|
||||
|
||||
/**
|
||||
* The default {@link AbstractAuditListener} implementation. Listens for
|
||||
* {@link AuditApplicationEvent}s and stores them in a {@link AuditEventRepository}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
public class AuditListener extends AbstractAuditListener {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AuditListener.class);
|
||||
|
||||
private final AuditEventRepository auditEventRepository;
|
||||
|
||||
public AuditListener(AuditEventRepository auditEventRepository) {
|
||||
this.auditEventRepository = auditEventRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuditEvent(AuditEvent event) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(event);
|
||||
}
|
||||
this.auditEventRepository.add(event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator auditing listeners.
|
||||
*/
|
||||
package org.springframework.boot.actuate.audit.listener;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core actuator auditing classes.
|
||||
*/
|
||||
package org.springframework.boot.actuate.audit;
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.beans;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Endpoint} to expose details of an application's beans, grouped by application
|
||||
* context.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Endpoint(id = "beans")
|
||||
public class BeansEndpoint {
|
||||
|
||||
private final ConfigurableApplicationContext context;
|
||||
|
||||
/**
|
||||
* Creates a new {@code BeansEndpoint} that will describe the beans in the given
|
||||
* {@code context} and all of its ancestors.
|
||||
*
|
||||
* @param context the application context
|
||||
* @see ConfigurableApplicationContext#getParent()
|
||||
*/
|
||||
public BeansEndpoint(ConfigurableApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public ApplicationContextDescriptor beans() {
|
||||
return ApplicationContextDescriptor.describing(this.context);
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an application context, primarily intended for serialization to
|
||||
* JSON.
|
||||
*/
|
||||
public static final class ApplicationContextDescriptor {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final Map<String, BeanDescriptor> beans;
|
||||
|
||||
private final ApplicationContextDescriptor parent;
|
||||
|
||||
private ApplicationContextDescriptor(String id, Map<String, BeanDescriptor> beans,
|
||||
ApplicationContextDescriptor parent) {
|
||||
this.id = id;
|
||||
this.beans = beans;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public ApplicationContextDescriptor getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public Map<String, BeanDescriptor> getBeans() {
|
||||
return this.beans;
|
||||
}
|
||||
|
||||
private static ApplicationContextDescriptor describing(
|
||||
ConfigurableApplicationContext context) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
return new ApplicationContextDescriptor(context.getId(),
|
||||
describeBeans(context.getBeanFactory()),
|
||||
describing(getConfigurableParent(context)));
|
||||
}
|
||||
|
||||
private static Map<String, BeanDescriptor> describeBeans(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
Map<String, BeanDescriptor> beans = new HashMap<>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
|
||||
if (isBeanEligible(beanName, definition, beanFactory)) {
|
||||
beans.put(beanName, describeBean(beanName, definition, beanFactory));
|
||||
}
|
||||
}
|
||||
return beans;
|
||||
}
|
||||
|
||||
private static BeanDescriptor describeBean(String name, BeanDefinition definition,
|
||||
ConfigurableListableBeanFactory factory) {
|
||||
return new BeanDescriptor(factory.getAliases(name), definition.getScope(),
|
||||
factory.getType(name), definition.getResourceDescription(),
|
||||
factory.getDependenciesForBean(name));
|
||||
}
|
||||
|
||||
private static boolean isBeanEligible(String beanName, BeanDefinition bd,
|
||||
ConfigurableBeanFactory bf) {
|
||||
return (bd.getRole() != BeanDefinition.ROLE_INFRASTRUCTURE
|
||||
&& (!bd.isLazyInit() || bf.containsSingleton(beanName)));
|
||||
}
|
||||
|
||||
private static ConfigurableApplicationContext getConfigurableParent(
|
||||
ConfigurableApplicationContext context) {
|
||||
ApplicationContext parent = context.getParent();
|
||||
if (parent instanceof ConfigurableApplicationContext) {
|
||||
return (ConfigurableApplicationContext) parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a bean in an application context, primarily intended for
|
||||
* serialization to JSON.
|
||||
*/
|
||||
public static final class BeanDescriptor {
|
||||
|
||||
private final String[] aliases;
|
||||
|
||||
private final String scope;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String resource;
|
||||
|
||||
private final String[] dependencies;
|
||||
|
||||
private BeanDescriptor(String[] aliases, String scope, Class<?> type,
|
||||
String resource, String[] dependencies) {
|
||||
this.aliases = aliases;
|
||||
this.scope = StringUtils.hasText(scope) ? scope
|
||||
: BeanDefinition.SCOPE_SINGLETON;
|
||||
this.type = type;
|
||||
this.resource = resource;
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
public String[] getAliases() {
|
||||
return this.aliases;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return this.scope;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public String getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
public String[] getDependencies() {
|
||||
return this.dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support relating to Spring Beans.
|
||||
*/
|
||||
package org.springframework.boot.actuate.beans;
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.cassandra;
|
||||
|
||||
import com.datastax.driver.core.ResultSet;
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
import com.datastax.driver.core.querybuilder.Select;
|
||||
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple implementation of a {@link HealthIndicator} returning status information for
|
||||
* Cassandra data stores.
|
||||
*
|
||||
* @author Julien Dubois
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CassandraHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private CassandraOperations cassandraOperations;
|
||||
|
||||
/**
|
||||
* Create a new {@link CassandraHealthIndicator} instance.
|
||||
* @param cassandraOperations the Cassandra operations
|
||||
*/
|
||||
public CassandraHealthIndicator(CassandraOperations cassandraOperations) {
|
||||
Assert.notNull(cassandraOperations, "CassandraOperations must not be null");
|
||||
this.cassandraOperations = cassandraOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
try {
|
||||
Select select = QueryBuilder.select("release_version").from("system",
|
||||
"local");
|
||||
ResultSet results = this.cassandraOperations.getCqlOperations()
|
||||
.queryForResultSet(select);
|
||||
if (results.isExhausted()) {
|
||||
builder.up();
|
||||
return;
|
||||
}
|
||||
String version = results.one().getString(0);
|
||||
builder.up().withDetail("version", version);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
builder.down(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support for Cassandra.
|
||||
*/
|
||||
package org.springframework.boot.actuate.cassandra;
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.context;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
/**
|
||||
* {@link Endpoint} to shutdown the {@link ApplicationContext}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Christian Dupuis
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Endpoint(id = "shutdown", defaultEnablement = DefaultEnablement.DISABLED)
|
||||
public class ShutdownEndpoint implements ApplicationContextAware {
|
||||
|
||||
private static final Map<String, String> NO_CONTEXT_MESSAGE = Collections
|
||||
.unmodifiableMap(Collections.<String, String>singletonMap("message",
|
||||
"No context to shutdown."));
|
||||
|
||||
private static final Map<String, String> SHUTDOWN_MESSAGE = Collections
|
||||
.unmodifiableMap(Collections.<String, String>singletonMap("message",
|
||||
"Shutting down, bye..."));
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@WriteOperation
|
||||
public Map<String, String> shutdown() {
|
||||
if (this.context == null) {
|
||||
return NO_CONTEXT_MESSAGE;
|
||||
}
|
||||
try {
|
||||
return SHUTDOWN_MESSAGE;
|
||||
}
|
||||
finally {
|
||||
Thread thread = new Thread(this::performShutdown);
|
||||
thread.setContextClassLoader(getClass().getClassLoader());
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void performShutdown() {
|
||||
try {
|
||||
Thread.sleep(500L);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
this.context.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
if (context instanceof ConfigurableApplicationContext) {
|
||||
this.context = (ConfigurableApplicationContext) context;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support relating to Spring Context.
|
||||
*/
|
||||
package org.springframework.boot.actuate.context;
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.introspect.Annotated;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
|
||||
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
|
||||
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
|
||||
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
|
||||
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
||||
import com.fasterxml.jackson.databind.ser.PropertyWriter;
|
||||
import com.fasterxml.jackson.databind.ser.SerializerFactory;
|
||||
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
|
||||
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetaData;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Endpoint} to expose application properties from {@link ConfigurationProperties}
|
||||
* annotated beans.
|
||||
*
|
||||
* <p>
|
||||
* To protect sensitive information from being exposed, certain property values are masked
|
||||
* if their names end with a set of configurable values (default "password" and "secret").
|
||||
* Configure property names by using {@code endpoints.configprops.keys_to_sanitize} in
|
||||
* your Spring Boot application configuration.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Endpoint(id = "configprops")
|
||||
public class ConfigurationPropertiesReportEndpoint implements ApplicationContextAware {
|
||||
|
||||
private static final String CGLIB_FILTER_ID = "cglibFilter";
|
||||
|
||||
private final Sanitizer sanitizer = new Sanitizer();
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public ConfigurationPropertiesDescriptor configurationProperties() {
|
||||
return extract(this.context);
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesDescriptor extract(ApplicationContext context) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
configureObjectMapper(mapper);
|
||||
return describeConfigurationProperties(context, mapper);
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesDescriptor describeConfigurationProperties(
|
||||
ApplicationContext context, ObjectMapper mapper) {
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
ConfigurationBeanFactoryMetaData beanFactoryMetaData = getBeanFactoryMetaData(
|
||||
context);
|
||||
Map<String, Object> beans = getConfigurationPropertiesBeans(context,
|
||||
beanFactoryMetaData);
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> beanDescriptors = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : beans.entrySet()) {
|
||||
String beanName = entry.getKey();
|
||||
Object bean = entry.getValue();
|
||||
String prefix = extractPrefix(context, beanFactoryMetaData, beanName, bean);
|
||||
beanDescriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(
|
||||
prefix, sanitize(prefix, safeSerialize(mapper, bean, prefix))));
|
||||
}
|
||||
return new ConfigurationPropertiesDescriptor(beanDescriptors,
|
||||
describeConfigurationProperties(context.getParent(), mapper));
|
||||
}
|
||||
|
||||
private ConfigurationBeanFactoryMetaData getBeanFactoryMetaData(
|
||||
ApplicationContext context) {
|
||||
Map<String, ConfigurationBeanFactoryMetaData> beans = context
|
||||
.getBeansOfType(ConfigurationBeanFactoryMetaData.class);
|
||||
if (beans.size() == 1) {
|
||||
return beans.values().iterator().next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, Object> getConfigurationPropertiesBeans(
|
||||
ApplicationContext context,
|
||||
ConfigurationBeanFactoryMetaData beanFactoryMetaData) {
|
||||
Map<String, Object> beans = new HashMap<>();
|
||||
beans.putAll(context.getBeansWithAnnotation(ConfigurationProperties.class));
|
||||
if (beanFactoryMetaData != null) {
|
||||
beans.putAll(beanFactoryMetaData
|
||||
.getBeansWithFactoryAnnotation(ConfigurationProperties.class));
|
||||
}
|
||||
return beans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cautiously serialize the bean to a map (returning a map with an error message
|
||||
* instead of throwing an exception if there is a problem).
|
||||
* @param mapper the object mapper
|
||||
* @param bean the source bean
|
||||
* @param prefix the prefix
|
||||
* @return the serialized instance
|
||||
*/
|
||||
private Map<String, Object> safeSerialize(ObjectMapper mapper, Object bean,
|
||||
String prefix) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> result = new HashMap<>(
|
||||
mapper.convertValue(bean, Map.class));
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return new HashMap<>(Collections.<String, Object>singletonMap("error",
|
||||
"Cannot serialize '" + prefix + "'"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Jackson's {@link ObjectMapper} to be used to serialize the
|
||||
* {@link ConfigurationProperties} objects into a {@link Map} structure.
|
||||
* @param mapper the object mapper
|
||||
*/
|
||||
protected void configureObjectMapper(ObjectMapper mapper) {
|
||||
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
mapper.setSerializationInclusion(Include.NON_NULL);
|
||||
applyCglibFilters(mapper);
|
||||
applySerializationModifier(mapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure only bindable and non-cyclic bean properties are reported.
|
||||
* @param mapper the object mapper
|
||||
*/
|
||||
private void applySerializationModifier(ObjectMapper mapper) {
|
||||
SerializerFactory factory = BeanSerializerFactory.instance
|
||||
.withSerializerModifier(new GenericSerializerModifier());
|
||||
mapper.setSerializerFactory(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure PropertyFilter to make sure Jackson doesn't process CGLIB generated bean
|
||||
* properties.
|
||||
* @param mapper the object mapper
|
||||
*/
|
||||
private void applyCglibFilters(ObjectMapper mapper) {
|
||||
mapper.setAnnotationIntrospector(new CglibAnnotationIntrospector());
|
||||
mapper.setFilterProvider(new SimpleFilterProvider().addFilter(CGLIB_FILTER_ID,
|
||||
new CglibBeanPropertyFilter()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract configuration prefix from {@link ConfigurationProperties} annotation.
|
||||
* @param context the application context
|
||||
* @param beanFactoryMetaData the bean factory meta-data
|
||||
* @param beanName the bean name
|
||||
* @param bean the bean
|
||||
* @return the prefix
|
||||
*/
|
||||
private String extractPrefix(ApplicationContext context,
|
||||
ConfigurationBeanFactoryMetaData beanFactoryMetaData, String beanName,
|
||||
Object bean) {
|
||||
ConfigurationProperties annotation = context.findAnnotationOnBean(beanName,
|
||||
ConfigurationProperties.class);
|
||||
if (beanFactoryMetaData != null) {
|
||||
ConfigurationProperties override = beanFactoryMetaData
|
||||
.findFactoryAnnotation(beanName, ConfigurationProperties.class);
|
||||
if (override != null) {
|
||||
// The @Bean-level @ConfigurationProperties overrides the one at type
|
||||
// level when binding. Arguably we should render them both, but this one
|
||||
// might be the most relevant for a starting point.
|
||||
annotation = override;
|
||||
}
|
||||
}
|
||||
return annotation.prefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize all unwanted configuration properties to avoid leaking of sensitive
|
||||
* information.
|
||||
* @param prefix the property prefix
|
||||
* @param map the source map
|
||||
* @return the sanitized map
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> sanitize(String prefix, Map<String, Object> map) {
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String qualifiedKey = (prefix.isEmpty() ? prefix : prefix + ".") + key;
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof Map) {
|
||||
map.put(key, sanitize(qualifiedKey, (Map<String, Object>) value));
|
||||
}
|
||||
else if (value instanceof List) {
|
||||
map.put(key, sanitize(qualifiedKey, (List<Object>) value));
|
||||
}
|
||||
else {
|
||||
value = this.sanitizer.sanitize(key, value);
|
||||
value = this.sanitizer.sanitize(qualifiedKey, value);
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> sanitize(String prefix, List<Object> list) {
|
||||
List<Object> sanitized = new ArrayList<>();
|
||||
for (Object item : list) {
|
||||
if (item instanceof Map) {
|
||||
sanitized.add(sanitize(prefix, (Map<String, Object>) item));
|
||||
}
|
||||
else if (item instanceof List) {
|
||||
sanitized.add(sanitize(prefix, (List<Object>) item));
|
||||
}
|
||||
else {
|
||||
sanitized.add(this.sanitizer.sanitize(prefix, item));
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension to {@link JacksonAnnotationIntrospector} to suppress CGLIB generated bean
|
||||
* properties.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class CglibAnnotationIntrospector
|
||||
extends JacksonAnnotationIntrospector {
|
||||
|
||||
@Override
|
||||
public Object findFilterId(Annotated a) {
|
||||
Object id = super.findFilterId(a);
|
||||
if (id == null) {
|
||||
id = CGLIB_FILTER_ID;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SimpleBeanPropertyFilter} to filter out all bean properties whose names
|
||||
* start with '$$'.
|
||||
*/
|
||||
private static class CglibBeanPropertyFilter extends SimpleBeanPropertyFilter {
|
||||
|
||||
@Override
|
||||
protected boolean include(BeanPropertyWriter writer) {
|
||||
return include(writer.getFullName().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean include(PropertyWriter writer) {
|
||||
return include(writer.getFullName().getSimpleName());
|
||||
}
|
||||
|
||||
private boolean include(String name) {
|
||||
return !name.startsWith("$$");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BeanSerializerModifier} to return only relevant configuration properties.
|
||||
*/
|
||||
protected static class GenericSerializerModifier extends BeanSerializerModifier {
|
||||
|
||||
@Override
|
||||
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
|
||||
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
|
||||
List<BeanPropertyWriter> result = new ArrayList<>();
|
||||
for (BeanPropertyWriter writer : beanProperties) {
|
||||
boolean readable = isReadable(beanDesc, writer);
|
||||
if (readable) {
|
||||
result.add(writer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isReadable(BeanDescription beanDesc, BeanPropertyWriter writer) {
|
||||
Class<?> parentType = beanDesc.getType().getRawClass();
|
||||
Class<?> type = writer.getType().getRawClass();
|
||||
AnnotatedMethod setter = findSetter(beanDesc, writer);
|
||||
// If there's a setter, we assume it's OK to report on the value,
|
||||
// similarly, if there's no setter but the package names match, we assume
|
||||
// that its a nested class used solely for binding to config props, so it
|
||||
// should be kosher. Lists and Maps are also auto-detected by default since
|
||||
// that's what the metadata generator does. This filter is not used if there
|
||||
// is JSON metadata for the property, so it's mainly for user-defined beans.
|
||||
return (setter != null)
|
||||
|| ClassUtils.getPackageName(parentType)
|
||||
.equals(ClassUtils.getPackageName(type))
|
||||
|| Map.class.isAssignableFrom(type)
|
||||
|| Collection.class.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
private AnnotatedMethod findSetter(BeanDescription beanDesc,
|
||||
BeanPropertyWriter writer) {
|
||||
String name = "set" + StringUtils.capitalize(writer.getName());
|
||||
Class<?> type = writer.getType().getRawClass();
|
||||
AnnotatedMethod setter = beanDesc.findMethod(name, new Class<?>[] { type });
|
||||
// The enabled property of endpoints returns a boolean primitive but is set
|
||||
// using a Boolean class
|
||||
if (setter == null && type.equals(Boolean.TYPE)) {
|
||||
setter = beanDesc.findMethod(name, new Class<?>[] { Boolean.class });
|
||||
}
|
||||
return setter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an application context's {@link ConfigurationProperties} beans.
|
||||
* Primarily intended for serialization to JSON.
|
||||
*/
|
||||
public static final class ConfigurationPropertiesDescriptor {
|
||||
|
||||
private final Map<String, ConfigurationPropertiesBeanDescriptor> beans;
|
||||
|
||||
private final ConfigurationPropertiesDescriptor parent;
|
||||
|
||||
private ConfigurationPropertiesDescriptor(
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> beans,
|
||||
ConfigurationPropertiesDescriptor parent) {
|
||||
this.beans = beans;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public Map<String, ConfigurationPropertiesBeanDescriptor> getBeans() {
|
||||
return this.beans;
|
||||
}
|
||||
|
||||
public ConfigurationPropertiesDescriptor getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a {@link ConfigurationProperties} bean. Primarily intended for
|
||||
* serialization to JSON.
|
||||
*/
|
||||
public static final class ConfigurationPropertiesBeanDescriptor {
|
||||
|
||||
private final String prefix;
|
||||
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
private ConfigurationPropertiesBeanDescriptor(String prefix,
|
||||
Map<String, Object> properties) {
|
||||
this.prefix = prefix;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
public Map<String, Object> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support relating to external configuration properties.
|
||||
*/
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.couchbase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.util.features.Version;
|
||||
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link HealthIndicator} for Couchbase.
|
||||
*
|
||||
* @author Eddú Meléndez
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CouchbaseHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private CouchbaseOperations couchbaseOperations;
|
||||
|
||||
public CouchbaseHealthIndicator(CouchbaseOperations couchbaseOperations) {
|
||||
Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null");
|
||||
this.couchbaseOperations = couchbaseOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
List<Version> versions = this.couchbaseOperations.getCouchbaseClusterInfo()
|
||||
.getAllVersions();
|
||||
builder.up().withDetail("versions",
|
||||
StringUtils.collectionToCommaDelimitedString(versions));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support for Couchbase.
|
||||
*/
|
||||
package org.springframework.boot.actuate.couchbase;
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.elasticsearch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
|
||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.client.Requests;
|
||||
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link HealthIndicator} for an Elasticsearch cluster.
|
||||
*
|
||||
* @author Binwei Yang
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ElasticsearchHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private static final String[] ALL_INDICES = { "_all" };
|
||||
|
||||
private final Client client;
|
||||
|
||||
private final String[] indices;
|
||||
|
||||
private final long responseTimeout;
|
||||
|
||||
/**
|
||||
* Create a new {@link ElasticsearchHealthIndicator} instance.
|
||||
* @param client the Elasticsearch client
|
||||
* @param responseTimeout the request timeout in milliseconds
|
||||
* @param indices the indices to check
|
||||
*/
|
||||
public ElasticsearchHealthIndicator(Client client, long responseTimeout,
|
||||
List<String> indices) {
|
||||
this(client, responseTimeout, (indices == null ? (String[]) null
|
||||
: indices.toArray(new String[indices.size()])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ElasticsearchHealthIndicator} instance.
|
||||
* @param client the Elasticsearch client
|
||||
* @param responseTimeout the request timeout in milliseconds
|
||||
* @param indices the indices to check
|
||||
*/
|
||||
public ElasticsearchHealthIndicator(Client client, long responseTimeout,
|
||||
String... indices) {
|
||||
this.client = client;
|
||||
this.responseTimeout = responseTimeout;
|
||||
this.indices = indices;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
ClusterHealthRequest request = Requests.clusterHealthRequest(
|
||||
ObjectUtils.isEmpty(this.indices) ? ALL_INDICES : this.indices);
|
||||
ClusterHealthResponse response = this.client.admin().cluster().health(request)
|
||||
.actionGet(this.responseTimeout);
|
||||
switch (response.getStatus()) {
|
||||
case GREEN:
|
||||
case YELLOW:
|
||||
builder.up();
|
||||
break;
|
||||
case RED:
|
||||
default:
|
||||
builder.down();
|
||||
break;
|
||||
}
|
||||
builder.withDetail("clusterName", response.getClusterName());
|
||||
builder.withDetail("numberOfNodes", response.getNumberOfNodes());
|
||||
builder.withDetail("numberOfDataNodes", response.getNumberOfDataNodes());
|
||||
builder.withDetail("activePrimaryShards", response.getActivePrimaryShards());
|
||||
builder.withDetail("activeShards", response.getActiveShards());
|
||||
builder.withDetail("relocatingShards", response.getRelocatingShards());
|
||||
builder.withDetail("initializingShards", response.getInitializingShards());
|
||||
builder.withDetail("unassignedShards", response.getUnassignedShards());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.elasticsearch;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import io.searchbox.client.JestClient;
|
||||
import io.searchbox.client.JestResult;
|
||||
import io.searchbox.indices.Stats;
|
||||
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
|
||||
/**
|
||||
* {@link HealthIndicator} for Elasticsearch using a {@link JestClient}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ElasticsearchJestHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private final JestClient jestClient;
|
||||
|
||||
private final JsonParser jsonParser = new JsonParser();
|
||||
|
||||
public ElasticsearchJestHealthIndicator(JestClient jestClient) {
|
||||
this.jestClient = jestClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
JestResult aliases = this.jestClient.execute(new Stats.Builder().build());
|
||||
JsonElement root = this.jsonParser.parse(aliases.getJsonString());
|
||||
JsonObject shards = root.getAsJsonObject().get("_shards").getAsJsonObject();
|
||||
int failedShards = shards.get("failed").getAsInt();
|
||||
if (failedShards != 0) {
|
||||
builder.outOfService();
|
||||
}
|
||||
else {
|
||||
builder.up();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support for Elasticsearch.
|
||||
*/
|
||||
package org.springframework.boot.actuate.elasticsearch;
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* Enumerate the enablement options for an endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum DefaultEnablement {
|
||||
|
||||
/**
|
||||
* The endpoint is enabled unless explicitly disabled.
|
||||
*/
|
||||
ENABLED,
|
||||
|
||||
/**
|
||||
* The endpoint is disabled unless explicitly enabled.
|
||||
*/
|
||||
DISABLED,
|
||||
|
||||
/**
|
||||
* The endpoint's enablement defaults to the "default" settings.
|
||||
*/
|
||||
NEUTRAL
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Discovers endpoints and provides an {@link EndpointInfo} for each of them.
|
||||
*
|
||||
* @param <T> the type of the operation
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EndpointDiscoverer<T extends Operation> {
|
||||
|
||||
/**
|
||||
* Perform endpoint discovery.
|
||||
* @return the discovered endpoints
|
||||
*/
|
||||
Collection<EndpointInfo<T>> discoverEndpoints();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* An enumeration of the available exposure technologies for an endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum EndpointExposure {
|
||||
|
||||
/**
|
||||
* Expose the endpoint as a JMX MBean.
|
||||
*/
|
||||
JMX(true),
|
||||
|
||||
/**
|
||||
* Expose the endpoint as a Web endpoint.
|
||||
*/
|
||||
WEB(false);
|
||||
|
||||
private final boolean enabledByDefault;
|
||||
|
||||
EndpointExposure(boolean enabledByDefault) {
|
||||
this.enabledByDefault = enabledByDefault;
|
||||
}
|
||||
|
||||
public boolean isEnabledByDefault() {
|
||||
return this.enabledByDefault;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Information describing an endpoint.
|
||||
*
|
||||
* @param <T> the type of the endpoint's operations
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class EndpointInfo<T extends Operation> {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final DefaultEnablement defaultEnablement;
|
||||
|
||||
private final Collection<T> operations;
|
||||
|
||||
/**
|
||||
* Creates a new {@code EndpointInfo} describing an endpoint with the given {@code id}
|
||||
* and {@code operations}.
|
||||
* @param id the id of the endpoint
|
||||
* @param defaultEnablement the {@link DefaultEnablement} of the endpoint
|
||||
* @param operations the operations of the endpoint
|
||||
*/
|
||||
public EndpointInfo(String id, DefaultEnablement defaultEnablement,
|
||||
Collection<T> operations) {
|
||||
this.id = id;
|
||||
this.defaultEnablement = defaultEnablement;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the endpoint.
|
||||
* @return the id
|
||||
*/
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link DefaultEnablement} of the endpoint.
|
||||
* @return the default enablement
|
||||
*/
|
||||
public DefaultEnablement getDefaultEnablement() {
|
||||
return this.defaultEnablement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operations of the endpoint.
|
||||
* @return the operations
|
||||
*/
|
||||
public Collection<T> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
/**
|
||||
* Utility class that can be used to filter source data using a name regular expression.
|
||||
* Detects if the name is classic "single value" key or a regular expression. Subclasses
|
||||
* must provide implementations of {@link #getValue(Object, String)} and
|
||||
* {@link #getNames(Object, NameCallback)}.
|
||||
*
|
||||
* @param <T> the source data type
|
||||
* @author Phillip Webb
|
||||
* @author Sergei Egorov
|
||||
* @author Andy Wilkinson
|
||||
* @author Dylian Bego
|
||||
*/
|
||||
abstract class NamePatternFilter<T> {
|
||||
|
||||
private static final String[] REGEX_PARTS = { "*", "$", "^", "+", "[" };
|
||||
|
||||
private final T source;
|
||||
|
||||
NamePatternFilter(T source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public Map<String, Object> getResults(String name) {
|
||||
Pattern pattern = compilePatternIfNecessary(name);
|
||||
if (pattern == null) {
|
||||
Object value = getValue(this.source, name);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put(name, value);
|
||||
return result;
|
||||
}
|
||||
ResultCollectingNameCallback resultCollector = new ResultCollectingNameCallback(
|
||||
pattern);
|
||||
getNames(this.source, resultCollector);
|
||||
return resultCollector.getResults();
|
||||
|
||||
}
|
||||
|
||||
private Pattern compilePatternIfNecessary(String name) {
|
||||
for (String part : REGEX_PARTS) {
|
||||
if (name.contains(part)) {
|
||||
try {
|
||||
return Pattern.compile(name);
|
||||
}
|
||||
catch (PatternSyntaxException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract void getNames(T source, NameCallback callback);
|
||||
|
||||
protected abstract Object getValue(T source, String name);
|
||||
|
||||
protected abstract Object getOptionalValue(T source, String name);
|
||||
|
||||
/**
|
||||
* Callback used to add a name.
|
||||
*/
|
||||
interface NameCallback {
|
||||
|
||||
void addName(String name);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link NameCallback} implementation to collect results.
|
||||
*/
|
||||
private class ResultCollectingNameCallback implements NameCallback {
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
private final Map<String, Object> results = new LinkedHashMap<>();
|
||||
|
||||
ResultCollectingNameCallback(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addName(String name) {
|
||||
if (this.pattern.matcher(name).matches()) {
|
||||
Object value = getOptionalValue(NamePatternFilter.this.source, name);
|
||||
if (value != null) {
|
||||
this.results.put(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getResults() {
|
||||
return this.results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* An operation on an endpoint.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class Operation {
|
||||
|
||||
private final OperationType type;
|
||||
|
||||
private final OperationInvoker invoker;
|
||||
|
||||
private final boolean blocking;
|
||||
|
||||
/**
|
||||
* Creates a new {@code EndpointOperation} for an operation of the given {@code type}.
|
||||
* The operation can be performed using the given {@code operationInvoker}.
|
||||
* @param type the type of the operation
|
||||
* @param operationInvoker used to perform the operation
|
||||
* @param blocking whether or not this is a blocking operation
|
||||
*/
|
||||
public Operation(OperationType type, OperationInvoker operationInvoker,
|
||||
boolean blocking) {
|
||||
this.type = type;
|
||||
this.invoker = operationInvoker;
|
||||
this.blocking = blocking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link OperationType type} of the operation.
|
||||
* @return the type
|
||||
*/
|
||||
public OperationType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code OperationInvoker} that can be used to invoke this endpoint
|
||||
* operation.
|
||||
* @return the operation invoker
|
||||
*/
|
||||
public OperationInvoker getInvoker() {
|
||||
return this.invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this is a blocking operation.
|
||||
*
|
||||
* @return {@code true} if it is a blocking operation, otherwise {@code false}.
|
||||
*/
|
||||
public boolean isBlocking() {
|
||||
return this.blocking;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An {@code OperationInvoker} is used to invoke an operation on an endpoint.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OperationInvoker {
|
||||
|
||||
/**
|
||||
* Invoke the underlying operation using the given {@code arguments}.
|
||||
* @param arguments the arguments to pass to the operation
|
||||
* @return the result of the operation, may be {@code null}
|
||||
*/
|
||||
Object invoke(Map<String, Object> arguments);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* An {@code OperationParameterMapper} is used to map parameters to the required type when
|
||||
* invoking an endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface OperationParameterMapper {
|
||||
|
||||
/**
|
||||
* Map the specified {@code input} parameter to the given {@code parameterType}.
|
||||
* @param input a parameter value
|
||||
* @param parameterType the required type of the parameter
|
||||
* @return a value suitable for that parameter
|
||||
* @param <T> the actual type of the parameter
|
||||
* @throws ParameterMappingException when a mapping failure occurs
|
||||
*/
|
||||
<T> T mapParameter(Object input, Class<T> parameterType);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* An enumeration of the different types of operation supported by an endpoint.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum OperationType {
|
||||
|
||||
/**
|
||||
* A read operation.
|
||||
*/
|
||||
READ,
|
||||
|
||||
/**
|
||||
* A write operation.
|
||||
*/
|
||||
WRITE,
|
||||
|
||||
/**
|
||||
* A delete operation.
|
||||
*/
|
||||
DELETE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
/**
|
||||
* A {@code ParameterMappingException} is thrown when a failure occurs during
|
||||
* {@link OperationParameterMapper#mapParameter(Object, Class) operation parameter
|
||||
* mapping}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ParameterMappingException extends RuntimeException {
|
||||
|
||||
private final Object input;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
/**
|
||||
* Creates a new {@code ParameterMappingException} for a failure that occurred when
|
||||
* trying to map the given {@code input} to the given {@code type}.
|
||||
*
|
||||
* @param input the input that was being mapped
|
||||
* @param type the type that was being mapped to
|
||||
* @param cause the cause of the mapping failure
|
||||
*/
|
||||
public ParameterMappingException(Object input, Class<?> type, Throwable cause) {
|
||||
super("Failed to map " + input + " of type " + input.getClass() + " to type "
|
||||
+ type, cause);
|
||||
this.input = input;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input that was to be mapped.
|
||||
* @return the input
|
||||
*/
|
||||
public Object getInput() {
|
||||
return this.input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type to be mapped to.
|
||||
* @return the type
|
||||
*/
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link RuntimeException} thrown when an endpoint invocation does not contain required
|
||||
* parameters.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ParametersMissingException extends RuntimeException {
|
||||
|
||||
private final Set<String> parameters;
|
||||
|
||||
public ParametersMissingException(Set<String> parameters) {
|
||||
super("Failed to invoke operation because the following required "
|
||||
+ "parameters were missing: "
|
||||
+ StringUtils.collectionToCommaDelimitedString(parameters));
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters that were missing.
|
||||
* @return the parameters
|
||||
*/
|
||||
public Set<String> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* An {@code OperationInvoker} that invokes an operation using reflection.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ReflectiveOperationInvoker implements OperationInvoker {
|
||||
|
||||
private final OperationParameterMapper parameterMapper;
|
||||
|
||||
private final Object target;
|
||||
|
||||
private final Method method;
|
||||
|
||||
/**
|
||||
* Creates a new {code ReflectiveOperationInvoker} that will invoke the given
|
||||
* {@code method} on the given {@code target}. The given {@code parameterMapper} will
|
||||
* be used to map parameters to the required types.
|
||||
* @param parameterMapper the parameter mapper
|
||||
* @param target the target of the reflective call
|
||||
* @param method the method to call
|
||||
*/
|
||||
public ReflectiveOperationInvoker(OperationParameterMapper parameterMapper,
|
||||
Object target, Method method) {
|
||||
this.parameterMapper = parameterMapper;
|
||||
this.target = target;
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Map<String, Object> arguments) {
|
||||
validateRequiredParameters(arguments);
|
||||
return ReflectionUtils.invokeMethod(this.method, this.target,
|
||||
resolveArguments(arguments));
|
||||
}
|
||||
|
||||
private void validateRequiredParameters(Map<String, Object> arguments) {
|
||||
Set<String> missingParameters = Stream.of(this.method.getParameters())
|
||||
.filter((p) -> isMissing(p, arguments)).map(Parameter::getName)
|
||||
.collect(Collectors.toSet());
|
||||
if (!missingParameters.isEmpty()) {
|
||||
throw new ParametersMissingException(missingParameters);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMissing(Parameter parameter, Map<String, Object> arguments) {
|
||||
Object resolved = arguments.get(parameter.getName());
|
||||
return (resolved == null && !isExplicitNullable(parameter));
|
||||
}
|
||||
|
||||
private boolean isExplicitNullable(Parameter parameter) {
|
||||
return (parameter.getAnnotationsByType(Nullable.class).length != 0);
|
||||
}
|
||||
|
||||
private Object[] resolveArguments(Map<String, Object> arguments) {
|
||||
return Stream.of(this.method.getParameters())
|
||||
.map((parameter) -> resolveArgument(parameter, arguments))
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(),
|
||||
(list) -> list.toArray(new Object[list.size()])));
|
||||
}
|
||||
|
||||
private Object resolveArgument(Parameter parameter, Map<String, Object> arguments) {
|
||||
Object resolved = arguments.get(parameter.getName());
|
||||
return this.parameterMapper.mapParameter(resolved, parameter.getType());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Strategy that be be used by endpoint implementations to sanitize potentially sensitive
|
||||
* keys.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @author Toshiaki Maki
|
||||
* @author Phillip Webb
|
||||
* @author Nicolas Lejeune
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class Sanitizer {
|
||||
|
||||
private static final String[] REGEX_PARTS = { "*", "$", "^", "+" };
|
||||
|
||||
private Pattern[] keysToSanitize;
|
||||
|
||||
public Sanitizer() {
|
||||
this("password", "secret", "key", "token", ".*credentials.*", "vcap_services");
|
||||
}
|
||||
|
||||
public Sanitizer(String... keysToSanitize) {
|
||||
setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keys that should be sanitized. Keys can be simple strings that the property ends
|
||||
* with or regular expressions.
|
||||
* @param keysToSanitize the keys to sanitize
|
||||
*/
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
Assert.notNull(keysToSanitize, "KeysToSanitize must not be null");
|
||||
this.keysToSanitize = new Pattern[keysToSanitize.length];
|
||||
for (int i = 0; i < keysToSanitize.length; i++) {
|
||||
this.keysToSanitize[i] = getPattern(keysToSanitize[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private Pattern getPattern(String value) {
|
||||
if (isRegex(value)) {
|
||||
return Pattern.compile(value, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private boolean isRegex(String value) {
|
||||
for (String part : REGEX_PARTS) {
|
||||
if (value.contains(part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the given value if necessary.
|
||||
* @param key the key to sanitize
|
||||
* @param value the value
|
||||
* @return the potentially sanitized value
|
||||
*/
|
||||
public Object sanitize(String key, Object value) {
|
||||
for (Pattern pattern : this.keysToSanitize) {
|
||||
if (pattern.matcher(key).matches()) {
|
||||
return (value == null ? null : "******");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointExposure;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A base {@link EndpointDiscoverer} implementation that discovers {@link Endpoint} beans
|
||||
* in an application context.
|
||||
*
|
||||
* @param <T> the type of the operation
|
||||
* @param <K> the type of the operation key
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class AnnotationEndpointDiscoverer<T extends Operation, K>
|
||||
implements EndpointDiscoverer<T> {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final EndpointOperationFactory<T> operationFactory;
|
||||
|
||||
private final Function<T, K> operationKeyFactory;
|
||||
|
||||
private final CachingConfigurationFactory cachingConfigurationFactory;
|
||||
|
||||
protected AnnotationEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
EndpointOperationFactory<T> operationFactory,
|
||||
Function<T, K> operationKeyFactory,
|
||||
CachingConfigurationFactory cachingConfigurationFactory) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.operationFactory = operationFactory;
|
||||
this.operationKeyFactory = operationKeyFactory;
|
||||
this.cachingConfigurationFactory = cachingConfigurationFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform endpoint discovery, including discovery and merging of extensions.
|
||||
* @param extensionType the annotation type of the extension
|
||||
* @param exposure the {@link EndpointExposure} that should be considered
|
||||
* @return the list of {@link EndpointInfo EndpointInfos} that describes the
|
||||
* discovered endpoints matching the specified {@link EndpointExposure}
|
||||
*/
|
||||
protected Collection<EndpointInfoDescriptor<T, K>> discoverEndpoints(
|
||||
Class<? extends Annotation> extensionType, EndpointExposure exposure) {
|
||||
Map<Class<?>, EndpointInfo<T>> endpoints = discoverEndpoints(exposure);
|
||||
Map<Class<?>, EndpointExtensionInfo<T>> extensions = discoverExtensions(endpoints,
|
||||
extensionType, exposure);
|
||||
Collection<EndpointInfoDescriptor<T, K>> result = new ArrayList<>();
|
||||
endpoints.forEach((endpointClass, endpointInfo) -> {
|
||||
EndpointExtensionInfo<T> extension = extensions.remove(endpointClass);
|
||||
result.add(createDescriptor(endpointClass, endpointInfo, extension));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Class<?>, EndpointInfo<T>> discoverEndpoints(EndpointExposure exposure) {
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, Endpoint.class);
|
||||
Map<Class<?>, EndpointInfo<T>> endpoints = new LinkedHashMap<>();
|
||||
Map<String, EndpointInfo<T>> endpointsById = new LinkedHashMap<>();
|
||||
for (String beanName : beanNames) {
|
||||
Class<?> beanType = this.applicationContext.getType(beanName);
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils
|
||||
.findMergedAnnotationAttributes(beanType, Endpoint.class, true, true);
|
||||
if (isExposedOver(attributes, exposure)) {
|
||||
EndpointInfo<T> info = createEndpointInfo(beanName, beanType, attributes);
|
||||
EndpointInfo<T> previous = endpointsById.putIfAbsent(info.getId(), info);
|
||||
Assert.state(previous == null, () -> "Found two endpoints with the id '"
|
||||
+ info.getId() + "': " + info + " and " + previous);
|
||||
endpoints.put(beanType, info);
|
||||
}
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private EndpointInfo<T> createEndpointInfo(String beanName, Class<?> beanType,
|
||||
AnnotationAttributes attributes) {
|
||||
String id = attributes.getString("id");
|
||||
DefaultEnablement defaultEnablement = (DefaultEnablement) attributes
|
||||
.get("defaultEnablement");
|
||||
Map<Method, T> operations = discoverOperations(id, beanName, beanType);
|
||||
return new EndpointInfo<>(id, defaultEnablement, operations.values());
|
||||
}
|
||||
|
||||
private Map<Class<?>, EndpointExtensionInfo<T>> discoverExtensions(
|
||||
Map<Class<?>, EndpointInfo<T>> endpoints,
|
||||
Class<? extends Annotation> extensionType, EndpointExposure exposure) {
|
||||
if (extensionType == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
|
||||
this.applicationContext, extensionType);
|
||||
Map<Class<?>, EndpointExtensionInfo<T>> extensions = new HashMap<>();
|
||||
for (String beanName : beanNames) {
|
||||
Class<?> beanType = this.applicationContext.getType(beanName);
|
||||
Class<?> endpointType = getEndpointType(extensionType, beanType);
|
||||
AnnotationAttributes endpointAttributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(endpointType, Endpoint.class);
|
||||
Assert.state(isExposedOver(endpointAttributes, exposure),
|
||||
"Invalid extension " + beanType.getName() + "': endpoint '"
|
||||
+ endpointType.getName()
|
||||
+ "' does not support such extension");
|
||||
EndpointInfo<T> info = getEndpointInfo(endpoints, beanType, endpointType);
|
||||
Map<Method, T> operations = discoverOperations(info.getId(), beanName,
|
||||
beanType);
|
||||
EndpointExtensionInfo<T> extension = new EndpointExtensionInfo<>(beanType,
|
||||
operations.values());
|
||||
EndpointExtensionInfo<T> previous = extensions.putIfAbsent(endpointType,
|
||||
extension);
|
||||
Assert.state(previous == null,
|
||||
() -> "Found two extensions for the same endpoint '"
|
||||
+ endpointType.getName() + "': "
|
||||
+ extension.getExtensionType().getName() + " and "
|
||||
+ previous.getExtensionType().getName());
|
||||
}
|
||||
return extensions;
|
||||
|
||||
}
|
||||
|
||||
private EndpointInfo<T> getEndpointInfo(Map<Class<?>, EndpointInfo<T>> endpoints,
|
||||
Class<?> beanType, Class<?> endpointClass) {
|
||||
EndpointInfo<T> endpoint = endpoints.get(endpointClass);
|
||||
Assert.state(endpoint != null, "Invalid extension '" + beanType.getName()
|
||||
+ "': no endpoint found with type '" + endpointClass.getName() + "'");
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private Class<?> getEndpointType(Class<? extends Annotation> extensionType,
|
||||
Class<?> beanType) {
|
||||
AnnotationAttributes attributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(beanType, extensionType);
|
||||
return (Class<?>) attributes.get("endpoint");
|
||||
}
|
||||
|
||||
private EndpointInfoDescriptor<T, K> createDescriptor(Class<?> type,
|
||||
EndpointInfo<T> info, EndpointExtensionInfo<T> extension) {
|
||||
Map<OperationKey<K>, List<T>> operations = indexOperations(info.getId(), type,
|
||||
info.getOperations());
|
||||
if (extension != null) {
|
||||
operations.putAll(indexOperations(info.getId(), extension.getExtensionType(),
|
||||
extension.getOperations()));
|
||||
return new EndpointInfoDescriptor<>(mergeEndpoint(info, extension),
|
||||
operations);
|
||||
}
|
||||
return new EndpointInfoDescriptor<>(info, operations);
|
||||
}
|
||||
|
||||
private EndpointInfo<T> mergeEndpoint(EndpointInfo<T> endpoint,
|
||||
EndpointExtensionInfo<T> extension) {
|
||||
Map<K, T> operations = new HashMap<>();
|
||||
Consumer<T> consumer = (operation) -> operations
|
||||
.put(this.operationKeyFactory.apply(operation), operation);
|
||||
endpoint.getOperations().forEach(consumer);
|
||||
extension.getOperations().forEach(consumer);
|
||||
return new EndpointInfo<>(endpoint.getId(), endpoint.getDefaultEnablement(),
|
||||
operations.values());
|
||||
}
|
||||
|
||||
private Map<OperationKey<K>, List<T>> indexOperations(String endpointId,
|
||||
Class<?> target, Collection<T> operations) {
|
||||
LinkedMultiValueMap<OperationKey<K>, T> result = new LinkedMultiValueMap<>();
|
||||
operations.forEach((operation) -> {
|
||||
K key = this.operationKeyFactory.apply(operation);
|
||||
result.add(new OperationKey<>(endpointId, target, key), operation);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isExposedOver(AnnotationAttributes attributes,
|
||||
EndpointExposure exposure) {
|
||||
if (exposure == null) {
|
||||
return true;
|
||||
}
|
||||
EndpointExposure[] supported = (EndpointExposure[]) attributes.get("exposure");
|
||||
return ObjectUtils.isEmpty(supported)
|
||||
|| ObjectUtils.containsElement(supported, exposure);
|
||||
}
|
||||
|
||||
private Map<Method, T> discoverOperations(String id, String name, Class<?> type) {
|
||||
return MethodIntrospector.selectMethods(type,
|
||||
(MethodIntrospector.MetadataLookup<T>) (
|
||||
method) -> createOperationIfPossible(id, name, method));
|
||||
}
|
||||
|
||||
private T createOperationIfPossible(String endpointId, String beanName,
|
||||
Method method) {
|
||||
T operation = createReadOperationIfPossible(endpointId, beanName, method);
|
||||
if (operation != null) {
|
||||
return operation;
|
||||
}
|
||||
operation = createWriteOperationIfPossible(endpointId, beanName, method);
|
||||
if (operation != null) {
|
||||
return operation;
|
||||
}
|
||||
return createDeleteOperationIfPossible(endpointId, beanName, method);
|
||||
}
|
||||
|
||||
private T createReadOperationIfPossible(String endpointId, String beanName,
|
||||
Method method) {
|
||||
return createOperationIfPossible(endpointId, beanName, method,
|
||||
ReadOperation.class, OperationType.READ);
|
||||
}
|
||||
|
||||
private T createWriteOperationIfPossible(String endpointId, String beanName,
|
||||
Method method) {
|
||||
return createOperationIfPossible(endpointId, beanName, method,
|
||||
WriteOperation.class, OperationType.WRITE);
|
||||
}
|
||||
|
||||
private T createDeleteOperationIfPossible(String endpointId, String beanName,
|
||||
Method method) {
|
||||
return createOperationIfPossible(endpointId, beanName, method,
|
||||
DeleteOperation.class, OperationType.DELETE);
|
||||
}
|
||||
|
||||
private T createOperationIfPossible(String endpointId, String beanName, Method method,
|
||||
Class<? extends Annotation> operationAnnotation,
|
||||
OperationType operationType) {
|
||||
AnnotationAttributes operationAttributes = AnnotatedElementUtils
|
||||
.getMergedAnnotationAttributes(method, operationAnnotation);
|
||||
if (operationAttributes == null) {
|
||||
return null;
|
||||
}
|
||||
CachingConfiguration cachingConfiguration = this.cachingConfigurationFactory
|
||||
.getCachingConfiguration(endpointId);
|
||||
return this.operationFactory.createOperation(endpointId, operationAttributes,
|
||||
this.applicationContext.getBean(beanName), method, operationType,
|
||||
determineTimeToLive(cachingConfiguration, operationType, method));
|
||||
}
|
||||
|
||||
private long determineTimeToLive(CachingConfiguration cachingConfiguration,
|
||||
OperationType operationType, Method method) {
|
||||
if (cachingConfiguration != null && cachingConfiguration.getTimeToLive() > 0
|
||||
&& operationType == OperationType.READ
|
||||
&& method.getParameters().length == 0) {
|
||||
return cachingConfiguration.getTimeToLive();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code EndpointOperationFactory} creates an {@link Operation} for an operation
|
||||
* on an endpoint.
|
||||
*
|
||||
* @param <T> the {@link Operation} type
|
||||
*/
|
||||
@FunctionalInterface
|
||||
protected interface EndpointOperationFactory<T extends Operation> {
|
||||
|
||||
/**
|
||||
* Creates an {@code EndpointOperation} for an operation on an endpoint.
|
||||
* @param endpointId the id of the endpoint
|
||||
* @param operationAttributes the annotation attributes for the operation
|
||||
* @param target the target that implements the operation
|
||||
* @param operationMethod the method on the bean that implements the operation
|
||||
* @param operationType the type of the operation
|
||||
* @param timeToLive the caching period in milliseconds
|
||||
* @return the operation info that describes the operation
|
||||
*/
|
||||
T createOperation(String endpointId, AnnotationAttributes operationAttributes,
|
||||
Object target, Method operationMethod, OperationType operationType,
|
||||
long timeToLive);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a tech-specific extension of an endpoint.
|
||||
* @param <T> the type of the operation
|
||||
*/
|
||||
private static final class EndpointExtensionInfo<T extends Operation> {
|
||||
|
||||
private final Class<?> extensionType;
|
||||
|
||||
private final Collection<T> operations;
|
||||
|
||||
private EndpointExtensionInfo(Class<?> extensionType, Collection<T> operations) {
|
||||
this.extensionType = extensionType;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
private Class<?> getExtensionType() {
|
||||
return this.extensionType;
|
||||
}
|
||||
|
||||
private Collection<T> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes an {@link EndpointInfo endpoint} and whether or not it is valid.
|
||||
*
|
||||
* @param <T> the type of the operation
|
||||
* @param <K> the type of the operation key
|
||||
*/
|
||||
protected static class EndpointInfoDescriptor<T extends Operation, K> {
|
||||
|
||||
private final EndpointInfo<T> endpointInfo;
|
||||
|
||||
private final Map<OperationKey<K>, List<T>> operations;
|
||||
|
||||
protected EndpointInfoDescriptor(EndpointInfo<T> endpointInfo,
|
||||
Map<OperationKey<K>, List<T>> operations) {
|
||||
this.endpointInfo = endpointInfo;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
public EndpointInfo<T> getEndpointInfo() {
|
||||
return this.endpointInfo;
|
||||
}
|
||||
|
||||
public Map<OperationKey<K>, List<T>> findDuplicateOperations() {
|
||||
Map<OperationKey<K>, List<T>> duplicateOperations = new HashMap<>();
|
||||
this.operations.forEach((k, list) -> {
|
||||
if (list.size() > 1) {
|
||||
duplicateOperations.put(k, list);
|
||||
}
|
||||
});
|
||||
return duplicateOperations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the key of an operation in the context of an operation's implementation.
|
||||
*
|
||||
* @param <K> the type of the key
|
||||
*/
|
||||
protected static final class OperationKey<K> {
|
||||
|
||||
private final String endpointId;
|
||||
|
||||
private final Class<?> endpointType;
|
||||
|
||||
private final K key;
|
||||
|
||||
public OperationKey(String endpointId, Class<?> endpointType, K key) {
|
||||
this.endpointId = endpointId;
|
||||
this.endpointType = endpointType;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OperationKey<?> other = (OperationKey<?>) o;
|
||||
Boolean result = true;
|
||||
result = result && this.endpointId.equals(other.endpointId);
|
||||
result = result && this.endpointType.equals(other.endpointType);
|
||||
result = result && this.key.equals(other.key);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.endpointId.hashCode();
|
||||
result = 31 * result + this.endpointType.hashCode();
|
||||
result = 31 * result + this.key.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Identifies a method on an {@link Endpoint} as being a delete operation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DeleteOperation {
|
||||
|
||||
/**
|
||||
* The media type of the result of the operation.
|
||||
*
|
||||
* @return the media type
|
||||
*/
|
||||
String[] produces() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.DefaultEnablement;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointExposure;
|
||||
|
||||
/**
|
||||
* Identifies a type as being an endpoint.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
* @see AnnotationEndpointDiscoverer
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Endpoint {
|
||||
|
||||
/**
|
||||
* The id of the endpoint.
|
||||
* @return the id
|
||||
*/
|
||||
String id();
|
||||
|
||||
/**
|
||||
* Defines the {@link EndpointExposure technologies} over which the endpoint should be
|
||||
* exposed. By default, all technologies are supported.
|
||||
* @return the supported endpoint exposure technologies
|
||||
*/
|
||||
EndpointExposure[] exposure() default {};
|
||||
|
||||
/**
|
||||
* Defines the {@link DefaultEnablement} of the endpoint. By default, the endpoint's
|
||||
* enablement defaults to the "default" settings.
|
||||
* @return the default enablement
|
||||
*/
|
||||
DefaultEnablement defaultEnablement() default DefaultEnablement.NEUTRAL;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Identifies a method on an {@link Endpoint} as being a read operation.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ReadOperation {
|
||||
|
||||
/**
|
||||
* The media type of the result of the operation.
|
||||
*
|
||||
* @return the media type
|
||||
*/
|
||||
String[] produces() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A {@code Selector} can be used on a parameter of an {@link Endpoint} method to indicate
|
||||
* that the parameter is used to select a subset of the endpoint's data.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Selector {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Identifies a method on an {@link Endpoint} as being a write operation.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface WriteOperation {
|
||||
|
||||
/**
|
||||
* The media type of the result of the operation.
|
||||
*
|
||||
* @return the media type
|
||||
*/
|
||||
String[] produces() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Annotation support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.cache;
|
||||
|
||||
/**
|
||||
* The caching configuration of an endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CachingConfiguration {
|
||||
|
||||
private final long timeToLive;
|
||||
|
||||
/**
|
||||
* Create a new instance with the given {@code timeToLive}.
|
||||
* @param timeToLive the time to live of an operation result in milliseconds
|
||||
*/
|
||||
public CachingConfiguration(long timeToLive) {
|
||||
this.timeToLive = timeToLive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time to live of a cached operation result.
|
||||
* @return the time to live of an operation result
|
||||
*/
|
||||
public long getTimeToLive() {
|
||||
return this.timeToLive;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.cache;
|
||||
|
||||
/**
|
||||
* Factory used to return a {@link CachingConfiguration} for an endpoint ID.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CachingConfigurationFactory {
|
||||
|
||||
/**
|
||||
* Return the {@link CachingConfiguration} for the given endpoint ID.
|
||||
* @param endpointId the endpoint ID
|
||||
* @return the caching configuration
|
||||
*/
|
||||
CachingConfiguration getCachingConfiguration(String endpointId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.cache;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OperationInvoker} that caches the response of an operation with a
|
||||
* configurable time to live.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CachingOperationInvoker implements OperationInvoker {
|
||||
|
||||
private final OperationInvoker target;
|
||||
|
||||
private final long timeToLive;
|
||||
|
||||
private volatile CachedResponse cachedResponse;
|
||||
|
||||
/**
|
||||
* Create a new instance with the target {@link OperationInvoker} to use to compute
|
||||
* the response and the time to live for the cache.
|
||||
* @param target the {@link OperationInvoker} this instance wraps
|
||||
* @param timeToLive the maximum time in milliseconds that a response can be cached
|
||||
*/
|
||||
public CachingOperationInvoker(OperationInvoker target, long timeToLive) {
|
||||
Assert.state(timeToLive > 0, "TimeToLive must be strictly positive");
|
||||
this.target = target;
|
||||
this.timeToLive = timeToLive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum time in milliseconds that a response can be cached.
|
||||
* @return the time to live of a response
|
||||
*/
|
||||
public long getTimeToLive() {
|
||||
return this.timeToLive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Map<String, Object> arguments) {
|
||||
long accessTime = System.currentTimeMillis();
|
||||
CachedResponse cached = this.cachedResponse;
|
||||
if (cached == null || cached.isStale(accessTime, this.timeToLive)) {
|
||||
Object response = this.target.invoke(arguments);
|
||||
this.cachedResponse = new CachedResponse(response, accessTime);
|
||||
return response;
|
||||
}
|
||||
return cached.getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* A cached response that encapsulates the response itself and the time at which it
|
||||
* was created.
|
||||
*/
|
||||
static class CachedResponse {
|
||||
|
||||
private final Object response;
|
||||
|
||||
private final long creationTime;
|
||||
|
||||
CachedResponse(Object response, long creationTime) {
|
||||
this.response = response;
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
public boolean isStale(long accessTime, long timeToLive) {
|
||||
return (accessTime - this.creationTime) >= timeToLive;
|
||||
}
|
||||
|
||||
public Object getResponse() {
|
||||
return this.response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Caching support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.cache;
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.convert;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.context.properties.bind.convert.BinderConversionService;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
|
||||
/**
|
||||
* {@link OperationParameterMapper} that uses a {@link ConversionService} to map parameter
|
||||
* values if necessary.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ConversionServiceOperationParameterMapper
|
||||
implements OperationParameterMapper {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public ConversionServiceOperationParameterMapper() {
|
||||
this(createDefaultConversionService());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the {@link ConversionService} to use.
|
||||
* @param conversionService the conversion service
|
||||
*/
|
||||
public ConversionServiceOperationParameterMapper(
|
||||
ConversionService conversionService) {
|
||||
this.conversionService = new BinderConversionService(conversionService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T mapParameter(Object input, Class<T> parameterType) {
|
||||
try {
|
||||
return this.conversionService.convert(input, parameterType);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ParameterMappingException(input, parameterType, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static ConversionService createDefaultConversionService() {
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
IsoOffsetDateTimeConverter.registerConverter(conversionService);
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.convert;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link String} to {@link Date} {@link Converter} that uses
|
||||
* {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO offset} parsing.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class IsoOffsetDateTimeConverter implements Converter<String, Date> {
|
||||
|
||||
@Override
|
||||
public Date convert(String source) {
|
||||
if (StringUtils.hasLength(source)) {
|
||||
OffsetDateTime offsetDateTime = OffsetDateTime.parse(source,
|
||||
DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
||||
return new Date(TimeUnit.SECONDS.toMillis(offsetDateTime.toEpochSecond()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void registerConverter(ConverterRegistry registry) {
|
||||
registry.addConverter(new IsoOffsetDateTimeConverter());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converter support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.convert;
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.http;
|
||||
|
||||
/**
|
||||
* Media types that can be consumed and produced by Actuator endpoints.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class ActuatorMediaType {
|
||||
|
||||
/**
|
||||
* Constant for the Actuator V1 media type.
|
||||
*/
|
||||
public static final String V1_JSON = "application/vnd.spring-boot.actuator.v1+json";
|
||||
|
||||
/**
|
||||
* Constant for the Actuator V2 media type.
|
||||
*/
|
||||
public static final String V2_JSON = "application/vnd.spring-boot.actuator.v2+json";
|
||||
|
||||
private ActuatorMediaType() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator endpoint HTTP concerns (not tied to any specific implementation).
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.http;
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.management.Attribute;
|
||||
import javax.management.AttributeList;
|
||||
import javax.management.AttributeNotFoundException;
|
||||
import javax.management.DynamicMBean;
|
||||
import javax.management.InvalidAttributeValueException;
|
||||
import javax.management.MBeanException;
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.ReflectionException;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* A {@link DynamicMBean} that invokes operations on an {@link EndpointInfo endpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
* @see EndpointMBeanInfoAssembler
|
||||
*/
|
||||
public class EndpointMBean implements DynamicMBean {
|
||||
|
||||
private static final boolean REACTOR_PRESENT = ClassUtils.isPresent(
|
||||
"reactor.core.publisher.Mono", EndpointMBean.class.getClassLoader());
|
||||
|
||||
private final Function<Object, Object> operationResponseConverter;
|
||||
|
||||
private final EndpointMBeanInfo endpointInfo;
|
||||
|
||||
EndpointMBean(Function<Object, Object> operationResponseConverter,
|
||||
EndpointMBeanInfo endpointInfo) {
|
||||
this.operationResponseConverter = operationResponseConverter;
|
||||
this.endpointInfo = endpointInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the related endpoint.
|
||||
* @return the endpoint id
|
||||
*/
|
||||
public String getEndpointId() {
|
||||
return this.endpointInfo.getEndpointId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MBeanInfo getMBeanInfo() {
|
||||
return this.endpointInfo.getMbeanInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(String actionName, Object[] params, String[] signature)
|
||||
throws MBeanException, ReflectionException {
|
||||
JmxEndpointOperation operation = this.endpointInfo.getOperations()
|
||||
.get(actionName);
|
||||
if (operation != null) {
|
||||
Map<String, Object> arguments = getArguments(params,
|
||||
operation.getParameters());
|
||||
try {
|
||||
Object result = operation.getInvoker().invoke(arguments);
|
||||
if (REACTOR_PRESENT) {
|
||||
result = ReactiveHandler.handle(result);
|
||||
}
|
||||
return this.operationResponseConverter.apply(result);
|
||||
}
|
||||
catch (ParametersMissingException | ParameterMappingException ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
throw new ReflectionException(new IllegalArgumentException(
|
||||
String.format("Endpoint with id '%s' has no operation named %s",
|
||||
this.endpointInfo.getEndpointId(), actionName)));
|
||||
}
|
||||
|
||||
private Map<String, Object> getArguments(Object[] params,
|
||||
List<JmxEndpointOperationParameterInfo> parameters) {
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
arguments.put(parameters.get(i).getName(), params[i]);
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String attribute)
|
||||
throws AttributeNotFoundException, MBeanException, ReflectionException {
|
||||
throw new AttributeNotFoundException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
|
||||
InvalidAttributeValueException, MBeanException, ReflectionException {
|
||||
throw new AttributeNotFoundException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeList getAttributes(String[] attributes) {
|
||||
return new AttributeList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeList setAttributes(AttributeList attributes) {
|
||||
return new AttributeList();
|
||||
}
|
||||
|
||||
private static class ReactiveHandler {
|
||||
|
||||
public static Object handle(Object result) {
|
||||
if (result instanceof Mono) {
|
||||
return ((Mono<?>) result).block();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.management.MBeanInfo;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
|
||||
/**
|
||||
* The {@link MBeanInfo} for a particular {@link EndpointInfo endpoint}. Maps operation
|
||||
* names to an {@link Operation}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class EndpointMBeanInfo {
|
||||
|
||||
private final String endpointId;
|
||||
|
||||
private final MBeanInfo mBeanInfo;
|
||||
|
||||
private final Map<String, JmxEndpointOperation> operations;
|
||||
|
||||
public EndpointMBeanInfo(String endpointId, MBeanInfo mBeanInfo,
|
||||
Map<String, JmxEndpointOperation> operations) {
|
||||
this.endpointId = endpointId;
|
||||
this.mBeanInfo = mBeanInfo;
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
public String getEndpointId() {
|
||||
return this.endpointId;
|
||||
}
|
||||
|
||||
public MBeanInfo getMbeanInfo() {
|
||||
return this.mBeanInfo;
|
||||
}
|
||||
|
||||
public Map<String, JmxEndpointOperation> getOperations() {
|
||||
return this.operations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanOperationInfo;
|
||||
import javax.management.MBeanParameterInfo;
|
||||
import javax.management.modelmbean.ModelMBeanAttributeInfo;
|
||||
import javax.management.modelmbean.ModelMBeanConstructorInfo;
|
||||
import javax.management.modelmbean.ModelMBeanInfoSupport;
|
||||
import javax.management.modelmbean.ModelMBeanNotificationInfo;
|
||||
import javax.management.modelmbean.ModelMBeanOperationInfo;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
|
||||
/**
|
||||
* Gathers the management operations of a particular {@link EndpointInfo endpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class EndpointMBeanInfoAssembler {
|
||||
|
||||
private final JmxOperationResponseMapper responseMapper;
|
||||
|
||||
EndpointMBeanInfoAssembler(JmxOperationResponseMapper responseMapper) {
|
||||
this.responseMapper = responseMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link EndpointMBeanInfo} for the specified {@link EndpointInfo
|
||||
* endpoint}.
|
||||
* @param endpointInfo the endpoint to handle
|
||||
* @return the mbean info for the endpoint
|
||||
*/
|
||||
EndpointMBeanInfo createEndpointMBeanInfo(
|
||||
EndpointInfo<JmxEndpointOperation> endpointInfo) {
|
||||
Map<String, OperationInfos> operationsMapping = getOperationInfo(endpointInfo);
|
||||
ModelMBeanOperationInfo[] operationsMBeanInfo = operationsMapping.values()
|
||||
.stream().map((t) -> t.mBeanOperationInfo).collect(Collectors.toList())
|
||||
.toArray(new ModelMBeanOperationInfo[] {});
|
||||
Map<String, JmxEndpointOperation> operationsInfo = new LinkedHashMap<>();
|
||||
operationsMapping.forEach((name, t) -> operationsInfo.put(name, t.operation));
|
||||
MBeanInfo info = new ModelMBeanInfoSupport(EndpointMBean.class.getName(),
|
||||
getDescription(endpointInfo), new ModelMBeanAttributeInfo[0],
|
||||
new ModelMBeanConstructorInfo[0], operationsMBeanInfo,
|
||||
new ModelMBeanNotificationInfo[0]);
|
||||
return new EndpointMBeanInfo(endpointInfo.getId(), info, operationsInfo);
|
||||
}
|
||||
|
||||
private String getDescription(EndpointInfo<?> endpointInfo) {
|
||||
return "MBean operations for endpoint " + endpointInfo.getId();
|
||||
}
|
||||
|
||||
private Map<String, OperationInfos> getOperationInfo(
|
||||
EndpointInfo<JmxEndpointOperation> endpointInfo) {
|
||||
Map<String, OperationInfos> operationInfos = new HashMap<>();
|
||||
endpointInfo.getOperations().forEach((operationInfo) -> {
|
||||
String name = operationInfo.getOperationName();
|
||||
ModelMBeanOperationInfo mBeanOperationInfo = new ModelMBeanOperationInfo(
|
||||
operationInfo.getOperationName(), operationInfo.getDescription(),
|
||||
getMBeanParameterInfos(operationInfo), this.responseMapper
|
||||
.mapResponseType(operationInfo.getOutputType()).getName(),
|
||||
mapOperationType(operationInfo.getType()));
|
||||
operationInfos.put(name,
|
||||
new OperationInfos(mBeanOperationInfo, operationInfo));
|
||||
});
|
||||
return operationInfos;
|
||||
}
|
||||
|
||||
private MBeanParameterInfo[] getMBeanParameterInfos(JmxEndpointOperation operation) {
|
||||
return operation.getParameters().stream()
|
||||
.map((operationParameter) -> new MBeanParameterInfo(
|
||||
operationParameter.getName(),
|
||||
operationParameter.getType().getName(),
|
||||
operationParameter.getDescription()))
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(),
|
||||
(parameterInfos) -> parameterInfos
|
||||
.toArray(new MBeanParameterInfo[parameterInfos.size()])));
|
||||
}
|
||||
|
||||
private int mapOperationType(OperationType type) {
|
||||
if (type == OperationType.READ) {
|
||||
return MBeanOperationInfo.INFO;
|
||||
}
|
||||
if (type == OperationType.WRITE || type == OperationType.DELETE) {
|
||||
return MBeanOperationInfo.ACTION;
|
||||
}
|
||||
return MBeanOperationInfo.UNKNOWN;
|
||||
}
|
||||
|
||||
private static class OperationInfos {
|
||||
|
||||
private final ModelMBeanOperationInfo mBeanOperationInfo;
|
||||
|
||||
private final JmxEndpointOperation operation;
|
||||
|
||||
OperationInfos(ModelMBeanOperationInfo mBeanOperationInfo,
|
||||
JmxEndpointOperation operation) {
|
||||
this.mBeanOperationInfo = mBeanOperationInfo;
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.MBeanRegistrationException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.jmx.JmxException;
|
||||
import org.springframework.jmx.export.MBeanExportException;
|
||||
import org.springframework.jmx.export.MBeanExporter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* JMX Registrar for {@link EndpointMBean}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
* @see EndpointObjectNameFactory
|
||||
*/
|
||||
public class EndpointMBeanRegistrar {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(EndpointMBeanRegistrar.class);
|
||||
|
||||
private final MBeanServer mBeanServer;
|
||||
|
||||
private final EndpointObjectNameFactory objectNameFactory;
|
||||
|
||||
/**
|
||||
* Create a new instance with the {@link MBeanExporter} and
|
||||
* {@link EndpointObjectNameFactory} to use.
|
||||
* @param mBeanServer the mbean exporter
|
||||
* @param objectNameFactory the {@link ObjectName} factory
|
||||
*/
|
||||
public EndpointMBeanRegistrar(MBeanServer mBeanServer,
|
||||
EndpointObjectNameFactory objectNameFactory) {
|
||||
Assert.notNull(mBeanServer, "MBeanServer must not be null");
|
||||
Assert.notNull(objectNameFactory, "ObjectNameFactory must not be null");
|
||||
this.mBeanServer = mBeanServer;
|
||||
this.objectNameFactory = objectNameFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the specified {@link EndpointMBean} and return its {@link ObjectName}.
|
||||
* @param endpoint the endpoint to register
|
||||
* @return the {@link ObjectName} used to register the {@code endpoint}
|
||||
*/
|
||||
public ObjectName registerEndpointMBean(EndpointMBean endpoint) {
|
||||
Assert.notNull(endpoint, "Endpoint must not be null");
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Registering endpoint with id '" + endpoint.getEndpointId()
|
||||
+ "' to the JMX domain");
|
||||
}
|
||||
ObjectName objectName = this.objectNameFactory.generate(endpoint);
|
||||
this.mBeanServer.registerMBean(endpoint, objectName);
|
||||
return objectName;
|
||||
}
|
||||
catch (MalformedObjectNameException ex) {
|
||||
throw new IllegalStateException("Invalid ObjectName for endpoint with id '"
|
||||
+ endpoint.getEndpointId() + "'", ex);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MBeanExportException(
|
||||
"Failed to register MBean for endpoint with id '"
|
||||
+ endpoint.getEndpointId() + "'",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the specified {@link ObjectName} if necessary.
|
||||
* @param objectName the {@link ObjectName} of the endpoint to unregister
|
||||
* @return {@code true} if the endpoint was unregistered, {@code false} if no such
|
||||
* endpoint was found
|
||||
*/
|
||||
public boolean unregisterEndpointMbean(ObjectName objectName) {
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Unregister endpoint with ObjectName '" + objectName + "' "
|
||||
+ "from the JMX domain");
|
||||
}
|
||||
this.mBeanServer.unregisterMBean(objectName);
|
||||
return true;
|
||||
}
|
||||
catch (InstanceNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
catch (MBeanRegistrationException ex) {
|
||||
throw new JmxException(
|
||||
"Failed to unregister MBean with ObjectName '" + objectName + "'",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import javax.management.MalformedObjectNameException;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
/**
|
||||
* A factory to create an {@link ObjectName} for an {@link EndpointMBean}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EndpointObjectNameFactory {
|
||||
|
||||
/**
|
||||
* Generate an {@link ObjectName} for the specified {@link EndpointMBean endpoint}.
|
||||
* @param mBean the endpoint to handle
|
||||
* @return the {@link ObjectName} to use for the endpoint
|
||||
* @throws MalformedObjectNameException if the object name is invalid
|
||||
*/
|
||||
ObjectName generate(EndpointMBean mBean) throws MalformedObjectNameException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
|
||||
/**
|
||||
* A factory for creating JMX MBeans for endpoint operations.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxEndpointMBeanFactory {
|
||||
|
||||
private final EndpointMBeanInfoAssembler assembler;
|
||||
|
||||
private final JmxOperationResponseMapper resultMapper;
|
||||
|
||||
/**
|
||||
* Create a new {@link JmxEndpointMBeanFactory} instance that will use the given
|
||||
* {@code responseMapper} to convert an operation's response to a JMX-friendly form.
|
||||
* @param responseMapper the response mapper
|
||||
*/
|
||||
public JmxEndpointMBeanFactory(JmxOperationResponseMapper responseMapper) {
|
||||
this.assembler = new EndpointMBeanInfoAssembler(responseMapper);
|
||||
this.resultMapper = responseMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates MBeans for the given {@code endpoints}.
|
||||
* @param endpoints the endpoints
|
||||
* @return the MBeans
|
||||
*/
|
||||
public Collection<EndpointMBean> createMBeans(
|
||||
Collection<EndpointInfo<JmxEndpointOperation>> endpoints) {
|
||||
return endpoints.stream().map(this::createMBean).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private EndpointMBean createMBean(EndpointInfo<JmxEndpointOperation> endpointInfo) {
|
||||
EndpointMBeanInfo endpointMBeanInfo = this.assembler
|
||||
.createEndpointMBeanInfo(endpointInfo);
|
||||
return new EndpointMBean(this.resultMapper::mapResponse, endpointMBeanInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
|
||||
/**
|
||||
* An operation on a JMX endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxEndpointOperation extends Operation {
|
||||
|
||||
private final String operationName;
|
||||
|
||||
private final Class<?> outputType;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final List<JmxEndpointOperationParameterInfo> parameters;
|
||||
|
||||
/**
|
||||
* Creates a new {@code JmxEndpointOperation} for an operation of the given
|
||||
* {@code type}. The operation can be performed using the given {@code invoker}.
|
||||
* @param type the type of the operation
|
||||
* @param invoker used to perform the operation
|
||||
* @param operationName the name of the operation
|
||||
* @param outputType the type of the output of the operation
|
||||
* @param description the description of the operation
|
||||
* @param parameters the parameters of the operation
|
||||
*/
|
||||
public JmxEndpointOperation(OperationType type, OperationInvoker invoker,
|
||||
String operationName, Class<?> outputType, String description,
|
||||
List<JmxEndpointOperationParameterInfo> parameters) {
|
||||
super(type, invoker, true);
|
||||
this.operationName = operationName;
|
||||
this.outputType = outputType;
|
||||
this.description = description;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the operation.
|
||||
* @return the operation name
|
||||
*/
|
||||
public String getOperationName() {
|
||||
return this.operationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the output of the operation.
|
||||
* @return the output type
|
||||
*/
|
||||
public Class<?> getOutputType() {
|
||||
return this.outputType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description of the operation.
|
||||
* @return the operation description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameters of the operation.
|
||||
* @return the operation parameters
|
||||
*/
|
||||
public List<JmxEndpointOperationParameterInfo> getParameters() {
|
||||
return Collections.unmodifiableList(this.parameters);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
/**
|
||||
* Describes the parameters of an operation on a JMX endpoint.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxEndpointOperationParameterInfo {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
private final String description;
|
||||
|
||||
public JmxEndpointOperationParameterInfo(String name, Class<?> type,
|
||||
String description) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the description of the parameter or {@code null} if none is available.
|
||||
* @return the description or {@code null}
|
||||
*/
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
/**
|
||||
* A {@code JmxOperationResponseMapper} maps an operation's response to a JMX-friendly
|
||||
* form.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface JmxOperationResponseMapper {
|
||||
|
||||
/**
|
||||
* Map the operation's response so that it can be consumed by a JMX compliant client.
|
||||
* @param response the operation's response
|
||||
* @return the {@code response}, in a JMX compliant format
|
||||
*/
|
||||
Object mapResponse(Object response);
|
||||
|
||||
/**
|
||||
* Map the response type to its JMX compliant counterpart.
|
||||
* @param responseType the operation's response type
|
||||
* @return the JMX compliant type
|
||||
*/
|
||||
Class<?> mapResponseType(Class<?> responseType);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointExposure;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.ReflectiveOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.jmx.JmxEndpointOperationParameterInfo;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
|
||||
import org.springframework.jmx.export.metadata.ManagedOperation;
|
||||
import org.springframework.jmx.export.metadata.ManagedOperationParameter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Discovers the {@link Endpoint endpoints} in an {@link ApplicationContext} with
|
||||
* {@link JmxEndpointExtension JMX extensions} applied to them.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JmxAnnotationEndpointDiscoverer
|
||||
extends AnnotationEndpointDiscoverer<JmxEndpointOperation, String> {
|
||||
|
||||
private static final AnnotationJmxAttributeSource jmxAttributeSource = new AnnotationJmxAttributeSource();
|
||||
|
||||
/**
|
||||
* Creates a new {@link JmxAnnotationEndpointDiscoverer} that will discover
|
||||
* {@link Endpoint endpoints} and {@link JmxEndpointExtension jmx extensions} using
|
||||
* the given {@link ApplicationContext}.
|
||||
* @param applicationContext the application context
|
||||
* @param parameterMapper the {@link OperationParameterMapper} used to convert
|
||||
* arguments when an operation is invoked
|
||||
* @param cachingConfigurationFactory the {@link CachingConfiguration} factory to use
|
||||
*/
|
||||
public JmxAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
OperationParameterMapper parameterMapper,
|
||||
CachingConfigurationFactory cachingConfigurationFactory) {
|
||||
super(applicationContext, new JmxEndpointOperationFactory(parameterMapper),
|
||||
JmxEndpointOperation::getOperationName, cachingConfigurationFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<EndpointInfo<JmxEndpointOperation>> discoverEndpoints() {
|
||||
Collection<EndpointInfoDescriptor<JmxEndpointOperation, String>> endpointDescriptors = discoverEndpoints(
|
||||
JmxEndpointExtension.class, EndpointExposure.JMX);
|
||||
verifyThatOperationsHaveDistinctName(endpointDescriptors);
|
||||
return endpointDescriptors.stream().map(EndpointInfoDescriptor::getEndpointInfo)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void verifyThatOperationsHaveDistinctName(
|
||||
Collection<EndpointInfoDescriptor<JmxEndpointOperation, String>> endpointDescriptors) {
|
||||
List<List<JmxEndpointOperation>> clashes = new ArrayList<>();
|
||||
endpointDescriptors.forEach((descriptor) -> clashes
|
||||
.addAll(descriptor.findDuplicateOperations().values()));
|
||||
if (!clashes.isEmpty()) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(
|
||||
String.format("Found multiple JMX operations with the same name:%n"));
|
||||
clashes.forEach((clash) -> {
|
||||
message.append(" ").append(clash.get(0).getOperationName())
|
||||
.append(String.format(":%n"));
|
||||
clash.forEach((operation) -> message.append(" ")
|
||||
.append(String.format("%s%n", operation)));
|
||||
});
|
||||
throw new IllegalStateException(message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class JmxEndpointOperationFactory
|
||||
implements EndpointOperationFactory<JmxEndpointOperation> {
|
||||
|
||||
private final OperationParameterMapper parameterMapper;
|
||||
|
||||
JmxEndpointOperationFactory(OperationParameterMapper parameterMapper) {
|
||||
this.parameterMapper = parameterMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JmxEndpointOperation createOperation(String endpointId,
|
||||
AnnotationAttributes operationAttributes, Object target, Method method,
|
||||
OperationType type, long timeToLive) {
|
||||
String operationName = method.getName();
|
||||
Class<?> outputType = mapParameterType(method.getReturnType());
|
||||
String description = getDescription(method,
|
||||
() -> "Invoke " + operationName + " for endpoint " + endpointId);
|
||||
List<JmxEndpointOperationParameterInfo> parameters = getParameters(method);
|
||||
OperationInvoker invoker = new ReflectiveOperationInvoker(
|
||||
this.parameterMapper, target, method);
|
||||
if (timeToLive > 0) {
|
||||
invoker = new CachingOperationInvoker(invoker, timeToLive);
|
||||
}
|
||||
return new JmxEndpointOperation(type, invoker, operationName, outputType,
|
||||
description, parameters);
|
||||
}
|
||||
|
||||
private String getDescription(Method method, Supplier<String> fallback) {
|
||||
ManagedOperation managedOperation = jmxAttributeSource
|
||||
.getManagedOperation(method);
|
||||
if (managedOperation != null
|
||||
&& StringUtils.hasText(managedOperation.getDescription())) {
|
||||
return managedOperation.getDescription();
|
||||
}
|
||||
return fallback.get();
|
||||
}
|
||||
|
||||
private List<JmxEndpointOperationParameterInfo> getParameters(Method method) {
|
||||
Parameter[] methodParameters = method.getParameters();
|
||||
if (methodParameters.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
ManagedOperationParameter[] managedOperationParameters = jmxAttributeSource
|
||||
.getManagedOperationParameters(method);
|
||||
if (managedOperationParameters.length == 0) {
|
||||
return getParametersInfo(methodParameters);
|
||||
}
|
||||
return getParametersInfo(methodParameters, managedOperationParameters);
|
||||
}
|
||||
|
||||
private List<JmxEndpointOperationParameterInfo> getParametersInfo(
|
||||
Parameter[] methodParameters) {
|
||||
List<JmxEndpointOperationParameterInfo> parameters = new ArrayList<>();
|
||||
for (Parameter methodParameter : methodParameters) {
|
||||
String name = methodParameter.getName();
|
||||
Class<?> type = mapParameterType(methodParameter.getType());
|
||||
parameters.add(new JmxEndpointOperationParameterInfo(name, type, null));
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private List<JmxEndpointOperationParameterInfo> getParametersInfo(
|
||||
Parameter[] methodParameters,
|
||||
ManagedOperationParameter[] managedOperationParameters) {
|
||||
List<JmxEndpointOperationParameterInfo> parameters = new ArrayList<>();
|
||||
for (int i = 0; i < managedOperationParameters.length; i++) {
|
||||
ManagedOperationParameter managedOperationParameter = managedOperationParameters[i];
|
||||
Parameter methodParameter = methodParameters[i];
|
||||
parameters.add(new JmxEndpointOperationParameterInfo(
|
||||
managedOperationParameter.getName(),
|
||||
mapParameterType(methodParameter.getType()),
|
||||
managedOperationParameter.getDescription()));
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private Class<?> mapParameterType(Class<?> parameter) {
|
||||
if (parameter.isEnum()) {
|
||||
return String.class;
|
||||
}
|
||||
if (Date.class.isAssignableFrom(parameter)) {
|
||||
return String.class;
|
||||
}
|
||||
if (parameter.getName().startsWith("java.")) {
|
||||
return parameter;
|
||||
}
|
||||
if (parameter.equals(Void.TYPE)) {
|
||||
return parameter;
|
||||
}
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.jmx.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
|
||||
/**
|
||||
* Identifies a type as being a JMX-specific extension of an {@link Endpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
* @see Endpoint
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface JmxEndpointExtension {
|
||||
|
||||
/**
|
||||
* The {@link Endpoint endpoint} class to which this JMX extension relates.
|
||||
* @return the endpoint class
|
||||
*/
|
||||
Class<?> endpoint();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Annotation support for actuator JMX endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.jmx.annotation;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* JMX support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator endpoint infrastructure.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint;
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
|
||||
/**
|
||||
* A resolver for {@link Link links} to web endpoints.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class EndpointLinksResolver {
|
||||
|
||||
/**
|
||||
* Resolves links to the operations of the given {code webEndpoints} based on a
|
||||
* request with the given {@code requestUrl}.
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param requestUrl the url of the request for the endpoint links
|
||||
* @return the links
|
||||
*/
|
||||
public Map<String, Link> resolveLinks(
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
String requestUrl) {
|
||||
String normalizedUrl = normalizeRequestUrl(requestUrl);
|
||||
Map<String, Link> links = new LinkedHashMap<>();
|
||||
links.put("self", new Link(normalizedUrl));
|
||||
for (EndpointInfo<WebEndpointOperation> endpoint : webEndpoints) {
|
||||
for (WebEndpointOperation operation : endpoint.getOperations()) {
|
||||
webEndpoints.stream().map(EndpointInfo::getId).forEach((id) -> links
|
||||
.put(operation.getId(), createLink(normalizedUrl, operation)));
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
private String normalizeRequestUrl(String requestUrl) {
|
||||
if (requestUrl.endsWith("/")) {
|
||||
return requestUrl.substring(0, requestUrl.length() - 1);
|
||||
}
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
private Link createLink(String requestUrl, WebEndpointOperation operation) {
|
||||
String path = operation.getRequestPredicate().getPath();
|
||||
return new Link(requestUrl + (path.startsWith("/") ? path : "/" + path));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* Details for a link in a
|
||||
* <a href="https://tools.ietf.org/html/draft-kelly-json-hal-08">HAL</a>-formatted
|
||||
* response.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class Link {
|
||||
|
||||
private final String href;
|
||||
|
||||
private final boolean templated;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Link} with the given {@code href}.
|
||||
* @param href the href
|
||||
*/
|
||||
public Link(String href) {
|
||||
this.href = href;
|
||||
this.templated = href.contains("{");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the href of the link.
|
||||
* @return the href
|
||||
*/
|
||||
public String getHref() {
|
||||
return this.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the {@link #getHref() href} is templated.
|
||||
* @return {@code true} if the href is templated, otherwise {@code false}
|
||||
*/
|
||||
public boolean isTemplated() {
|
||||
return this.templated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("href", this.href).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* A predicate for a request to an operation on a web endpoint.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class OperationRequestPredicate {
|
||||
|
||||
private final String path;
|
||||
|
||||
private final String canonicalPath;
|
||||
|
||||
private final WebEndpointHttpMethod httpMethod;
|
||||
|
||||
private final Collection<String> consumes;
|
||||
|
||||
private final Collection<String> produces;
|
||||
|
||||
/**
|
||||
* Creates a new {@code OperationRequestPredicate}.
|
||||
*
|
||||
* @param path the path for the operation
|
||||
* @param httpMethod the HTTP method that the operation supports
|
||||
* @param produces the media types that the operation produces
|
||||
* @param consumes the media types that the operation consumes
|
||||
*/
|
||||
public OperationRequestPredicate(String path, WebEndpointHttpMethod httpMethod,
|
||||
Collection<String> consumes, Collection<String> produces) {
|
||||
this.path = path;
|
||||
this.canonicalPath = path.replaceAll("\\{.*?}", "{*}");
|
||||
this.httpMethod = httpMethod;
|
||||
this.consumes = consumes;
|
||||
this.produces = produces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path for the operation.
|
||||
* @return the path
|
||||
*/
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP method for the operation.
|
||||
* @return the HTTP method
|
||||
*/
|
||||
public WebEndpointHttpMethod getHttpMethod() {
|
||||
return this.httpMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media types that the operation consumes.
|
||||
* @return the consumed media types
|
||||
*/
|
||||
public Collection<String> getConsumes() {
|
||||
return Collections.unmodifiableCollection(this.consumes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the media types that the operation produces.
|
||||
* @return the produced media types
|
||||
*/
|
||||
public Collection<String> getProduces() {
|
||||
return Collections.unmodifiableCollection(this.produces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("httpMethod", this.httpMethod)
|
||||
.append("path", this.path).append("consumes", this.consumes)
|
||||
.append("produces", this.produces).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + this.consumes.hashCode();
|
||||
result = prime * result + this.httpMethod.hashCode();
|
||||
result = prime * result + this.canonicalPath.hashCode();
|
||||
result = prime * result + this.produces.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
OperationRequestPredicate other = (OperationRequestPredicate) obj;
|
||||
boolean result = true;
|
||||
result = result && this.consumes.equals(other.consumes);
|
||||
result = result && this.httpMethod == other.httpMethod;
|
||||
result = result && this.canonicalPath.equals(other.canonicalPath);
|
||||
result = result && this.produces.equals(other.produces);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
/**
|
||||
* An enumeration of HTTP methods supported by web endpoint operations.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum WebEndpointHttpMethod {
|
||||
|
||||
/**
|
||||
* An HTTP GET request.
|
||||
*/
|
||||
GET,
|
||||
|
||||
/**
|
||||
* An HTTP POST request.
|
||||
*/
|
||||
POST,
|
||||
|
||||
/**
|
||||
* An HTTP DELETE request.
|
||||
*/
|
||||
DELETE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
|
||||
/**
|
||||
* An operation on a web endpoint.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class WebEndpointOperation extends Operation {
|
||||
|
||||
private final OperationRequestPredicate requestPredicate;
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointOperation} with the given {@code type}. The
|
||||
* operation can be performed using the given {@code operationInvoker}. The operation
|
||||
* can handle requests that match the given {@code requestPredicate}.
|
||||
* @param type the type of the operation
|
||||
* @param operationInvoker used to perform the operation
|
||||
* @param blocking whether or not this is a blocking operation
|
||||
* @param requestPredicate the predicate for requests that can be handled by the
|
||||
* @param id the id of the operation, unique within its endpoint operation
|
||||
*/
|
||||
public WebEndpointOperation(OperationType type, OperationInvoker operationInvoker,
|
||||
boolean blocking, OperationRequestPredicate requestPredicate, String id) {
|
||||
super(type, operationInvoker, blocking);
|
||||
this.requestPredicate = requestPredicate;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the predicate for requests that can be handled by this operation.
|
||||
* @return the predicate
|
||||
*/
|
||||
public OperationRequestPredicate getRequestPredicate() {
|
||||
return this.requestPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the operation that uniquely identifies it within its endpoint.
|
||||
* @return the ID
|
||||
*/
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension;
|
||||
|
||||
/**
|
||||
* A {@code WebEndpointResponse} can be returned by an operation on a
|
||||
* {@link WebEndpointExtension} to provide additional, web-specific information such as
|
||||
* the HTTP status code.
|
||||
*
|
||||
* @param <T> the type of the response body
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @author Vedran Pavic
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public final class WebEndpointResponse<T> {
|
||||
|
||||
/**
|
||||
* {@code 200 OK}.
|
||||
*/
|
||||
public static final int STATUS_OK = 200;
|
||||
|
||||
/**
|
||||
* {@code 400 Bad Request}.
|
||||
*/
|
||||
public static final int STATUS_BAD_REQUEST = 400;
|
||||
|
||||
/**
|
||||
* {@code 404 Not Found}.
|
||||
*/
|
||||
public static final int STATUS_NOT_FOUND = 404;
|
||||
|
||||
/**
|
||||
* {@code 429 Too Many Requests}.
|
||||
*/
|
||||
public static final int STATUS_TOO_MANY_REQUESTS = 429;
|
||||
|
||||
/**
|
||||
* {@code 500 Internal Server Error}.
|
||||
*/
|
||||
public static final int STATUS_INTERNAL_SERVER_ERROR = 500;
|
||||
|
||||
/**
|
||||
* {@code 503 Service Unavailable}.
|
||||
*/
|
||||
public static final int STATUS_SERVICE_UNAVAILABLE = 503;
|
||||
|
||||
private final T body;
|
||||
|
||||
private final int status;
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointResponse} with no body and a 200 (OK) status.
|
||||
*/
|
||||
public WebEndpointResponse() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointResponse} with no body and the given
|
||||
* {@code status}.
|
||||
* @param status the HTTP status
|
||||
*/
|
||||
public WebEndpointResponse(int status) {
|
||||
this(null, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointResponse} with then given body and a 200 (OK)
|
||||
* status.
|
||||
* @param body the body
|
||||
*/
|
||||
public WebEndpointResponse(T body) {
|
||||
this(body, STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointResponse} with then given body and status.
|
||||
* @param body the body
|
||||
* @param status the HTTP status
|
||||
*/
|
||||
public WebEndpointResponse(T body, int status) {
|
||||
this.body = body;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the body for the response.
|
||||
* @return the body
|
||||
*/
|
||||
public T getBody() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status for the response.
|
||||
* @return the status
|
||||
*/
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointExposure;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationParameterMapper;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.ReflectiveOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.AnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
|
||||
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Discovers the {@link Endpoint endpoints} in an {@link ApplicationContext} with
|
||||
* {@link WebEndpointExtension web extensions} applied to them.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class WebAnnotationEndpointDiscoverer extends
|
||||
AnnotationEndpointDiscoverer<WebEndpointOperation, OperationRequestPredicate> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebAnnotationEndpointDiscoverer} that will discover
|
||||
* {@link Endpoint endpoints} and {@link WebEndpointExtension web extensions} using
|
||||
* the given {@link ApplicationContext}.
|
||||
* @param applicationContext the application context
|
||||
* @param operationParameterMapper the {@link OperationParameterMapper} used to
|
||||
* convert arguments when an operation is invoked
|
||||
* @param cachingConfigurationFactory the {@link CachingConfiguration} factory to use
|
||||
* @param consumedMediaTypes the media types consumed by web endpoint operations
|
||||
* @param producedMediaTypes the media types produced by web endpoint operations
|
||||
*/
|
||||
public WebAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
|
||||
OperationParameterMapper operationParameterMapper,
|
||||
CachingConfigurationFactory cachingConfigurationFactory,
|
||||
Collection<String> consumedMediaTypes,
|
||||
Collection<String> producedMediaTypes) {
|
||||
super(applicationContext,
|
||||
new WebEndpointOperationFactory(operationParameterMapper,
|
||||
consumedMediaTypes, producedMediaTypes),
|
||||
WebEndpointOperation::getRequestPredicate, cachingConfigurationFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<EndpointInfo<WebEndpointOperation>> discoverEndpoints() {
|
||||
Collection<EndpointInfoDescriptor<WebEndpointOperation, OperationRequestPredicate>> endpoints = discoverEndpoints(
|
||||
WebEndpointExtension.class, EndpointExposure.WEB);
|
||||
verifyThatOperationsHaveDistinctPredicates(endpoints);
|
||||
return endpoints.stream().map(EndpointInfoDescriptor::getEndpointInfo)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void verifyThatOperationsHaveDistinctPredicates(
|
||||
Collection<EndpointInfoDescriptor<WebEndpointOperation, OperationRequestPredicate>> endpointDescriptors) {
|
||||
List<List<WebEndpointOperation>> clashes = new ArrayList<>();
|
||||
endpointDescriptors.forEach((descriptor) -> clashes
|
||||
.addAll(descriptor.findDuplicateOperations().values()));
|
||||
if (!clashes.isEmpty()) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(String.format(
|
||||
"Found multiple web operations with matching request predicates:%n"));
|
||||
clashes.forEach((clash) -> {
|
||||
message.append(" ").append(clash.get(0).getRequestPredicate())
|
||||
.append(String.format(":%n"));
|
||||
clash.forEach((operation) -> message.append(" ")
|
||||
.append(String.format("%s%n", operation)));
|
||||
});
|
||||
throw new IllegalStateException(message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WebEndpointOperationFactory
|
||||
implements EndpointOperationFactory<WebEndpointOperation> {
|
||||
|
||||
private static final boolean REACTIVE_STREAMS_PRESENT = ClassUtils.isPresent(
|
||||
"org.reactivestreams.Publisher",
|
||||
WebEndpointOperationFactory.class.getClassLoader());
|
||||
|
||||
private final OperationParameterMapper parameterMapper;
|
||||
|
||||
private final Collection<String> consumedMediaTypes;
|
||||
|
||||
private final Collection<String> producedMediaTypes;
|
||||
|
||||
private WebEndpointOperationFactory(OperationParameterMapper parameterMapper,
|
||||
Collection<String> consumedMediaTypes,
|
||||
Collection<String> producedMediaTypes) {
|
||||
this.parameterMapper = parameterMapper;
|
||||
this.consumedMediaTypes = consumedMediaTypes;
|
||||
this.producedMediaTypes = producedMediaTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebEndpointOperation createOperation(String endpointId,
|
||||
AnnotationAttributes operationAttributes, Object target, Method method,
|
||||
OperationType type, long timeToLive) {
|
||||
WebEndpointHttpMethod httpMethod = determineHttpMethod(type);
|
||||
OperationRequestPredicate requestPredicate = new OperationRequestPredicate(
|
||||
determinePath(endpointId, method), httpMethod,
|
||||
determineConsumedMediaTypes(httpMethod, method),
|
||||
determineProducedMediaTypes(
|
||||
operationAttributes.getStringArray("produces"), method));
|
||||
OperationInvoker invoker = new ReflectiveOperationInvoker(
|
||||
this.parameterMapper, target, method);
|
||||
if (timeToLive > 0) {
|
||||
invoker = new CachingOperationInvoker(invoker, timeToLive);
|
||||
}
|
||||
return new WebEndpointOperation(type, invoker, determineBlocking(method),
|
||||
requestPredicate, determineId(endpointId, method));
|
||||
}
|
||||
|
||||
private String determinePath(String endpointId, Method operationMethod) {
|
||||
StringBuilder path = new StringBuilder(endpointId);
|
||||
Stream.of(operationMethod.getParameters())
|
||||
.filter((
|
||||
parameter) -> parameter.getAnnotation(Selector.class) != null)
|
||||
.map((parameter) -> "/{" + parameter.getName() + "}")
|
||||
.forEach(path::append);
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
private String determineId(String endpointId, Method operationMethod) {
|
||||
StringBuilder path = new StringBuilder(endpointId);
|
||||
Stream.of(operationMethod.getParameters())
|
||||
.filter((
|
||||
parameter) -> parameter.getAnnotation(Selector.class) != null)
|
||||
.map((parameter) -> "-" + parameter.getName()).forEach(path::append);
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
private Collection<String> determineConsumedMediaTypes(
|
||||
WebEndpointHttpMethod httpMethod, Method method) {
|
||||
if (WebEndpointHttpMethod.POST == httpMethod && consumesRequestBody(method)) {
|
||||
return this.consumedMediaTypes;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Collection<String> determineProducedMediaTypes(String[] produces,
|
||||
Method method) {
|
||||
if (produces.length > 0) {
|
||||
return Arrays.asList(produces);
|
||||
}
|
||||
if (Void.class.equals(method.getReturnType())
|
||||
|| void.class.equals(method.getReturnType())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (producesResourceResponseBody(method)) {
|
||||
return Collections.singletonList("application/octet-stream");
|
||||
}
|
||||
return this.producedMediaTypes;
|
||||
}
|
||||
|
||||
private boolean producesResourceResponseBody(Method method) {
|
||||
if (Resource.class.equals(method.getReturnType())) {
|
||||
return true;
|
||||
}
|
||||
if (WebEndpointResponse.class.isAssignableFrom(method.getReturnType())) {
|
||||
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
|
||||
if (ResolvableType.forClass(Resource.class)
|
||||
.isAssignableFrom(returnType.getGeneric(0))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean consumesRequestBody(Method method) {
|
||||
return Stream.of(method.getParameters()).anyMatch(
|
||||
(parameter) -> parameter.getAnnotation(Selector.class) == null);
|
||||
}
|
||||
|
||||
private WebEndpointHttpMethod determineHttpMethod(OperationType operationType) {
|
||||
if (operationType == OperationType.WRITE) {
|
||||
return WebEndpointHttpMethod.POST;
|
||||
}
|
||||
if (operationType == OperationType.DELETE) {
|
||||
return WebEndpointHttpMethod.DELETE;
|
||||
}
|
||||
return WebEndpointHttpMethod.GET;
|
||||
}
|
||||
|
||||
private boolean determineBlocking(Method method) {
|
||||
return !REACTIVE_STREAMS_PRESENT
|
||||
|| !Publisher.class.isAssignableFrom(method.getReturnType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
|
||||
/**
|
||||
* Identifies a type as being a Web-specific extension of an {@link Endpoint}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
* @see Endpoint
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface WebEndpointExtension {
|
||||
|
||||
/**
|
||||
* The {@link Endpoint endpoint} class to which this Web extension relates.
|
||||
* @return the endpoint class
|
||||
*/
|
||||
Class<?> endpoint();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Annotation support for actuator web endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.web.annotation;
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web.jersey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.glassfish.jersey.process.Inflector;
|
||||
import org.glassfish.jersey.server.ContainerRequest;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.glassfish.jersey.server.model.Resource.Builder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for
|
||||
* {@link WebEndpointOperation web endpoint operations}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JerseyEndpointResourceFactory {
|
||||
|
||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
||||
|
||||
/**
|
||||
* Creates {@link Resource Resources} for the operations of the given
|
||||
* {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @return the resources for the operations
|
||||
*/
|
||||
public Collection<Resource> createEndpointResources(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
webEndpoints.stream()
|
||||
.flatMap((endpointInfo) -> endpointInfo.getOperations().stream())
|
||||
.map((operation) -> createResource(endpointMapping, operation))
|
||||
.forEach(resources::add);
|
||||
if (StringUtils.hasText(endpointMapping.getPath())) {
|
||||
resources.add(
|
||||
createEndpointLinksResource(endpointMapping.getPath(), webEndpoints));
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private Resource createResource(EndpointMapping endpointMapping,
|
||||
WebEndpointOperation operation) {
|
||||
OperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
Builder resourceBuilder = Resource.builder()
|
||||
.path(endpointMapping.createSubPath(requestPredicate.getPath()));
|
||||
resourceBuilder.addMethod(requestPredicate.getHttpMethod().name())
|
||||
.consumes(toStringArray(requestPredicate.getConsumes()))
|
||||
.produces(toStringArray(requestPredicate.getProduces()))
|
||||
.handledBy(new EndpointInvokingInflector(operation.getInvoker(),
|
||||
!requestPredicate.getConsumes().isEmpty()));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
private String[] toStringArray(Collection<String> collection) {
|
||||
return collection.toArray(new String[collection.size()]);
|
||||
}
|
||||
|
||||
private Resource createEndpointLinksResource(String endpointPath,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointPath);
|
||||
resourceBuilder.addMethod("GET").produces(MediaType.APPLICATION_JSON).handledBy(
|
||||
new EndpointLinksInflector(webEndpoints, this.endpointLinksResolver));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
private static final class EndpointInvokingInflector
|
||||
implements Inflector<ContainerRequestContext, Object> {
|
||||
|
||||
private static final List<Function<Object, Object>> bodyConverters;
|
||||
|
||||
static {
|
||||
List<Function<Object, Object>> converters = new ArrayList<>();
|
||||
converters.add(new ResourceBodyConverter());
|
||||
if (ClassUtils.isPresent("reactor.core.publisher.Mono",
|
||||
EndpointInvokingInflector.class.getClassLoader())) {
|
||||
converters.add(new MonoBodyConverter());
|
||||
}
|
||||
bodyConverters = Collections.unmodifiableList(converters);
|
||||
}
|
||||
|
||||
private final OperationInvoker operationInvoker;
|
||||
|
||||
private final boolean readBody;
|
||||
|
||||
private EndpointInvokingInflector(OperationInvoker operationInvoker,
|
||||
boolean readBody) {
|
||||
this.operationInvoker = operationInvoker;
|
||||
this.readBody = readBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(ContainerRequestContext data) {
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
if (this.readBody) {
|
||||
arguments.putAll(extractBodyArguments(data));
|
||||
}
|
||||
arguments.putAll(extractPathParameters(data));
|
||||
arguments.putAll(extractQueryParameters(data));
|
||||
try {
|
||||
Object response = this.operationInvoker.invoke(arguments);
|
||||
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
||||
}
|
||||
catch (ParametersMissingException | ParameterMappingException ex) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> extractBodyArguments(ContainerRequestContext data) {
|
||||
Map<?, ?> entity = ((ContainerRequest) data).readEntity(Map.class);
|
||||
if (entity == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return (Map<String, Object>) entity;
|
||||
}
|
||||
|
||||
private Map<String, Object> extractPathParameters(
|
||||
ContainerRequestContext requestContext) {
|
||||
return extract(requestContext.getUriInfo().getPathParameters());
|
||||
}
|
||||
|
||||
private Map<String, Object> extractQueryParameters(
|
||||
ContainerRequestContext requestContext) {
|
||||
return extract(requestContext.getUriInfo().getQueryParameters());
|
||||
}
|
||||
|
||||
private Map<String, Object> extract(
|
||||
MultivaluedMap<String, String> multivaluedMap) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
multivaluedMap.forEach((name, values) -> {
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
result.put(name, values.size() == 1 ? values.get(0) : values);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private Response convertToJaxRsResponse(Object response, String httpMethod) {
|
||||
if (response == null) {
|
||||
boolean isGet = HttpMethod.GET.equals(httpMethod);
|
||||
Status status = (isGet ? Status.NOT_FOUND : Status.NO_CONTENT);
|
||||
return Response.status(status).build();
|
||||
}
|
||||
try {
|
||||
if (!(response instanceof WebEndpointResponse)) {
|
||||
return Response.status(Status.OK).entity(convertIfNecessary(response))
|
||||
.build();
|
||||
}
|
||||
WebEndpointResponse<?> webEndpointResponse = (WebEndpointResponse<?>) response;
|
||||
return Response.status(webEndpointResponse.getStatus())
|
||||
.entity(convertIfNecessary(webEndpointResponse.getBody()))
|
||||
.build();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertIfNecessary(Object body) throws IOException {
|
||||
for (Function<Object, Object> converter : bodyConverters) {
|
||||
body = converter.apply(body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ResourceBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (body instanceof org.springframework.core.io.Resource) {
|
||||
try {
|
||||
return ((org.springframework.core.io.Resource) body).getInputStream();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class MonoBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (body instanceof Mono) {
|
||||
return ((Mono<?>) body).block();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class EndpointLinksInflector
|
||||
implements Inflector<ContainerRequestContext, Response> {
|
||||
|
||||
private final Collection<EndpointInfo<WebEndpointOperation>> endpoints;
|
||||
|
||||
private final EndpointLinksResolver linksResolver;
|
||||
|
||||
private EndpointLinksInflector(
|
||||
Collection<EndpointInfo<WebEndpointOperation>> endpoints,
|
||||
EndpointLinksResolver linksResolver) {
|
||||
this.endpoints = endpoints;
|
||||
this.linksResolver = linksResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(ContainerRequestContext request) {
|
||||
Map<String, Link> links = this.linksResolver.resolveLinks(this.endpoints,
|
||||
request.getUriInfo().getAbsolutePath().toString());
|
||||
return Response.ok(Collections.singletonMap("_links", links)).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jersey support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.web.jersey;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Web support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.web;
|
||||
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web.reactive;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoSink;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.result.condition.ConsumesRequestCondition;
|
||||
import org.springframework.web.reactive.result.condition.PatternsRequestCondition;
|
||||
import org.springframework.web.reactive.result.condition.ProducesRequestCondition;
|
||||
import org.springframework.web.reactive.result.condition.RequestMethodsRequestCondition;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that makes web endpoints available over HTTP using
|
||||
* Spring WebFlux.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class WebFluxEndpointHandlerMapping extends RequestMappingInfoHandlerMapping
|
||||
implements InitializingBean {
|
||||
|
||||
private static final PathPatternParser pathPatternParser = new PathPatternParser();
|
||||
|
||||
private final Method handleRead = ReflectionUtils
|
||||
.findMethod(ReadOperationHandler.class, "handle", ServerWebExchange.class);
|
||||
|
||||
private final Method handleWrite = ReflectionUtils.findMethod(
|
||||
WriteOperationHandler.class, "handle", ServerWebExchange.class, Map.class);
|
||||
|
||||
private final Method links = ReflectionUtils.findMethod(getClass(), "links",
|
||||
ServerHttpRequest.class);
|
||||
|
||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
||||
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
|
||||
|
||||
private final CorsConfiguration corsConfiguration;
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints
|
||||
*/
|
||||
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointMapping, collection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the path beneath which all endpoints should be mapped
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public WebFluxEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.webEndpoints = webEndpoints;
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
this.webEndpoints.stream()
|
||||
.flatMap((webEndpoint) -> webEndpoint.getOperations().stream())
|
||||
.forEach(this::registerMappingForOperation);
|
||||
if (StringUtils.hasText(this.endpointMapping.getPath())) {
|
||||
registerLinksMapping();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLinksMapping() {
|
||||
registerMapping(new RequestMappingInfo(
|
||||
new PatternsRequestCondition(
|
||||
pathPatternParser.parse(this.endpointMapping.getPath())),
|
||||
new RequestMethodsRequestCondition(RequestMethod.GET), null, null, null,
|
||||
null, null), this, this.links);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration initCorsConfiguration(Object handler, Method method,
|
||||
RequestMappingInfo mapping) {
|
||||
return this.corsConfiguration;
|
||||
}
|
||||
|
||||
private void registerMappingForOperation(WebEndpointOperation operation) {
|
||||
OperationType operationType = operation.getType();
|
||||
OperationInvoker operationInvoker = operation.getInvoker();
|
||||
if (operation.isBlocking()) {
|
||||
operationInvoker = new ElasticSchedulerOperationInvoker(operationInvoker);
|
||||
}
|
||||
registerMapping(createRequestMappingInfo(operation),
|
||||
operationType == OperationType.WRITE
|
||||
? new WriteOperationHandler(operationInvoker)
|
||||
: new ReadOperationHandler(operationInvoker),
|
||||
operationType == OperationType.WRITE ? this.handleWrite
|
||||
: this.handleRead);
|
||||
}
|
||||
|
||||
private RequestMappingInfo createRequestMappingInfo(
|
||||
WebEndpointOperation operationInfo) {
|
||||
OperationRequestPredicate requestPredicate = operationInfo.getRequestPredicate();
|
||||
PatternsRequestCondition patterns = new PatternsRequestCondition(pathPatternParser
|
||||
.parse(this.endpointMapping.createSubPath(requestPredicate.getPath())));
|
||||
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
|
||||
RequestMethod.valueOf(requestPredicate.getHttpMethod().name()));
|
||||
ConsumesRequestCondition consumes = new ConsumesRequestCondition(
|
||||
toStringArray(requestPredicate.getConsumes()));
|
||||
ProducesRequestCondition produces = new ProducesRequestCondition(
|
||||
toStringArray(requestPredicate.getProduces()));
|
||||
return new RequestMappingInfo(null, patterns, methods, null, null, consumes,
|
||||
produces, null);
|
||||
}
|
||||
|
||||
private String[] toStringArray(Collection<String> collection) {
|
||||
return collection.toArray(new String[collection.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isHandler(Class<?> beanType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestMappingInfo getMappingForMethod(Method method,
|
||||
Class<?> handlerType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
private Map<String, Map<String, Link>> links(ServerHttpRequest request) {
|
||||
return Collections.singletonMap("_links",
|
||||
this.endpointLinksResolver.resolveLinks(this.webEndpoints,
|
||||
UriComponentsBuilder.fromUri(request.getURI()).replaceQuery(null)
|
||||
.toUriString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for handlers for endpoint operations.
|
||||
*/
|
||||
abstract class AbstractOperationHandler {
|
||||
|
||||
private final OperationInvoker operationInvoker;
|
||||
|
||||
AbstractOperationHandler(OperationInvoker operationInvoker) {
|
||||
this.operationInvoker = operationInvoker;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
Publisher<ResponseEntity<Object>> doHandle(ServerWebExchange exchange,
|
||||
Map<String, String> body) {
|
||||
Map<String, Object> arguments = new HashMap<>((Map<String, String>) exchange
|
||||
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
|
||||
if (body != null) {
|
||||
arguments.putAll(body);
|
||||
}
|
||||
exchange.getRequest().getQueryParams().forEach((name, values) -> arguments
|
||||
.put(name, values.size() == 1 ? values.get(0) : values));
|
||||
return handleResult((Publisher<?>) this.operationInvoker.invoke(arguments),
|
||||
exchange.getRequest().getMethod());
|
||||
}
|
||||
|
||||
private Publisher<ResponseEntity<Object>> handleResult(Publisher<?> result,
|
||||
HttpMethod httpMethod) {
|
||||
return Mono.from(result).map(this::toResponseEntity)
|
||||
.onErrorReturn(ParametersMissingException.class,
|
||||
new ResponseEntity<>(HttpStatus.BAD_REQUEST))
|
||||
.onErrorReturn(ParameterMappingException.class,
|
||||
new ResponseEntity<>(HttpStatus.BAD_REQUEST))
|
||||
.defaultIfEmpty(new ResponseEntity<>(httpMethod == HttpMethod.GET
|
||||
? HttpStatus.NOT_FOUND : HttpStatus.NO_CONTENT));
|
||||
}
|
||||
|
||||
private ResponseEntity<Object> toResponseEntity(Object response) {
|
||||
if (!(response instanceof WebEndpointResponse)) {
|
||||
return new ResponseEntity<>(response, HttpStatus.OK);
|
||||
}
|
||||
WebEndpointResponse<?> webEndpointResponse = (WebEndpointResponse<?>) response;
|
||||
return new ResponseEntity<>(webEndpointResponse.getBody(),
|
||||
HttpStatus.valueOf(webEndpointResponse.getStatus()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler for an endpoint write operation.
|
||||
*/
|
||||
final class WriteOperationHandler extends AbstractOperationHandler {
|
||||
|
||||
WriteOperationHandler(OperationInvoker operationInvoker) {
|
||||
super(operationInvoker);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
public Publisher<ResponseEntity<Object>> handle(ServerWebExchange exchange,
|
||||
@RequestBody(required = false) Map<String, String> body) {
|
||||
return doHandle(exchange, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler for an endpoint write operation.
|
||||
*/
|
||||
final class ReadOperationHandler extends AbstractOperationHandler {
|
||||
|
||||
ReadOperationHandler(OperationInvoker operationInvoker) {
|
||||
super(operationInvoker);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
public Publisher<ResponseEntity<Object>> handle(ServerWebExchange exchange) {
|
||||
return doHandle(exchange, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link OperationInvoker} that performs the invocation of a blocking operation on
|
||||
* a separate thread using Reactor's {@link Schedulers#elastic() elastic scheduler}.
|
||||
*/
|
||||
private static final class ElasticSchedulerOperationInvoker
|
||||
implements OperationInvoker {
|
||||
|
||||
private final OperationInvoker delegate;
|
||||
|
||||
private ElasticSchedulerOperationInvoker(OperationInvoker delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Map<String, Object> arguments) {
|
||||
return Mono.create((sink) -> Schedulers.elastic()
|
||||
.schedule(() -> invoke(arguments, sink)));
|
||||
}
|
||||
|
||||
private void invoke(Map<String, Object> arguments, MonoSink<Object> sink) {
|
||||
try {
|
||||
Object result = this.delegate.invoke(arguments);
|
||||
sink.success(result);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
sink.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring WebFlux support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.web.reactive;
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web.servlet;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that makes web endpoints available over HTTP using
|
||||
* Spring MVC.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class AbstractWebMvcEndpointHandlerMapping
|
||||
extends RequestMappingInfoHandlerMapping implements InitializingBean {
|
||||
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final Collection<EndpointInfo<WebEndpointOperation>> webEndpoints;
|
||||
|
||||
private final CorsConfiguration corsConfiguration;
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints operations
|
||||
*/
|
||||
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointMapping, collection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.webEndpoints = webEndpoints;
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
public Collection<EndpointInfo<WebEndpointOperation>> getEndpoints() {
|
||||
return this.webEndpoints;
|
||||
}
|
||||
|
||||
public EndpointMapping getEndpointMapping() {
|
||||
return this.endpointMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
this.webEndpoints.stream()
|
||||
.flatMap((webEndpoint) -> webEndpoint.getOperations().stream())
|
||||
.forEach(this::registerMappingForOperation);
|
||||
if (StringUtils.hasText(this.endpointMapping.getPath())) {
|
||||
registerLinksRequestMapping();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerLinksRequestMapping() {
|
||||
PatternsRequestCondition patterns = patternsRequestConditionForPattern("");
|
||||
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
|
||||
RequestMethod.GET);
|
||||
RequestMappingInfo mapping = new RequestMappingInfo(patterns, methods, null, null,
|
||||
null, null, null);
|
||||
registerMapping(mapping, this, getLinks());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration initCorsConfiguration(Object handler, Method method,
|
||||
RequestMappingInfo mapping) {
|
||||
return this.corsConfiguration;
|
||||
}
|
||||
|
||||
protected abstract Method getLinks();
|
||||
|
||||
protected abstract void registerMappingForOperation(WebEndpointOperation operation);
|
||||
|
||||
protected RequestMappingInfo createRequestMappingInfo(
|
||||
WebEndpointOperation operationInfo) {
|
||||
OperationRequestPredicate requestPredicate = operationInfo.getRequestPredicate();
|
||||
PatternsRequestCondition patterns = patternsRequestConditionForPattern(
|
||||
requestPredicate.getPath());
|
||||
RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
|
||||
RequestMethod.valueOf(requestPredicate.getHttpMethod().name()));
|
||||
ConsumesRequestCondition consumes = new ConsumesRequestCondition(
|
||||
toStringArray(requestPredicate.getConsumes()));
|
||||
ProducesRequestCondition produces = new ProducesRequestCondition(
|
||||
toStringArray(requestPredicate.getProduces()));
|
||||
return new RequestMappingInfo(null, patterns, methods, null, null, consumes,
|
||||
produces, null);
|
||||
}
|
||||
|
||||
private PatternsRequestCondition patternsRequestConditionForPattern(String path) {
|
||||
String[] patterns = new String[] { this.endpointMapping.createSubPath(path) };
|
||||
return new PatternsRequestCondition(patterns, null, null, false, false);
|
||||
}
|
||||
|
||||
private String[] toStringArray(Collection<String> collection) {
|
||||
return collection.toArray(new String[collection.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isHandler(Class<?> beanType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestMappingInfo getMappingForMethod(Method method,
|
||||
Class<?> handlerType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendInterceptors(List<Object> interceptors) {
|
||||
interceptors.add(new SkipPathExtensionContentNegotiation());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HandlerInterceptorAdapter} to ensure that
|
||||
* {@link PathExtensionContentNegotiationStrategy} is skipped for web endpoints.
|
||||
*/
|
||||
private static final class SkipPathExtensionContentNegotiation
|
||||
extends HandlerInterceptorAdapter {
|
||||
|
||||
private static final String SKIP_ATTRIBUTE = PathExtensionContentNegotiationStrategy.class
|
||||
.getName() + ".SKIP";
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
request.setAttribute(SKIP_ATTRIBUTE, Boolean.TRUE);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.endpoint.web.servlet;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.EndpointInfo;
|
||||
import org.springframework.boot.actuate.endpoint.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.ParameterMappingException;
|
||||
import org.springframework.boot.actuate.endpoint.ParametersMissingException;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that makes web endpoints available over HTTP using
|
||||
* Spring MVC.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
|
||||
|
||||
private final Method handle = ReflectionUtils.findMethod(OperationHandler.class,
|
||||
"handle", HttpServletRequest.class, Map.class);
|
||||
|
||||
private final Method links = ReflectionUtils.findMethod(
|
||||
WebMvcEndpointHandlerMapping.class, "links", HttpServletRequest.class);
|
||||
|
||||
private final EndpointLinksResolver endpointLinksResolver = new EndpointLinksResolver();
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param collection the web endpoints operations
|
||||
*/
|
||||
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> collection) {
|
||||
this(endpointMapping, collection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param webEndpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints
|
||||
*/
|
||||
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<EndpointInfo<WebEndpointOperation>> webEndpoints,
|
||||
CorsConfiguration corsConfiguration) {
|
||||
super(endpointMapping, webEndpoints, corsConfiguration);
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerMappingForOperation(WebEndpointOperation operation) {
|
||||
registerMapping(createRequestMappingInfo(operation),
|
||||
new OperationHandler(operation.getInvoker()), this.handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Method getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
private Map<String, Map<String, Link>> links(HttpServletRequest request) {
|
||||
return Collections.singletonMap("_links", this.endpointLinksResolver
|
||||
.resolveLinks(getEndpoints(), request.getRequestURL().toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler for an endpoint operation.
|
||||
*/
|
||||
final class OperationHandler {
|
||||
|
||||
private final OperationInvoker operationInvoker;
|
||||
|
||||
OperationHandler(OperationInvoker operationInvoker) {
|
||||
this.operationInvoker = operationInvoker;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ResponseBody
|
||||
public Object handle(HttpServletRequest request,
|
||||
@RequestBody(required = false) Map<String, String> body) {
|
||||
Map<String, Object> arguments = new HashMap<>((Map<String, String>) request
|
||||
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
|
||||
HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod());
|
||||
if (body != null && HttpMethod.POST == httpMethod) {
|
||||
arguments.putAll(body);
|
||||
}
|
||||
request.getParameterMap().forEach((name, values) -> arguments.put(name,
|
||||
values.length == 1 ? values[0] : Arrays.asList(values)));
|
||||
try {
|
||||
return handleResult(this.operationInvoker.invoke(arguments), httpMethod);
|
||||
}
|
||||
catch (ParametersMissingException | ParameterMappingException ex) {
|
||||
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
private Object handleResult(Object result, HttpMethod httpMethod) {
|
||||
if (result == null) {
|
||||
return new ResponseEntity<>(httpMethod == HttpMethod.GET
|
||||
? HttpStatus.NOT_FOUND : HttpStatus.NO_CONTENT);
|
||||
}
|
||||
if (!(result instanceof WebEndpointResponse)) {
|
||||
return result;
|
||||
}
|
||||
WebEndpointResponse<?> response = (WebEndpointResponse<?>) result;
|
||||
return new ResponseEntity<Object>(response.getBody(),
|
||||
HttpStatus.valueOf(response.getStatus()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring MVC support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.web.servlet;
|
||||
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.env;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
|
||||
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.boot.origin.OriginLookup;
|
||||
import org.springframework.core.env.CompositePropertySource;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.PropertyPlaceholderHelper;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Christian Dupuis
|
||||
* @author Madhura Bhave
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Endpoint(id = "env")
|
||||
public class EnvironmentEndpoint {
|
||||
|
||||
private final Sanitizer sanitizer = new Sanitizer();
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public EnvironmentEndpoint(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public void setKeysToSanitize(String... keysToSanitize) {
|
||||
this.sanitizer.setKeysToSanitize(keysToSanitize);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public EnvironmentDescriptor environment(@Nullable String pattern) {
|
||||
if (StringUtils.hasText(pattern)) {
|
||||
return getEnvironmentDescriptor(Pattern.compile(pattern).asPredicate());
|
||||
}
|
||||
return getEnvironmentDescriptor((name) -> true);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public EnvironmentEntryDescriptor environmentEntry(@Selector String toMatch) {
|
||||
return getEnvironmentEntryDescriptor(toMatch);
|
||||
}
|
||||
|
||||
private EnvironmentDescriptor getEnvironmentDescriptor(
|
||||
Predicate<String> propertyNamePredicate) {
|
||||
PlaceholdersResolver resolver = getResolver();
|
||||
List<PropertySourceDescriptor> propertySources = new ArrayList<>();
|
||||
getPropertySourcesAsMap().forEach((sourceName, source) -> {
|
||||
if (source instanceof EnumerablePropertySource) {
|
||||
propertySources.add(
|
||||
describeSource(sourceName, (EnumerablePropertySource<?>) source,
|
||||
resolver, propertyNamePredicate));
|
||||
}
|
||||
});
|
||||
return new EnvironmentDescriptor(
|
||||
Arrays.asList(this.environment.getActiveProfiles()), propertySources);
|
||||
}
|
||||
|
||||
private EnvironmentEntryDescriptor getEnvironmentEntryDescriptor(
|
||||
String propertyName) {
|
||||
Map<String, PropertyValueDescriptor> descriptors = getPropertySourceDescriptors(
|
||||
propertyName);
|
||||
PropertySummaryDescriptor summary = getPropertySummaryDescriptor(descriptors);
|
||||
return new EnvironmentEntryDescriptor(summary,
|
||||
Arrays.asList(this.environment.getActiveProfiles()),
|
||||
toPropertySourceDescriptors(descriptors));
|
||||
}
|
||||
|
||||
private List<PropertySourceEntryDescriptor> toPropertySourceDescriptors(
|
||||
Map<String, PropertyValueDescriptor> descriptors) {
|
||||
List<PropertySourceEntryDescriptor> result = new ArrayList<>();
|
||||
descriptors.forEach((name, property) -> result
|
||||
.add(new PropertySourceEntryDescriptor(name, property)));
|
||||
return result;
|
||||
}
|
||||
|
||||
private PropertySummaryDescriptor getPropertySummaryDescriptor(
|
||||
Map<String, PropertyValueDescriptor> descriptors) {
|
||||
for (Map.Entry<String, PropertyValueDescriptor> entry : descriptors.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
return new PropertySummaryDescriptor(entry.getKey(),
|
||||
entry.getValue().getValue());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, PropertyValueDescriptor> getPropertySourceDescriptors(
|
||||
String propertyName) {
|
||||
Map<String, PropertyValueDescriptor> propertySources = new LinkedHashMap<>();
|
||||
PlaceholdersResolver resolver = getResolver();
|
||||
getPropertySourcesAsMap()
|
||||
.forEach((sourceName, source) -> propertySources.put(sourceName,
|
||||
source.containsProperty(propertyName)
|
||||
? describeValueOf(propertyName, source, resolver)
|
||||
: null));
|
||||
return propertySources;
|
||||
}
|
||||
|
||||
private PropertySourceDescriptor describeSource(String sourceName,
|
||||
EnumerablePropertySource<?> source, PlaceholdersResolver resolver,
|
||||
Predicate<String> namePredicate) {
|
||||
Map<String, PropertyValueDescriptor> properties = new LinkedHashMap<>();
|
||||
Stream.of(source.getPropertyNames()).filter(namePredicate).forEach(
|
||||
(name) -> properties.put(name, describeValueOf(name, source, resolver)));
|
||||
return new PropertySourceDescriptor(sourceName, properties);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private PropertyValueDescriptor describeValueOf(String name, PropertySource<?> source,
|
||||
PlaceholdersResolver resolver) {
|
||||
Object resolved = resolver.resolvePlaceholders(source.getProperty(name));
|
||||
String origin = (source instanceof OriginLookup)
|
||||
? ((OriginLookup<Object>) source).getOrigin(name).toString() : null;
|
||||
return new PropertyValueDescriptor(sanitize(name, resolved), origin);
|
||||
}
|
||||
|
||||
private PlaceholdersResolver getResolver() {
|
||||
return new PropertySourcesPlaceholdersSanitizingResolver(getPropertySources(),
|
||||
this.sanitizer);
|
||||
}
|
||||
|
||||
private Map<String, PropertySource<?>> getPropertySourcesAsMap() {
|
||||
Map<String, PropertySource<?>> map = new LinkedHashMap<>();
|
||||
for (PropertySource<?> source : getPropertySources()) {
|
||||
if (!ConfigurationPropertySources
|
||||
.isAttachedConfigurationPropertySource(source)) {
|
||||
extract("", map, source);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private MutablePropertySources getPropertySources() {
|
||||
if (this.environment instanceof ConfigurableEnvironment) {
|
||||
return ((ConfigurableEnvironment) this.environment).getPropertySources();
|
||||
}
|
||||
return new StandardEnvironment().getPropertySources();
|
||||
}
|
||||
|
||||
private void extract(String root, Map<String, PropertySource<?>> map,
|
||||
PropertySource<?> source) {
|
||||
if (source instanceof CompositePropertySource) {
|
||||
for (PropertySource<?> nest : ((CompositePropertySource) source)
|
||||
.getPropertySources()) {
|
||||
extract(source.getName() + ":", map, nest);
|
||||
}
|
||||
}
|
||||
else {
|
||||
map.put(root + source.getName(), source);
|
||||
}
|
||||
}
|
||||
|
||||
public Object sanitize(String name, Object object) {
|
||||
return this.sanitizer.sanitize(name, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertySourcesPlaceholdersResolver} that sanitizes sensitive placeholders
|
||||
* if present.
|
||||
*/
|
||||
private static class PropertySourcesPlaceholdersSanitizingResolver
|
||||
extends PropertySourcesPlaceholdersResolver {
|
||||
|
||||
private final Sanitizer sanitizer;
|
||||
|
||||
PropertySourcesPlaceholdersSanitizingResolver(Iterable<PropertySource<?>> sources,
|
||||
Sanitizer sanitizer) {
|
||||
super(sources,
|
||||
new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
|
||||
SystemPropertyUtils.PLACEHOLDER_SUFFIX,
|
||||
SystemPropertyUtils.VALUE_SEPARATOR, true));
|
||||
this.sanitizer = sanitizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolvePlaceholder(String placeholder) {
|
||||
String value = super.resolvePlaceholder(placeholder);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return (String) this.sanitizer.sanitize(placeholder, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an {@link Environment}.
|
||||
*/
|
||||
public static final class EnvironmentDescriptor {
|
||||
|
||||
private final List<String> activeProfiles;
|
||||
|
||||
private final List<PropertySourceDescriptor> propertySources;
|
||||
|
||||
private EnvironmentDescriptor(List<String> activeProfiles,
|
||||
List<PropertySourceDescriptor> propertySources) {
|
||||
this.activeProfiles = activeProfiles;
|
||||
this.propertySources = propertySources;
|
||||
}
|
||||
|
||||
public List<String> getActiveProfiles() {
|
||||
return this.activeProfiles;
|
||||
}
|
||||
|
||||
public List<PropertySourceDescriptor> getPropertySources() {
|
||||
return this.propertySources;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an entry of the {@link Environment}.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class EnvironmentEntryDescriptor {
|
||||
|
||||
private final PropertySummaryDescriptor property;
|
||||
|
||||
private final List<String> activeProfiles;
|
||||
|
||||
private final List<PropertySourceEntryDescriptor> propertySources;
|
||||
|
||||
private EnvironmentEntryDescriptor(PropertySummaryDescriptor property,
|
||||
List<String> activeProfiles,
|
||||
List<PropertySourceEntryDescriptor> propertySources) {
|
||||
this.property = property;
|
||||
this.activeProfiles = activeProfiles;
|
||||
this.propertySources = propertySources;
|
||||
}
|
||||
|
||||
public PropertySummaryDescriptor getProperty() {
|
||||
return this.property;
|
||||
}
|
||||
|
||||
public List<String> getActiveProfiles() {
|
||||
return this.activeProfiles;
|
||||
}
|
||||
|
||||
public List<PropertySourceEntryDescriptor> getPropertySources() {
|
||||
return this.propertySources;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary of a particular entry of the {@link Environment}.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class PropertySummaryDescriptor {
|
||||
|
||||
private final String source;
|
||||
|
||||
private final Object value;
|
||||
|
||||
public PropertySummaryDescriptor(String source, Object value) {
|
||||
this.source = source;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a {@link PropertySource}.
|
||||
*/
|
||||
public static final class PropertySourceDescriptor {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final Map<String, PropertyValueDescriptor> properties;
|
||||
|
||||
private PropertySourceDescriptor(String name,
|
||||
Map<String, PropertyValueDescriptor> properties) {
|
||||
this.name = name;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Map<String, PropertyValueDescriptor> getProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a particular entry of {@link PropertySource}.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class PropertySourceEntryDescriptor {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final PropertyValueDescriptor property;
|
||||
|
||||
private PropertySourceEntryDescriptor(String name,
|
||||
PropertyValueDescriptor property) {
|
||||
this.name = name;
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public PropertyValueDescriptor getProperty() {
|
||||
return this.property;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a property's value, including its origin if available.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class PropertyValueDescriptor {
|
||||
|
||||
private final Object value;
|
||||
|
||||
private final String origin;
|
||||
|
||||
private PropertyValueDescriptor(Object value, String origin) {
|
||||
this.value = value;
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getOrigin() {
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.env;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointExtension;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
||||
|
||||
/**
|
||||
* {@link WebEndpointExtension} for the {@link EnvironmentEndpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@WebEndpointExtension(endpoint = EnvironmentEndpoint.class)
|
||||
public class EnvironmentWebEndpointExtension {
|
||||
|
||||
private final EnvironmentEndpoint delegate;
|
||||
|
||||
public EnvironmentWebEndpointExtension(EnvironmentEndpoint delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<EnvironmentEntryDescriptor> environmentEntry(
|
||||
@Selector String toMatch) {
|
||||
EnvironmentEntryDescriptor descriptor = this.delegate.environmentEntry(toMatch);
|
||||
return new WebEndpointResponse<>(descriptor, getStatus(descriptor));
|
||||
}
|
||||
|
||||
private int getStatus(EnvironmentEntryDescriptor descriptor) {
|
||||
if (descriptor.getProperty() == null) {
|
||||
return WebEndpointResponse.STATUS_NOT_FOUND;
|
||||
}
|
||||
return WebEndpointResponse.STATUS_OK;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support for Spring Framework's
|
||||
* {@link org.springframework.core.env.Environment}.
|
||||
*/
|
||||
package org.springframework.boot.actuate.env;
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.flyway;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.flywaydb.core.api.MigrationInfo;
|
||||
import org.flywaydb.core.api.MigrationState;
|
||||
import org.flywaydb.core.api.MigrationType;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Endpoint} to expose flyway info.
|
||||
*
|
||||
* @author Eddú Meléndez
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Endpoint(id = "flyway")
|
||||
public class FlywayEndpoint {
|
||||
|
||||
private final Map<String, Flyway> flywayBeans;
|
||||
|
||||
public FlywayEndpoint(Map<String, Flyway> flywayBeans) {
|
||||
Assert.notEmpty(flywayBeans, "FlywayBeans must be specified");
|
||||
this.flywayBeans = flywayBeans;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Map<String, FlywayReport> flywayReports() {
|
||||
Map<String, FlywayReport> reports = new HashMap<>();
|
||||
this.flywayBeans.forEach((name, flyway) -> reports.put(name,
|
||||
new FlywayReport(flyway.info().all())));
|
||||
return reports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report for one {@link Flyway} instance.
|
||||
*/
|
||||
public static class FlywayReport {
|
||||
|
||||
private final List<FlywayMigration> migrations;
|
||||
|
||||
public FlywayReport(MigrationInfo[] migrations) {
|
||||
this.migrations = Stream.of(migrations).map(FlywayMigration::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public FlywayReport(List<FlywayMigration> migrations) {
|
||||
this.migrations = migrations;
|
||||
}
|
||||
|
||||
public List<FlywayMigration> getMigrations() {
|
||||
return this.migrations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Details of a migration performed by Flyway.
|
||||
*/
|
||||
public static class FlywayMigration {
|
||||
|
||||
private final MigrationType type;
|
||||
|
||||
private final Integer checksum;
|
||||
|
||||
private final String version;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final String script;
|
||||
|
||||
private final MigrationState state;
|
||||
|
||||
private final String installedBy;
|
||||
|
||||
private final Date installedOn;
|
||||
|
||||
private final Integer installedRank;
|
||||
|
||||
private final Integer executionTime;
|
||||
|
||||
public FlywayMigration(MigrationInfo info) {
|
||||
this.type = info.getType();
|
||||
this.checksum = info.getChecksum();
|
||||
this.version = nullSafeToString(info.getVersion());
|
||||
this.description = info.getDescription();
|
||||
this.script = info.getScript();
|
||||
this.state = info.getState();
|
||||
this.installedBy = info.getInstalledBy();
|
||||
this.installedOn = info.getInstalledOn();
|
||||
this.installedRank = info.getInstalledRank();
|
||||
this.executionTime = info.getExecutionTime();
|
||||
}
|
||||
|
||||
private String nullSafeToString(Object obj) {
|
||||
return (obj == null ? null : obj.toString());
|
||||
}
|
||||
|
||||
public MigrationType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public Integer getChecksum() {
|
||||
return this.checksum;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public String getScript() {
|
||||
return this.script;
|
||||
}
|
||||
|
||||
public MigrationState getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public String getInstalledBy() {
|
||||
return this.installedBy;
|
||||
}
|
||||
|
||||
public Date getInstalledOn() {
|
||||
return this.installedOn;
|
||||
}
|
||||
|
||||
public Integer getInstalledRank() {
|
||||
return this.installedRank;
|
||||
}
|
||||
|
||||
public Integer getExecutionTime() {
|
||||
return this.executionTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support for Flyway.
|
||||
*/
|
||||
package org.springframework.boot.actuate.flyway;
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base {@link HealthAggregator} implementation to allow subclasses to focus on
|
||||
* aggregating the {@link Status} instances and not deal with contextual details etc.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @author Vedran Pavic
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class AbstractHealthAggregator implements HealthAggregator {
|
||||
|
||||
@Override
|
||||
public final Health aggregate(Map<String, Health> healths) {
|
||||
List<Status> statusCandidates = new ArrayList<>();
|
||||
for (Map.Entry<String, Health> entry : healths.entrySet()) {
|
||||
statusCandidates.add(entry.getValue().getStatus());
|
||||
}
|
||||
Status status = aggregateStatus(statusCandidates);
|
||||
Map<String, Object> details = aggregateDetails(healths);
|
||||
return new Health.Builder(status, details).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the single 'aggregate' status that should be used from the specified
|
||||
* candidates.
|
||||
* @param candidates the candidates
|
||||
* @return a single status
|
||||
*/
|
||||
protected abstract Status aggregateStatus(List<Status> candidates);
|
||||
|
||||
/**
|
||||
* Return the map of 'aggregate' details that should be used from the specified
|
||||
* healths.
|
||||
* @param healths the health instances to aggregate
|
||||
* @return a map of details
|
||||
* @since 1.3.1
|
||||
*/
|
||||
protected Map<String, Object> aggregateDetails(Map<String, Health> healths) {
|
||||
return new LinkedHashMap<>(healths);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health.Builder;
|
||||
|
||||
/**
|
||||
* Base {@link HealthIndicator} implementations that encapsulates creation of
|
||||
* {@link Health} instance and error handling.
|
||||
* <p>
|
||||
* This implementation is only suitable if an {@link Exception} raised from
|
||||
* {@link #doHealthCheck(org.springframework.boot.actuate.health.Health.Builder)} should
|
||||
* create a {@link Status#DOWN} health status.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class AbstractHealthIndicator implements HealthIndicator {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@Override
|
||||
public final Health health() {
|
||||
Health.Builder builder = new Health.Builder();
|
||||
try {
|
||||
doHealthCheck(builder);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
this.logger.warn("Health check failed", ex);
|
||||
builder.down(ex);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual health check logic.
|
||||
* @param builder the {@link Builder} to report health status and details
|
||||
* @throws Exception any {@link Exception} that should create a {@link Status#DOWN}
|
||||
* system status.
|
||||
*/
|
||||
protected abstract void doHealthCheck(Health.Builder builder) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Base {@link ReactiveHealthIndicator} implementations that encapsulates creation of
|
||||
* {@link Health} instance and error handling.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public abstract class AbstractReactiveHealthIndicator implements ReactiveHealthIndicator {
|
||||
|
||||
@Override
|
||||
public final Mono<Health> health() {
|
||||
return doHealthCheck(new Health.Builder())
|
||||
.onErrorResume((ex) -> Mono.just(new Health.Builder().down(ex).build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual health check logic. If an error occurs in the pipeline it will be handled
|
||||
* automatically.
|
||||
* @param builder the {@link Health.Builder} to report health status and details
|
||||
* @return a {@link Mono} that provides the {@link Health}
|
||||
*/
|
||||
protected abstract Mono<Health> doHealthCheck(Health.Builder builder);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link HealthIndicator} that returns {@link Status#UP}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Christian Dupuis
|
||||
* @see Status#UP
|
||||
*/
|
||||
public class ApplicationHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
builder.up();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link HealthIndicator} that returns health indications from all registered delegates.
|
||||
*
|
||||
* @author Tyler J. Frederick
|
||||
* @author Phillip Webb
|
||||
* @author Christian Dupuis
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class CompositeHealthIndicator implements HealthIndicator {
|
||||
|
||||
private final Map<String, HealthIndicator> indicators;
|
||||
|
||||
private final HealthAggregator healthAggregator;
|
||||
|
||||
/**
|
||||
* Create a new {@link CompositeHealthIndicator}.
|
||||
* @param healthAggregator the health aggregator
|
||||
*/
|
||||
public CompositeHealthIndicator(HealthAggregator healthAggregator) {
|
||||
this(healthAggregator, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CompositeHealthIndicator} from the specified indicators.
|
||||
* @param healthAggregator the health aggregator
|
||||
* @param indicators a map of {@link HealthIndicator}s with the key being used as an
|
||||
* indicator name.
|
||||
*/
|
||||
public CompositeHealthIndicator(HealthAggregator healthAggregator,
|
||||
Map<String, HealthIndicator> indicators) {
|
||||
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
|
||||
Assert.notNull(indicators, "Indicators must not be null");
|
||||
this.indicators = new LinkedHashMap<>(indicators);
|
||||
this.healthAggregator = healthAggregator;
|
||||
}
|
||||
|
||||
public void addHealthIndicator(String name, HealthIndicator indicator) {
|
||||
this.indicators.put(name, indicator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
Map<String, Health> healths = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, HealthIndicator> entry : this.indicators.entrySet()) {
|
||||
healths.put(entry.getKey(), entry.getValue().health());
|
||||
}
|
||||
return this.healthAggregator.aggregate(healths);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Factory to create a {@link CompositeHealthIndicator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CompositeHealthIndicatorFactory {
|
||||
|
||||
private final Function<String, String> healthIndicatorNameFactory;
|
||||
|
||||
public CompositeHealthIndicatorFactory(
|
||||
Function<String, String> healthIndicatorNameFactory) {
|
||||
this.healthIndicatorNameFactory = healthIndicatorNameFactory;
|
||||
}
|
||||
|
||||
public CompositeHealthIndicatorFactory() {
|
||||
this(new HealthIndicatorNameFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link CompositeHealthIndicator} based on the specified health indicators.
|
||||
* @param healthAggregator the {@link HealthAggregator}
|
||||
* @param healthIndicators the {@link HealthIndicator} instances mapped by name
|
||||
* @return a {@link HealthIndicator} that delegates to the specified
|
||||
* {@code healthIndicators}.
|
||||
*/
|
||||
public CompositeHealthIndicator createHealthIndicator(
|
||||
HealthAggregator healthAggregator,
|
||||
Map<String, HealthIndicator> healthIndicators) {
|
||||
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
|
||||
Assert.notNull(healthIndicators, "HealthIndicators must not be null");
|
||||
CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(
|
||||
healthAggregator);
|
||||
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
|
||||
String name = this.healthIndicatorNameFactory.apply(entry.getKey());
|
||||
healthIndicator.addHealthIndicator(name, entry.getValue());
|
||||
}
|
||||
return healthIndicator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ReactiveHealthIndicator} that returns health indications from all registered
|
||||
* delegates. Provides an alternative {@link Health} for a delegate that reaches a
|
||||
* configurable timeout.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CompositeReactiveHealthIndicator implements ReactiveHealthIndicator {
|
||||
|
||||
private final Map<String, ReactiveHealthIndicator> indicators;
|
||||
|
||||
private final HealthAggregator healthAggregator;
|
||||
|
||||
private Long timeout;
|
||||
|
||||
private Health timeoutHealth;
|
||||
|
||||
private final Function<Mono<Health>, Mono<Health>> timeoutCompose;
|
||||
|
||||
public CompositeReactiveHealthIndicator(HealthAggregator healthAggregator) {
|
||||
this(healthAggregator, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
public CompositeReactiveHealthIndicator(HealthAggregator healthAggregator,
|
||||
Map<String, ReactiveHealthIndicator> indicators) {
|
||||
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
|
||||
Assert.notNull(indicators, "Indicators must not be null");
|
||||
this.indicators = new LinkedHashMap<>(indicators);
|
||||
this.healthAggregator = healthAggregator;
|
||||
this.timeoutCompose = (mono) -> this.timeout != null ? mono.timeout(
|
||||
Duration.ofMillis(this.timeout), Mono.just(this.timeoutHealth)) : mono;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link ReactiveHealthIndicator} with the specified name.
|
||||
* @param name the name of the health indicator
|
||||
* @param indicator the health indicator to add
|
||||
* @return this instance
|
||||
*/
|
||||
public CompositeReactiveHealthIndicator addHealthIndicator(String name,
|
||||
ReactiveHealthIndicator indicator) {
|
||||
this.indicators.put(name, indicator);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify an alternative timeout {@link Health} if a {@link HealthIndicator} failed
|
||||
* to reply after specified {@code timeout}.
|
||||
* @param timeout number of milliseconds to wait before using the
|
||||
* {@code timeoutHealth}
|
||||
* @param timeoutHealth the {@link Health} to use if an health indicator reached the
|
||||
* {@code timeout}
|
||||
* @return this instance
|
||||
*/
|
||||
public CompositeReactiveHealthIndicator timeoutStrategy(long timeout,
|
||||
Health timeoutHealth) {
|
||||
this.timeout = timeout;
|
||||
this.timeoutHealth = (timeoutHealth != null ? timeoutHealth
|
||||
: Health.unknown().build());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Health> health() {
|
||||
return Flux.fromIterable(this.indicators.entrySet())
|
||||
.flatMap((entry) -> Mono.zip(Mono.just(entry.getKey()),
|
||||
entry.getValue().health().compose(this.timeoutCompose)))
|
||||
.collectMap(Tuple2::getT1, Tuple2::getT2)
|
||||
.map(this.healthAggregator::aggregate);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Factory to create a {@link CompositeReactiveHealthIndicator}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class CompositeReactiveHealthIndicatorFactory {
|
||||
|
||||
private final Function<String, String> healthIndicatorNameFactory;
|
||||
|
||||
public CompositeReactiveHealthIndicatorFactory(
|
||||
Function<String, String> healthIndicatorNameFactory) {
|
||||
this.healthIndicatorNameFactory = healthIndicatorNameFactory;
|
||||
}
|
||||
|
||||
public CompositeReactiveHealthIndicatorFactory() {
|
||||
this(new HealthIndicatorNameFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link CompositeReactiveHealthIndicator} based on the specified health
|
||||
* indicators. Each {@link HealthIndicator} are wrapped to a
|
||||
* {@link HealthIndicatorReactiveAdapter}. If two instances share the same name, the
|
||||
* reactive variant takes precedence.
|
||||
* @param healthAggregator the {@link HealthAggregator}
|
||||
* @param reactiveHealthIndicators the {@link ReactiveHealthIndicator} instances
|
||||
* mapped by name
|
||||
* @param healthIndicators the {@link HealthIndicator} instances mapped by name if
|
||||
* any.
|
||||
* @return a {@link ReactiveHealthIndicator} that delegates to the specified
|
||||
* {@code reactiveHealthIndicators}.
|
||||
*/
|
||||
public CompositeReactiveHealthIndicator createReactiveHealthIndicator(
|
||||
HealthAggregator healthAggregator,
|
||||
Map<String, ReactiveHealthIndicator> reactiveHealthIndicators,
|
||||
Map<String, HealthIndicator> healthIndicators) {
|
||||
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
|
||||
Assert.notNull(reactiveHealthIndicators,
|
||||
"ReactiveHealthIndicators must not be null");
|
||||
CompositeReactiveHealthIndicator healthIndicator = new CompositeReactiveHealthIndicator(
|
||||
healthAggregator);
|
||||
merge(reactiveHealthIndicators, healthIndicators)
|
||||
.forEach((beanName, indicator) -> {
|
||||
String name = this.healthIndicatorNameFactory.apply(beanName);
|
||||
healthIndicator.addHealthIndicator(name, indicator);
|
||||
});
|
||||
return healthIndicator;
|
||||
}
|
||||
|
||||
private Map<String, ReactiveHealthIndicator> merge(
|
||||
Map<String, ReactiveHealthIndicator> reactiveHealthIndicators,
|
||||
Map<String, HealthIndicator> healthIndicators) {
|
||||
if (ObjectUtils.isEmpty(healthIndicators)) {
|
||||
return reactiveHealthIndicators;
|
||||
}
|
||||
Map<String, ReactiveHealthIndicator> allIndicators = new LinkedHashMap<>(
|
||||
reactiveHealthIndicators);
|
||||
healthIndicators.forEach((beanName, indicator) -> {
|
||||
String name = this.healthIndicatorNameFactory.apply(beanName);
|
||||
allIndicators.computeIfAbsent(name,
|
||||
(n) -> new HealthIndicatorReactiveAdapter(indicator));
|
||||
});
|
||||
return allIndicators;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Carries information about the health of a component or subsystem.
|
||||
* <p>
|
||||
* {@link Health} contains a {@link Status} to express the state of a component or
|
||||
* subsystem and some additional details to carry some contextual information.
|
||||
* <p>
|
||||
* {@link Health} instances can be created by using {@link Builder}'s fluent API. Typical
|
||||
* usage in a {@link HealthIndicator} would be:
|
||||
*
|
||||
* <pre class="code">
|
||||
* try {
|
||||
* // do some test to determine state of component
|
||||
* return new Health.Builder().up().withDetail("version", "1.1.2").build();
|
||||
* }
|
||||
* catch (Exception ex) {
|
||||
* return new Health.Builder().down(ex).build();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @author Phillip Webb
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public final class Health {
|
||||
|
||||
private final Status status;
|
||||
|
||||
private final Map<String, Object> details;
|
||||
|
||||
/**
|
||||
* Create a new {@link Health} instance with the specified status and details.
|
||||
* @param builder the Builder to use
|
||||
*/
|
||||
private Health(Builder builder) {
|
||||
Assert.notNull(builder, "Builder must not be null");
|
||||
this.status = builder.status;
|
||||
this.details = Collections.unmodifiableMap(builder.details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the status of the health.
|
||||
* @return the status (never {@code null})
|
||||
*/
|
||||
@JsonUnwrapped
|
||||
public Status getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the details of the health.
|
||||
* @return the details (or an empty map)
|
||||
*/
|
||||
public Map<String, Object> getDetails() {
|
||||
return this.details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj != null && obj instanceof Health) {
|
||||
Health other = (Health) obj;
|
||||
return this.status.equals(other.status) && this.details.equals(other.details);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = this.status.hashCode();
|
||||
return 13 * hashCode + this.details.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getStatus() + " " + getDetails();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} instance with an {@link Status#UNKNOWN} status.
|
||||
* @return a new {@link Builder} instance
|
||||
*/
|
||||
public static Builder unknown() {
|
||||
return status(Status.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} instance with an {@link Status#UP} status.
|
||||
* @return a new {@link Builder} instance
|
||||
*/
|
||||
public static Builder up() {
|
||||
return status(Status.UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} instance with an {@link Status#DOWN} status and the
|
||||
* specified exception details.
|
||||
* @param ex the exception
|
||||
* @return a new {@link Builder} instance
|
||||
*/
|
||||
public static Builder down(Exception ex) {
|
||||
return down().withException(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} instance with a {@link Status#DOWN} status.
|
||||
* @return a new {@link Builder} instance
|
||||
*/
|
||||
public static Builder down() {
|
||||
return status(Status.DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} instance with an {@link Status#OUT_OF_SERVICE} status.
|
||||
* @return a new {@link Builder} instance
|
||||
*/
|
||||
public static Builder outOfService() {
|
||||
return status(Status.OUT_OF_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} instance with a specific status code.
|
||||
* @param statusCode the status code
|
||||
* @return a new {@link Builder} instance
|
||||
*/
|
||||
public static Builder status(String statusCode) {
|
||||
return status(new Status(statusCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} instance with a specific {@link Status}.
|
||||
* @param status the status
|
||||
* @return a new {@link Builder} instance
|
||||
*/
|
||||
public static Builder status(Status status) {
|
||||
return new Builder(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating immutable {@link Health} instances.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private Status status;
|
||||
|
||||
private Map<String, Object> details;
|
||||
|
||||
/**
|
||||
* Create new Builder instance.
|
||||
*/
|
||||
public Builder() {
|
||||
this.status = Status.UNKNOWN;
|
||||
this.details = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Builder instance, setting status to given {@code status}.
|
||||
* @param status the {@link Status} to use
|
||||
*/
|
||||
public Builder(Status status) {
|
||||
Assert.notNull(status, "Status must not be null");
|
||||
this.status = status;
|
||||
this.details = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Builder instance, setting status to given {@code status} and details
|
||||
* to given {@code details}.
|
||||
* @param status the {@link Status} to use
|
||||
* @param details the details {@link Map} to use
|
||||
*/
|
||||
public Builder(Status status, Map<String, ?> details) {
|
||||
Assert.notNull(status, "Status must not be null");
|
||||
Assert.notNull(details, "Details must not be null");
|
||||
this.status = status;
|
||||
this.details = new LinkedHashMap<>(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record detail for given {@link Exception}.
|
||||
* @param ex the exception
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder withException(Throwable ex) {
|
||||
Assert.notNull(ex, "Exception must not be null");
|
||||
return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Record detail using given {@code key} and {@code value}.
|
||||
* @param key the detail key
|
||||
* @param value the detail value
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder withDetail(String key, Object value) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(value, "Value must not be null");
|
||||
this.details.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status to {@link Status#UNKNOWN} status.
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder unknown() {
|
||||
return status(Status.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status to {@link Status#UP} status.
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder up() {
|
||||
return status(Status.UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status to {@link Status#DOWN} and add details for given {@link Throwable}.
|
||||
* @param ex the exception
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder down(Throwable ex) {
|
||||
return down().withException(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status to {@link Status#DOWN}.
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder down() {
|
||||
return status(Status.DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status to {@link Status#OUT_OF_SERVICE}.
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder outOfService() {
|
||||
return status(Status.OUT_OF_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status to given {@code statusCode}.
|
||||
* @param statusCode the status code
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder status(String statusCode) {
|
||||
return status(new Status(statusCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set status to given {@link Status} instance.
|
||||
* @param status the status
|
||||
* @return this {@link Builder} instance
|
||||
*/
|
||||
public Builder status(Status status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Health} instance with the previously specified code and
|
||||
* details.
|
||||
* @return a new {@link Health} instance
|
||||
*/
|
||||
public Health build() {
|
||||
return new Health(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user