Move code from spring-boot-actuator to spring-boot-jms

This commit is contained in:
Andy Wilkinson
2025-05-09 16:39:44 +01:00
committed by Phillip Webb
parent 8190edfe28
commit 0e489acfdb
7 changed files with 14 additions and 11 deletions

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2012-2025 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
*
* https://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.jms.actuate.health;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jakarta.jms.Connection;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
/**
* {@link HealthIndicator} for a JMS {@link ConnectionFactory}.
*
* @author Stephane Nicoll
* @since 4.0.0
*/
public class JmsHealthIndicator extends AbstractHealthIndicator {
private final Log logger = LogFactory.getLog(JmsHealthIndicator.class);
private final ConnectionFactory connectionFactory;
public JmsHealthIndicator(ConnectionFactory connectionFactory) {
super("JMS health check failed");
this.connectionFactory = connectionFactory;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
try (Connection connection = this.connectionFactory.createConnection()) {
new MonitoredConnection(connection).start();
builder.up().withDetail("provider", connection.getMetaData().getJMSProviderName());
}
}
private final class MonitoredConnection {
private final CountDownLatch latch = new CountDownLatch(1);
private final Connection connection;
MonitoredConnection(Connection connection) {
this.connection = connection;
}
void start() throws JMSException {
new Thread(() -> {
try {
if (!this.latch.await(5, TimeUnit.SECONDS)) {
JmsHealthIndicator.this.logger
.warn("Connection failed to start within 5 seconds and will be closed.");
closeConnection();
}
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}, "jms-health-indicator").start();
this.connection.start();
this.latch.countDown();
}
private void closeConnection() {
try {
this.connection.close();
}
catch (Exception ex) {
// Continue
}
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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
*
* https://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.
*/
/**
* Health integration for JMS.
*/
package org.springframework.boot.jms.actuate.health;

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2012-2025 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
*
* https://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.jms.actuate.health;
import jakarta.jms.Connection;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.ConnectionMetaData;
import jakarta.jms.JMSException;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JmsHealthIndicator}.
*
* @author Stephane Nicoll
*/
class JmsHealthIndicatorTests {
@Test
void jmsBrokerIsUp() throws JMSException {
ConnectionMetaData connectionMetaData = mock(ConnectionMetaData.class);
given(connectionMetaData.getJMSProviderName()).willReturn("JMS test provider");
Connection connection = mock(Connection.class);
given(connection.getMetaData()).willReturn(connectionMetaData);
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
given(connectionFactory.createConnection()).willReturn(connection);
JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory);
Health health = indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsEntry("provider", "JMS test provider");
then(connection).should().close();
}
@Test
void jmsBrokerIsDown() throws JMSException {
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
given(connectionFactory.createConnection()).willThrow(new JMSException("test", "123"));
JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory);
Health health = indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails()).doesNotContainKey("provider");
}
@Test
void jmsBrokerCouldNotRetrieveProviderMetadata() throws JMSException {
ConnectionMetaData connectionMetaData = mock(ConnectionMetaData.class);
given(connectionMetaData.getJMSProviderName()).willThrow(new JMSException("test", "123"));
Connection connection = mock(Connection.class);
given(connection.getMetaData()).willReturn(connectionMetaData);
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
given(connectionFactory.createConnection()).willReturn(connection);
JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory);
Health health = indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails()).doesNotContainKey("provider");
then(connection).should().close();
}
@Test
void jmsBrokerUsesFailover() throws JMSException {
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
ConnectionMetaData connectionMetaData = mock(ConnectionMetaData.class);
given(connectionMetaData.getJMSProviderName()).willReturn("JMS test provider");
Connection connection = mock(Connection.class);
given(connection.getMetaData()).willReturn(connectionMetaData);
willThrow(new JMSException("Could not start", "123")).given(connection).start();
given(connectionFactory.createConnection()).willReturn(connection);
JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory);
Health health = indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails()).doesNotContainKey("provider");
}
@Test
void whenConnectionStartIsUnresponsiveStatusIsDown() throws JMSException {
ConnectionMetaData connectionMetaData = mock(ConnectionMetaData.class);
given(connectionMetaData.getJMSProviderName()).willReturn("JMS test provider");
Connection connection = mock(Connection.class);
UnresponsiveStartAnswer unresponsiveStartAnswer = new UnresponsiveStartAnswer();
willAnswer(unresponsiveStartAnswer).given(connection).start();
willAnswer((invocation) -> {
unresponsiveStartAnswer.connectionClosed();
return null;
}).given(connection).close();
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
given(connectionFactory.createConnection()).willReturn(connection);
JmsHealthIndicator indicator = new JmsHealthIndicator(connectionFactory);
Health health = indicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat((String) health.getDetails().get("error")).contains("Connection closed");
}
private static final class UnresponsiveStartAnswer implements Answer<Void> {
private boolean connectionClosed;
private final Object monitor = new Object();
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
synchronized (this.monitor) {
while (!this.connectionClosed) {
this.monitor.wait();
}
}
throw new JMSException("Connection closed");
}
private void connectionClosed() {
synchronized (this.monitor) {
this.connectionClosed = true;
this.monitor.notifyAll();
}
}
}
}