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 c81900cc9df..4e08ed8729d 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 @@ -31,11 +31,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * {@link EnableAutoConfiguration Auto-configuration} to enable JMX export for * {@link Endpoint}s. * * @author Christian Dupuis + * @author Andy Wilkinson */ @Configuration @ConditionalOnExpression("${endpoints.jmx.enabled:true} && ${spring.jmx.enabled:true}") @@ -44,11 +47,14 @@ import org.springframework.util.StringUtils; public class EndpointMBeanExportAutoConfiguration { @Autowired - EndpointMBeanExportProperties properties = new EndpointMBeanExportProperties(); + private EndpointMBeanExportProperties properties = new EndpointMBeanExportProperties(); + + @Autowired(required = false) + private ObjectMapper objectMapper; @Bean public EndpointMBeanExporter endpointMBeanExporter(MBeanServer server) { - EndpointMBeanExporter mbeanExporter = new EndpointMBeanExporter(); + EndpointMBeanExporter mbeanExporter = new EndpointMBeanExporter(this.objectMapper); String domain = this.properties.getDomain(); if (StringUtils.hasText(domain)) { mbeanExporter.setDomain(domain); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java index 097c0b59139..9e769a25f9a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/DataEndpointMBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -20,19 +20,28 @@ import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedResource; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * Simple wrapper around {@link Endpoint} implementations that provide actuator data of * some sort. * * @author Christian Dupuis + * @author Andy Wilkinson */ @ManagedResource public class DataEndpointMBean extends EndpointMBean { + @Deprecated public DataEndpointMBean(String beanName, Endpoint endpoint) { super(beanName, endpoint); } + public DataEndpointMBean(String beanName, Endpoint endpoint, + ObjectMapper objectMapper) { + super(beanName, endpoint, objectMapper); + } + @ManagedAttribute(description = "Invoke the underlying endpoint") public Object getData() { return convert(getEndpoint().invoke()); 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 92b9df933d7..064b9f41643 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -31,18 +31,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; * Simple wrapper around {@link Endpoint} implementations to enable JMX export. * * @author Christian Dupuis + * @author Andy Wilkinson */ @ManagedResource public class EndpointMBean { private final Endpoint endpoint; - private final ObjectMapper mapper = new ObjectMapper(); + private final ObjectMapper mapper; + @Deprecated public EndpointMBean(String beanName, Endpoint endpoint) { + this(beanName, endpoint, new ObjectMapper()); + } + + public EndpointMBean(String beanName, Endpoint endpoint, ObjectMapper objectMapper) { Assert.notNull(beanName, "BeanName must not be null"); Assert.notNull(endpoint, "Endpoint must not be null"); + Assert.notNull(objectMapper, "ObjectMapper must not be null"); this.endpoint = endpoint; + this.mapper = objectMapper; } @ManagedAttribute(description = "Returns the class of the underlying endpoint") 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 cdcceca3af3..c602ec2d1b5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-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. @@ -49,12 +49,15 @@ import org.springframework.jmx.export.naming.SelfNaming; import org.springframework.jmx.support.ObjectNameManager; import org.springframework.util.ObjectUtils; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * {@link ApplicationListener} that registers all known {@link Endpoint}s with an * {@link MBeanServer} using the {@link MBeanExporter} located from the application * context. * * @author Christian Dupuis + * @author Andy Wilkinson */ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecycle, BeanFactoryAware, ApplicationContextAware { @@ -91,8 +94,14 @@ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecyc private Properties objectNameStaticProperties = new Properties(); + private final ObjectMapper objectMapper; + public EndpointMBeanExporter() { - super(); + this(null); + } + + public EndpointMBeanExporter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper; setAutodetect(false); setNamingStrategy(this.defaultNamingStrategy); setAssembler(this.assembler); @@ -168,9 +177,9 @@ public class EndpointMBeanExporter extends MBeanExporter implements SmartLifecyc protected EndpointMBean getEndpointMBean(String beanName, Endpoint endpoint) { if (endpoint instanceof ShutdownEndpoint) { - return new ShutdownEndpointMBean(beanName, endpoint); + return new ShutdownEndpointMBean(beanName, endpoint, this.objectMapper); } - return new DataEndpointMBean(beanName, endpoint); + return new DataEndpointMBean(beanName, endpoint, this.objectMapper); } @Override diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java index 02ba38761cd..854df90ba57 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/ShutdownEndpointMBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -21,18 +21,27 @@ import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * Special endpoint wrapper for {@link ShutdownEndpoint}. * * @author Christian Dupuis + * @author Andy Wilkinson */ @ManagedResource public class ShutdownEndpointMBean extends EndpointMBean { + @Deprecated public ShutdownEndpointMBean(String beanName, Endpoint endpoint) { super(beanName, endpoint); } + public ShutdownEndpointMBean(String beanName, Endpoint endpoint, + ObjectMapper mapper) { + super(beanName, endpoint, mapper); + } + @ManagedOperation(description = "Shutdown the ApplicationContext") public Object shutdown() { return convert(getEndpoint().invoke()); 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 88eec13e078..3b546bb51c6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -16,8 +16,11 @@ package org.springframework.boot.actuate.endpoint.jmx; +import java.text.SimpleDateFormat; import java.util.Collections; +import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; @@ -28,6 +31,7 @@ import javax.management.ObjectName; import org.junit.After; import org.junit.Test; import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.context.ApplicationContext; @@ -36,13 +40,19 @@ import org.springframework.jmx.export.MBeanExporter; import org.springframework.jmx.support.ObjectNameManager; import org.springframework.util.ObjectUtils; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; /** * Tests for {@link EndpointMBeanExporter} * * @author Christian Dupuis + * @author Andy Wilkinson */ public class EndpointMBeanExporterTests { @@ -176,6 +186,47 @@ public class EndpointMBeanExporterTests { parent.close(); } + @Test + public void jsonConversionWithDefaultObjectMapper() throws Exception { + this.context = new GenericApplicationContext(); + this.context.registerBeanDefinition("endpointMbeanExporter", + new RootBeanDefinition(EndpointMBeanExporter.class)); + this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition( + JsonConversionEndpoint.class)); + this.context.refresh(); + + MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); + Object response = mbeanExporter.getServer().invoke( + getObjectName("endpoint1", this.context), "getData", new Object[0], + new String[0]); + + assertThat(response, is(instanceOf(Map.class))); + assertThat(((Map) response).get("date"), is(instanceOf(Long.class))); + } + + @Test + public void jsonConversionWithCustomObjectMapper() throws Exception { + this.context = new GenericApplicationContext(); + ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + constructorArgs.addIndexedArgumentValue(0, objectMapper); + this.context + .registerBeanDefinition("endpointMbeanExporter", new RootBeanDefinition( + EndpointMBeanExporter.class, constructorArgs, null)); + this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition( + JsonConversionEndpoint.class)); + this.context.refresh(); + + MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class); + Object response = mbeanExporter.getServer().invoke( + getObjectName("endpoint1", this.context), "getData", new Object[0], + new String[0]); + + assertThat(response, is(instanceOf(Map.class))); + assertThat(((Map) response).get("date"), is(instanceOf(String.class))); + } + private ObjectName getObjectName(String beanKey, GenericApplicationContext context) throws MalformedObjectNameException { return getObjectName("org.springframework.boot", beanKey, false, context); @@ -209,4 +260,20 @@ public class EndpointMBeanExporterTests { } } + public static class JsonConversionEndpoint extends + AbstractEndpoint> { + + public JsonConversionEndpoint() { + super("json-conversion"); + } + + @Override + public Map invoke() { + Map result = new LinkedHashMap(); + result.put("date", new Date()); + return result; + } + + } + }