From 5b40eb48e08065d12bf0c3a1b727b29439171d05 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 2 Jan 2017 19:28:49 -0800 Subject: [PATCH] Improve MBean without backing Endpoint support Improve support for MBeans without a backing endpoint by introducing a `JmxEndpoint` interface. The `JmxEndpoint` is intentionally similar in design to the `MvcEndpoint` from the `mvc` package and allows for completely custom JMX beans that are not backed by any real actuator `Endpoint`. The `AuditEventsMBean` has been refactored to use the new interface and has been renamed to `AuditEventsJmxEndpoint`. See gh-6579 --- .../EndpointMBeanExportAutoConfiguration.java | 6 +- ...intMBean.java => AbstractJmxEndpoint.java} | 52 ++++---- ...MBean.java => AuditEventsJmxEndpoint.java} | 31 ++--- .../actuate/endpoint/jmx/DataConverter.java | 62 +++++++++ .../actuate/endpoint/jmx/EndpointMBean.java | 39 +++++- .../endpoint/jmx/EndpointMBeanExporter.java | 124 ++++++++++++------ .../endpoint/jmx/EndpointMBeanSupport.java | 70 ---------- .../actuate/endpoint/jmx/JmxEndpoint.java | 54 ++++++++ ...mentWebSecurityAutoConfigurationTests.java | 4 +- .../jmx/EndpointMBeanExporterTests.java | 6 +- .../appendix-application-properties.adoc | 1 - 11 files changed, 286 insertions(+), 163 deletions(-) rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/{AbstractEndpointMBean.java => AbstractJmxEndpoint.java} (63%) rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/{AuditEventsMBean.java => AuditEventsJmxEndpoint.java} (77%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanSupport.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java index 69957b8dcae..9f4a72eb988 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointMBeanExportAutoConfiguration.java @@ -25,7 +25,7 @@ import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.autoconfigure.EndpointMBeanExportAutoConfiguration.JmxEnabledCondition; import org.springframework.boot.actuate.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.actuate.endpoint.jmx.AuditEventsMBean; +import org.springframework.boot.actuate.endpoint.jmx.AuditEventsJmxEndpoint; import org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -90,9 +90,9 @@ public class EndpointMBeanExportAutoConfiguration { @Bean @ConditionalOnBean(AuditEventRepository.class) @ConditionalOnEnabledEndpoint("auditevents") - public AuditEventsMBean abstractEndpointMBean( + public AuditEventsJmxEndpoint abstractEndpointMBean( AuditEventRepository auditEventRepository) { - return new AuditEventsMBean(this.objectMapper, auditEventRepository); + return new AuditEventsJmxEndpoint(this.objectMapper, auditEventRepository); } /** diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java similarity index 63% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractEndpointMBean.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java index a47b323c059..ce43333f13c 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractEndpointMBean.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AbstractJmxEndpoint.java @@ -22,16 +22,21 @@ import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.EndpointProperties; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.util.ObjectUtils; /** - * Abstract base class for JMX endpoint implementations without a backing + * Abstract base class for {@link JmxEndpoint} implementations without a backing * {@link Endpoint}. * * @author Vedran Pavic + * @author Phillip Webb * @since 1.5.0 */ -public abstract class AbstractEndpointMBean extends EndpointMBeanSupport - implements EnvironmentAware { +@ManagedResource +public abstract class AbstractJmxEndpoint implements JmxEndpoint, EnvironmentAware { + + private final DataConverter dataConverter; private Environment environment; @@ -40,23 +45,8 @@ public abstract class AbstractEndpointMBean extends EndpointMBeanSupport */ private Boolean enabled; - /** - * Mark if the endpoint exposes sensitive information. - */ - private Boolean sensitive; - - private final boolean sensitiveDefault; - - public AbstractEndpointMBean(ObjectMapper objectMapper, boolean sensitive) { - super(objectMapper); - this.sensitiveDefault = sensitive; - } - - public AbstractEndpointMBean(ObjectMapper objectMapper, boolean sensitive, - boolean enabled) { - super(objectMapper); - this.sensitiveDefault = sensitive; - this.enabled = enabled; + public AbstractJmxEndpoint(ObjectMapper objectMapper) { + this.dataConverter = new DataConverter(objectMapper); } @Override @@ -68,6 +58,7 @@ public abstract class AbstractEndpointMBean extends EndpointMBeanSupport return this.environment; } + @Override public boolean isEnabled() { return EndpointProperties.isEnabled(this.environment, this.enabled); } @@ -77,18 +68,23 @@ public abstract class AbstractEndpointMBean extends EndpointMBeanSupport } @Override - public boolean isSensitive() { - return EndpointProperties.isSensitive(this.environment, this.sensitive, - this.sensitiveDefault); - } - - public void setSensitive(Boolean sensitive) { - this.sensitive = sensitive; + public String getIdentity() { + return ObjectUtils.getIdentityHexString(this); } @Override - public String getEndpointClass() { + @SuppressWarnings("rawtypes") + public Class getEndpointType() { return null; } + /** + * Convert the given data into JSON. + * @param data the source data + * @return the JSON representation + */ + protected Object convert(Object data) { + return this.dataConverter.convert(data); + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java similarity index 77% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsMBean.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java index ed878d7d48b..2c1186d8dfe 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsMBean.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/AuditEventsJmxEndpoint.java @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.endpoint.jmx; -import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -28,57 +27,55 @@ import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.util.Assert; /** - * Special JMX endpoint wrapper for {@link AuditEventRepository}. + * {@link JmxEndpoint} for {@link AuditEventRepository}. * * @author Vedran Pavic * @since 1.5.0 */ -@ManagedResource @ConfigurationProperties(prefix = "endpoints.auditevents") -public class AuditEventsMBean extends AbstractEndpointMBean { +public class AuditEventsJmxEndpoint extends AbstractJmxEndpoint { - private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; private final AuditEventRepository auditEventRepository; - public AuditEventsMBean(ObjectMapper objectMapper, + public AuditEventsJmxEndpoint(ObjectMapper objectMapper, AuditEventRepository auditEventRepository) { - super(objectMapper, true); + super(objectMapper); Assert.notNull(auditEventRepository, "AuditEventRepository must not be null"); this.auditEventRepository = auditEventRepository; } @ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria") public Object getData(String dateAfter) { - List auditEvents = this.auditEventRepository.find( - parseDate(dateAfter)); + List auditEvents = this.auditEventRepository + .find(parseDate(dateAfter)); return convert(auditEvents); } @ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria") public Object getData(String dateAfter, String principal) { - List auditEvents = this.auditEventRepository.find( - principal, parseDate(dateAfter)); + List auditEvents = this.auditEventRepository.find(principal, + parseDate(dateAfter)); return convert(auditEvents); } @ManagedOperation(description = "Retrieves a list of audit events meeting the given criteria") public Object getData(String principal, String dateAfter, String type) { - List auditEvents = this.auditEventRepository.find( - principal, parseDate(dateAfter), type); + List auditEvents = this.auditEventRepository.find(principal, + parseDate(dateAfter), type); return convert(auditEvents); } private Date parseDate(String date) { try { - return dateFormat.parse(date); + return new SimpleDateFormat(DATE_FORMAT).parse(date); } - catch (ParseException e) { - throw new IllegalArgumentException(e); + catch (ParseException ex) { + throw new IllegalArgumentException(ex); } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java new file mode 100644 index 00000000000..d37dc3ffcb9 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataConverter.java @@ -0,0 +1,62 @@ +/* + * 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.endpoint.jmx; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Internal converter that uses an {@link ObjectMapper} to convert to JSON. + * + * @author Christian Dupuis + * @author Andy Wilkinson + * @author Phillip Webb + */ +class DataConverter { + + private final ObjectMapper objectMapper; + + private final JavaType listObject; + + private final JavaType mapStringObject; + + DataConverter(ObjectMapper objectMapper) { + this.objectMapper = (objectMapper == null ? new ObjectMapper() : objectMapper); + this.listObject = this.objectMapper.getTypeFactory() + .constructParametricType(List.class, Object.class); + this.mapStringObject = this.objectMapper.getTypeFactory() + .constructParametricType(Map.class, String.class, Object.class); + + } + + public Object convert(Object data) { + if (data == null) { + return null; + } + if (data instanceof String) { + return data; + } + if (data.getClass().isArray() || data instanceof List) { + return this.objectMapper.convertValue(data, this.listObject); + } + return this.objectMapper.convertValue(data, this.mapStringObject); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java index a98160b0cba..a04e131fa3f 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java @@ -23,16 +23,22 @@ import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; /** - * Simple wrapper around {@link Endpoint} implementations to enable JMX export. + * Base for adapters that convert {@link Endpoint} implementations to {@link JmxEndpoint}. * * @author Christian Dupuis * @author Andy Wilkinson * @author Vedran Pavic + * @author Phillip Webb + * @see JmxEndpoint + * @see DataEndpointMBean */ @ManagedResource -public class EndpointMBean extends EndpointMBeanSupport { +public abstract class EndpointMBean implements JmxEndpoint { + + private final DataConverter dataConverter; private final Endpoint endpoint; @@ -44,7 +50,7 @@ public class EndpointMBean extends EndpointMBeanSupport { */ public EndpointMBean(String beanName, Endpoint endpoint, ObjectMapper objectMapper) { - super(objectMapper); + this.dataConverter = new DataConverter(objectMapper); Assert.notNull(beanName, "BeanName must not be null"); Assert.notNull(endpoint, "Endpoint must not be null"); this.endpoint = endpoint; @@ -52,7 +58,12 @@ public class EndpointMBean extends EndpointMBeanSupport { @ManagedAttribute(description = "Returns the class of the underlying endpoint") public String getEndpointClass() { - return ClassUtils.getQualifiedName(this.endpoint.getClass()); + return ClassUtils.getQualifiedName(getEndpointType()); + } + + @Override + public boolean isEnabled() { + return this.endpoint.isEnabled(); } @ManagedAttribute(description = "Indicates whether the underlying endpoint exposes sensitive information") @@ -60,8 +71,28 @@ public class EndpointMBean extends EndpointMBeanSupport { return this.endpoint.isSensitive(); } + @Override + public String getIdentity() { + return ObjectUtils.getIdentityHexString(getEndpoint()); + } + + @Override + @SuppressWarnings("rawtypes") + public Class getEndpointType() { + return getEndpoint().getClass(); + } + public Endpoint getEndpoint() { return this.endpoint; } + /** + * Convert the given data into JSON. + * @param data the source data + * @return the JSON representation + */ + protected Object convert(Object data) { + return this.dataConverter.convert(data); + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java index a1416ae1ac6..df3e4db7f63 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporter.java @@ -39,7 +39,6 @@ import org.springframework.boot.actuate.endpoint.LoggersEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationListener; import org.springframework.context.SmartLifecycle; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.jmx.export.MBeanExportException; @@ -53,7 +52,7 @@ import org.springframework.jmx.support.ObjectNameManager; import org.springframework.util.ObjectUtils; /** - * {@link ApplicationListener} that registers all known {@link Endpoint}s with an + * {@link SmartLifecycle} bean that registers all known {@link Endpoint}s with an * {@link MBeanServer} using the {@link MBeanExporter} located from the application * context. * @@ -79,7 +78,7 @@ public class EndpointMBeanExporter extends MBeanExporter private final MetadataNamingStrategy defaultNamingStrategy = new MetadataNamingStrategy( this.attributeSource); - private final Set> registeredEndpoints = new HashSet>(); + private final Set> registeredEndpoints = new HashSet>(); private volatile boolean autoStartup = true; @@ -156,39 +155,88 @@ public class EndpointMBeanExporter extends MBeanExporter locateAndRegisterEndpoints(); } - @SuppressWarnings({ "rawtypes" }) protected void locateAndRegisterEndpoints() { - Map endpoints = this.beanFactory.getBeansOfType(Endpoint.class); - for (Map.Entry endpointEntry : endpoints.entrySet()) { - if (!this.registeredEndpoints.contains(endpointEntry.getValue()) - && endpointEntry.getValue().isEnabled()) { - registerEndpoint(endpointEntry.getKey(), endpointEntry.getValue()); - this.registeredEndpoints.add(endpointEntry.getValue()); + registerJmxEndpoints(this.beanFactory.getBeansOfType(JmxEndpoint.class)); + registerEndpoints(this.beanFactory.getBeansOfType(Endpoint.class)); + } + + private void registerJmxEndpoints(Map endpoints) { + for (Map.Entry entry : endpoints.entrySet()) { + String name = entry.getKey(); + JmxEndpoint endpoint = entry.getValue(); + Class type = (endpoint.getEndpointType() != null + ? endpoint.getEndpointType() : endpoint.getClass()); + if (!this.registeredEndpoints.contains(type) && endpoint.isEnabled()) { + try { + registerBeanNameOrInstance(endpoint, name); + } + catch (MBeanExportException ex) { + logger.error("Could not register JmxEndpoint [" + name + "]", ex); + } + this.registeredEndpoints.add(type); } } } - protected void registerEndpoint(String beanName, Endpoint endpoint) { - @SuppressWarnings("rawtypes") - Class type = endpoint.getClass(); - if (AnnotationUtils.findAnnotation(type, ManagedResource.class) != null) { - // Already managed - return; + @SuppressWarnings("rawtypes") + private void registerEndpoints(Map endpoints) { + for (Map.Entry entry : endpoints.entrySet()) { + String name = entry.getKey(); + Endpoint endpoint = entry.getValue(); + Class type = endpoint.getClass(); + if (!this.registeredEndpoints.contains(type) && endpoint.isEnabled()) { + registerEndpoint(name, endpoint); + this.registeredEndpoints.add(type); + } } - if (type.isMemberClass() - && AnnotationUtils.findAnnotation(type.getEnclosingClass(), - ManagedResource.class) != null) { - // Nested class with @ManagedResource in parent + } + + /** + * Register a regular {@link Endpoint} with the {@link MBeanServer}. + * @param beanName the bean name + * @param endpoint the endpoint to register + * @deprecated as of 1.5 in favor of direct {@link JmxEndpoint} registration or + * {@link #adaptEndpoint(String, Endpoint)} + */ + @Deprecated + protected void registerEndpoint(String beanName, Endpoint endpoint) { + Class type = endpoint.getClass(); + if (isAnnotatedWithManagedResource(type) || (type.isMemberClass() + && isAnnotatedWithManagedResource(type.getEnclosingClass()))) { + // Endpoint is directly managed return; } + JmxEndpoint jmxEndpoint = adaptEndpoint(beanName, endpoint); try { - registerBeanNameOrInstance(getEndpointMBean(beanName, endpoint), beanName); + registerBeanNameOrInstance(jmxEndpoint, beanName); } catch (MBeanExportException ex) { logger.error("Could not register MBean for endpoint [" + beanName + "]", ex); } } + private boolean isAnnotatedWithManagedResource(Class type) { + return AnnotationUtils.findAnnotation(type, ManagedResource.class) != null; + } + + /** + * Adapt the given {@link Endpoint} to a {@link JmxEndpoint}. + * @param beanName the bean name + * @param endpoint the endpoint to adapt + * @return an adapted endpoint + */ + protected JmxEndpoint adaptEndpoint(String beanName, Endpoint endpoint) { + return getEndpointMBean(beanName, endpoint); + } + + /** + * Get a {@link EndpointMBean} for the specified {@link Endpoint}. + * @param beanName the bean name + * @param endpoint the endpoint + * @return an {@link EndpointMBean} + * @deprecated as of 1.5 in favor of {@link #adaptEndpoint(String, Endpoint)} + */ + @Deprecated protected EndpointMBean getEndpointMBean(String beanName, Endpoint endpoint) { if (endpoint instanceof ShutdownEndpoint) { return new ShutdownEndpointMBean(beanName, endpoint, this.objectMapper); @@ -205,27 +253,29 @@ public class EndpointMBeanExporter extends MBeanExporter if (bean instanceof SelfNaming) { return ((SelfNaming) bean).getObjectName(); } - if (bean instanceof EndpointMBean) { - StringBuilder builder = new StringBuilder(); - builder.append(this.domain); - builder.append(":type=Endpoint"); - builder.append(",name=" + beanKey); - if (parentContextContainsSameBean(this.applicationContext, beanKey)) { - builder.append(",context=" - + ObjectUtils.getIdentityHexString(this.applicationContext)); - } - if (this.ensureUniqueRuntimeObjectNames) { - builder.append(",identity=" + ObjectUtils - .getIdentityHexString(((EndpointMBean) bean).getEndpoint())); - } - builder.append(getStaticNames()); - return ObjectNameManager.getInstance(builder.toString()); + return getObjectName((EndpointMBean) bean, beanKey); } - return this.defaultNamingStrategy.getObjectName(bean, beanKey); } + private ObjectName getObjectName(JmxEndpoint jmxEndpoint, String beanKey) + throws MalformedObjectNameException { + StringBuilder builder = new StringBuilder(); + builder.append(this.domain); + builder.append(":type=Endpoint"); + builder.append(",name=" + beanKey); + if (parentContextContainsSameBean(this.applicationContext, beanKey)) { + builder.append(",context=" + + ObjectUtils.getIdentityHexString(this.applicationContext)); + } + if (this.ensureUniqueRuntimeObjectNames) { + builder.append(",identity=" + jmxEndpoint.getIdentity()); + } + builder.append(getStaticNames()); + return ObjectNameManager.getInstance(builder.toString()); + } + private boolean parentContextContainsSameBean(ApplicationContext applicationContext, String beanKey) { if (applicationContext.getParent() != null) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanSupport.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanSupport.java deleted file mode 100644 index a755c650417..00000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanSupport.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.endpoint.jmx; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.util.Assert; - -/** - * Abstract base class for JMX endpoint implementations. - * - * @author Vedran Pavic - * @since 1.5.0 - */ -public abstract class EndpointMBeanSupport { - - private final ObjectMapper mapper; - - private final JavaType listObject; - - private final JavaType mapStringObject; - - public EndpointMBeanSupport(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "ObjectMapper must not be null"); - this.mapper = objectMapper; - this.listObject = objectMapper.getTypeFactory() - .constructParametricType(List.class, Object.class); - this.mapStringObject = objectMapper.getTypeFactory() - .constructParametricType(Map.class, String.class, Object.class); - } - - @ManagedAttribute(description = "Indicates whether the underlying endpoint exposes sensitive information") - public abstract boolean isSensitive(); - - @ManagedAttribute(description = "Returns the class of the underlying endpoint") - public abstract String getEndpointClass(); - - protected Object convert(Object result) { - if (result == null) { - return null; - } - if (result instanceof String) { - return result; - } - if (result.getClass().isArray() || result instanceof List) { - return this.mapper.convertValue(result, this.listObject); - } - return this.mapper.convertValue(result, this.mapStringObject); - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java new file mode 100644 index 00000000000..70926afa6e5 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpoint.java @@ -0,0 +1,54 @@ +/* + * 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.endpoint.jmx; + +import org.springframework.boot.actuate.endpoint.Endpoint; + +/** + * A strategy for the JMX layer on top of an {@link Endpoint}. Implementations are allowed + * to use {@code @ManagedAttribute} and the full Spring JMX machinery. Implementations may + * be backed by an actual {@link Endpoint} or may be specifically designed for JMX only. + * + * @author Phillip Webb + * @since 1.5.0 + * @see EndpointMBean + * @see AbstractJmxEndpoint + */ +public interface JmxEndpoint { + + /** + * Return if the JMX endpoint is enabled. + * @return if the endpoint is enabled + */ + boolean isEnabled(); + + /** + * Return the MBean identity for this endpoint. + * @return the MBean identity. + */ + String getIdentity(); + + /** + * Return the type of {@link Endpoint} exposed, or {@code null} if this + * {@link JmxEndpoint} exposes information that cannot be represented as a traditional + * {@link Endpoint}. + * @return the endpoint type + */ + @SuppressWarnings("rawtypes") + Class getEndpointType(); + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfigurationTests.java index a4477c6926c..164f604437c 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfigurationTests.java @@ -202,8 +202,8 @@ public class ManagementWebSecurityAutoConfigurationTests { HttpMessageConvertersAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, - WebMvcAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, AuditAutoConfiguration.class); + WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, + AuditAutoConfiguration.class); this.context.refresh(); Filter filter = this.context.getBean("springSecurityFilterChain", Filter.class); diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java index efc37364e8e..6514739d58e 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBeanExporterTests.java @@ -128,7 +128,7 @@ public class EndpointMBeanExporterTests { this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(TestEndpoint.class)); this.context.registerBeanDefinition("endpoint2", - new RootBeanDefinition(TestEndpoint.class)); + new RootBeanDefinition(TestEndpoint2.class)); this.context.refresh(); MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); assertThat(mbeanExporter.getServer() @@ -329,6 +329,10 @@ public class EndpointMBeanExporterTests { } + public static class TestEndpoint2 extends TestEndpoint { + + } + public static class JsonMapConversionEndpoint extends AbstractEndpoint> { diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 570ebf7ec09..f87de388671 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -964,7 +964,6 @@ content into your application; rather pick only the properties that you need. endpoints.auditevents.enabled= # Enable the endpoint. endpoints.auditevents.id= # Endpoint identifier. endpoints.auditevents.path= # Endpoint path. - endpoints.auditevents.sensitive= # Mark if the endpoint exposes sensitive information. endpoints.autoconfig.enabled= # Enable the endpoint. endpoints.autoconfig.id= # Endpoint identifier. endpoints.autoconfig.path= # Endpoint path.