diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/SmartLifecycleRoleController.java b/spring-integration-core/src/main/java/org/springframework/integration/support/SmartLifecycleRoleController.java index cbb0b37e4f..2f67255fdb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/SmartLifecycleRoleController.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/SmartLifecycleRoleController.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-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. @@ -17,10 +17,14 @@ package org.springframework.integration.support; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -30,10 +34,12 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; +import org.springframework.context.Lifecycle; import org.springframework.context.SmartLifecycle; import org.springframework.integration.leader.event.AbstractLeaderEvent; import org.springframework.integration.leader.event.OnGrantedEvent; import org.springframework.integration.leader.event.OnRevokedEvent; +import org.springframework.integration.support.context.NamedComponent; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -42,6 +48,8 @@ import org.springframework.util.MultiValueMap; * Bulk start/stop {@link SmartLifecycle} in a particular role in phase order. * * @author Gary Russell + * @author Artem Bilan + * * @since 4.2 * */ @@ -135,7 +143,7 @@ public class SmartLifecycleRoleController implements ApplicationListener o1.getPhase() < o2.getPhase() ? -1 : o1.getPhase() > o2.getPhase() ? 1 : 0); if (logger.isDebugEnabled()) { - logger.debug("Zookeeper leadership granted: Starting: " + lifecycles); + logger.debug("Starting " + lifecycles + " in role " + role); } for (SmartLifecycle lifecycle : lifecycles) { try { @@ -148,7 +156,7 @@ public class SmartLifecycleRoleController implements ApplicationListener o1.getPhase() < o2.getPhase() ? 1 : o1.getPhase() > o2.getPhase() ? -1 : 0); if (logger.isDebugEnabled()) { - logger.debug("Zookeeper leadership revoked: Stopping: " + lifecycles); + logger.debug("Stopping " + lifecycles + " in role " + role); } for (SmartLifecycle lifecycle : lifecycles) { try { @@ -180,12 +188,71 @@ public class SmartLifecycleRoleController implements ApplicationListener getRoles() { + if (this.lazyLifecycles.size() > 0) { + addLazyLifecycles(); + } + return new ArrayList<>(this.lifecycles.keySet()); + } + + /** + * Return true if all endpoints in the role are running. + * @param role the role. + * @return true if at least one endpoint in the role, and all are running. + * @since 4.3.8 + */ + public boolean allEndpointsRunning(String role) { + Map status = getEndpointsRunningStatus(role); + return !status.isEmpty() && status.values().stream().allMatch(b -> b); + } + + /** + * Return true if none of the endpoints in the role are running or if + * there are no endpoints in the role. + * @param role the role. + * @return true if there are no endpoints or none are running. + * @since 4.3.8 + */ + public boolean noEndpointsRunning(String role) { + Map status = getEndpointsRunningStatus(role); + return status.isEmpty() || status.values().stream().noneMatch(b -> b); + } + + /** + * Return the running status of each endpoint in the role. + * @param role the role. + * @return A map of component names : running status + * @since 4.3.8 + */ + public Map getEndpointsRunningStatus(String role) { + if (this.lazyLifecycles.size() > 0) { + addLazyLifecycles(); + } + if (!this.lifecycles.containsKey(role)) { + return Collections.emptyMap(); + } + + AtomicInteger index = new AtomicInteger(); + return this.lifecycles.get(role) + .stream() + .collect(Collectors.toMap(e -> + (e instanceof NamedComponent) + ? ((NamedComponent) e).getComponentName() + : (e.getClass().getSimpleName() + "#" + index.getAndIncrement()), + Lifecycle::isRunning)); + } + + private synchronized void addLazyLifecycles() { for (Entry> entry : this.lazyLifecycles.entrySet()) { doAddLifecyclesToRole(entry.getKey(), entry.getValue()); } @@ -199,7 +266,7 @@ public class SmartLifecycleRoleController implements ApplicationListener state = this.roleController.getEndpointsRunningStatus("foo"); + assertThat(state.get("annotationTestService.handle.serviceActivator"), equalTo(Boolean.FALSE)); + assertThat(state.get("enableIntegrationTests.ContextConfiguration2.sendAsyncHandler.serviceActivator"), + equalTo(Boolean.TRUE)); + this.roleController.startLifecyclesInRole("foo"); + assertTrue(this.roleController.allEndpointsRunning("foo")); this.roleController.stopLifecyclesInRole("foo"); + assertFalse(this.roleController.allEndpointsRunning("foo")); + assertTrue(this.roleController.noEndpointsRunning("foo")); + @SuppressWarnings("unchecked") MultiValueMap lifecycles = TestUtils.getPropertyValue(this.roleController, "lifecycles", MultiValueMap.class); diff --git a/src/reference/asciidoc/endpoint.adoc b/src/reference/asciidoc/endpoint.adoc index 14bc61ff23..1e384fe57b 100644 --- a/src/reference/asciidoc/endpoint.adoc +++ b/src/reference/asciidoc/endpoint.adoc @@ -633,6 +633,27 @@ start/stop its configured `SmartLifecycle` objects when leadership is granted/re IMPORTANT: When using leadership election to start/stop components, it is important to set the `auto-startup` XML attribute (`autoStartup` bean property) to `false` so the application context does not start the components during context intialization. +Starting with _version 4.3.8, the `SmartLifecycleRoleController` provides several status methods: + +[source, java] +---- +public Collection getRoles() <1> + +public boolean allEndpointsRunning(String role) <2> + +public boolean noEndpointsRunning(String role) <3> + +public Map getEndpointsRunningStatus(String role) <4> +---- + +<1> Returns a list of the roles being managed. + +<2> Returns true if all endpoints in the role are running. + +<3> Returns true if none of the endpoints in the role are running. + +<4> Returns a map of `component name : running status` - the component name is usually the bean name. + [[leadership-event-handling]] === Leadership Event Handling diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 6dcb4fb97e..62abbf0b60 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -33,6 +33,9 @@ See <> for more information. All the request-reply endpoints (based on `AbstractReplyProducingMessageHandler`) can now start transaction and, therefore, make the whole downstream flow transactional. See <> for more information. +The `SmartLifecycleRoleController` now provides methods to obtain status of endpoints in roles. +See <> for more information. + ==== JMS Changes Previously, Spring Integration JMS XML configuration used a default bean name `connectionFactory` for the JMS Connection Factory, allowing the property to be omitted from component definitions.