diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 48a866fda3e..508e125c14f 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -56,6 +56,11 @@ javax.servlet-api true + + org.apache.activemq + activemq-broker + true + org.hibernate hibernate-validator diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index c0c4f703fc1..52d46b19660 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -19,10 +19,11 @@ package org.springframework.boot.actuate.autoconfigure; import java.util.Collection; import java.util.Collections; import java.util.Map; - +import javax.jms.ConnectionFactory; import javax.sql.DataSource; import org.apache.solr.client.solrj.SolrServer; + import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; @@ -32,6 +33,7 @@ import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.JmsHealthIndicator; import org.springframework.boot.actuate.health.MailHealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator; import org.springframework.boot.actuate.health.OrderedHealthAggregator; @@ -49,6 +51,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadata; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProviders; +import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; @@ -74,7 +77,7 @@ import org.springframework.mail.javamail.JavaMailSenderImpl; @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, RedisAutoConfiguration.class, RabbitAutoConfiguration.class, SolrAutoConfiguration.class, - MailSenderAutoConfiguration.class }) + MailSenderAutoConfiguration.class, JmsAutoConfiguration.class}) @EnableConfigurationProperties({ HealthIndicatorAutoConfigurationProperties.class }) public class HealthIndicatorAutoConfiguration { @@ -316,4 +319,40 @@ public class HealthIndicatorAutoConfiguration { } } + @Configuration + @ConditionalOnBean(ConnectionFactory.class) + @ConditionalOnProperty(prefix = "management.health.jms", name = "enabled", matchIfMissing = true) + public static class JmsHealthIndicatorConfiguration { + + @Autowired + private HealthAggregator healthAggregator; + + @Autowired(required = false) + private Map connectionFactories; + + @Bean + @ConditionalOnMissingBean(name = "jmsHealthIndicator") + public HealthIndicator jmsHealthIndicator() { + if (this.connectionFactories.size() == 1) { + ConnectionFactory connectionFactory = this.connectionFactories.values() + .iterator().next(); + return createJmsHealthIndicator(connectionFactory); + } + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); + for (Map.Entry entry : this.connectionFactories + .entrySet()) { + String name = entry.getKey(); + ConnectionFactory connectionFactory = entry.getValue(); + composite.addHealthIndicator(name, createJmsHealthIndicator(connectionFactory)); + } + return composite; + } + + private JmsHealthIndicator createJmsHealthIndicator( + ConnectionFactory connectionFactory) { + return new JmsHealthIndicator(connectionFactory); + } + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/JmsHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/JmsHealthIndicator.java new file mode 100644 index 00000000000..5f310834230 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/JmsHealthIndicator.java @@ -0,0 +1,50 @@ +/* + * 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. + */ + +package org.springframework.boot.actuate.health; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; + +/** + * {@link HealthIndicator} for a JMS {@link ConnectionFactory}. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +public class JmsHealthIndicator extends AbstractHealthIndicator { + + private final ConnectionFactory connectionFactory; + + public JmsHealthIndicator(ConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + Connection conToClose = null; + try { + conToClose = this.connectionFactory.createConnection(); + builder.up().withDetail("provider", + conToClose.getMetaData().getJMSProviderName()); + } finally { + if (conToClose != null) { + conToClose.close(); + } + } + } + +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 9f4510dd870..c4a99c96466 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -34,6 +34,12 @@ "description": "Enable disk space health check.", "defaultValue": true }, + { + "name": "management.health.jms.enabled", + "type": "java.lang.Boolean", + "description": "Enable JMS health check.", + "defaultValue": true + }, { "name": "management.health.mongo.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java index c3428977050..f3a0ee60569 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java @@ -27,6 +27,7 @@ import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.JmsHealthIndicator; import org.springframework.boot.actuate.health.MailHealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator; import org.springframework.boot.actuate.health.RabbitHealthIndicator; @@ -38,6 +39,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration; +import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; @@ -56,6 +58,7 @@ import static org.junit.Assert.assertEquals; * Tests for {@link HealthIndicatorAutoConfiguration}. * * @author Christian Dupuis + * @author Stephane Nicoll */ public class HealthIndicatorAutoConfigurationTests { @@ -340,4 +343,37 @@ public class HealthIndicatorAutoConfigurationTests { .getClass()); } + @Test + public void jmsHealthIndicator() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "management.health.diskspace.enabled:false"); + this.context.register(ActiveMQAutoConfiguration.class, + ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertEquals(1, beans.size()); + assertEquals(JmsHealthIndicator.class, beans.values().iterator().next() + .getClass()); + } + + @Test + public void notJmsHealthIndicator() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "management.health.jms.enabled:false", + "management.health.diskspace.enabled:false"); + this.context.register(ActiveMQAutoConfiguration.class, + ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertEquals(1, beans.size()); + assertEquals(ApplicationHealthIndicator.class, beans.values().iterator().next() + .getClass()); + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/JmsHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/JmsHealthIndicatorTests.java new file mode 100644 index 00000000000..1fabdbfc0e9 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/JmsHealthIndicatorTests.java @@ -0,0 +1,81 @@ +/* + * 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. + */ + +package org.springframework.boot.actuate.health; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.ConnectionMetaData; +import javax.jms.JMSException; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link JmsHealthIndicator}. + * + * @author Stephane Nicoll + */ +public class JmsHealthIndicatorTests { + + @Test + public void jmsBrokerIsUp() throws JMSException { + ConnectionMetaData connectionMetaData = mock(ConnectionMetaData.class); + when(connectionMetaData.getJMSProviderName()).thenReturn("JMS test provider"); + Connection connection = mock(Connection.class); + when(connection.getMetaData()).thenReturn(connectionMetaData); + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + when(connectionFactory.createConnection()).thenReturn(connection); + + JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory); + Health health = indicator.health(); + assertEquals(Status.UP, health.getStatus()); + assertEquals("JMS test provider", health.getDetails().get("provider")); + verify(connection, times(1)).close(); + } + + @Test + public void jmsBrokerIsDown() throws JMSException { + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + when(connectionFactory.createConnection()).thenThrow(new JMSException("test", "123")); + JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory); + Health health = indicator.health(); + assertEquals(Status.DOWN, health.getStatus()); + assertEquals(null, health.getDetails().get("provider")); + } + + @Test + public void jmsBrokerCouldNotRetrieveProviderMetadata() throws JMSException { + ConnectionMetaData connectionMetaData = mock(ConnectionMetaData.class); + when(connectionMetaData.getJMSProviderName()).thenThrow(new JMSException("test", "123")); + Connection connection = mock(Connection.class); + when(connection.getMetaData()).thenReturn(connectionMetaData); + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + when(connectionFactory.createConnection()).thenReturn(connection); + + JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory); + Health health = indicator.health(); + assertEquals(Status.DOWN, health.getStatus()); + assertEquals(null, health.getDetails().get("provider")); + verify(connection, times(1)).close(); + } + +}