/* * 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. * 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.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; import org.springframework.beans.BeansException; 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; /** * Bulk start/stop {@link SmartLifecycle} in a particular role in phase order. * * @author Gary Russell * @author Artem Bilan * * @since 4.2 * */ public class SmartLifecycleRoleController implements ApplicationListener<AbstractLeaderEvent>, ApplicationContextAware { private static final Log logger = LogFactory.getLog(SmartLifecycleRoleController.class); private final MultiValueMap<String, SmartLifecycle> lifecycles = new LinkedMultiValueMap<String, SmartLifecycle>(); private final MultiValueMap<String, String> lazyLifecycles = new LinkedMultiValueMap<String, String>(); private ApplicationContext applicationContext; /** * Construct an instance with the provided lists of roles and lifecycles, which must be of equal length. * @param roles the roles. * @param lifecycles the lifecycles corresponding to the roles. */ public SmartLifecycleRoleController(List<String> roles, List<SmartLifecycle> lifecycles) { Assert.notNull(roles, "'roles' cannot be null"); Assert.notNull(lifecycles, "'lifecycles' cannot be null"); Assert.isTrue(roles.size() == lifecycles.size(), "'roles' and 'lifecycles' must be the same lenght"); Iterator<SmartLifecycle> iterator = lifecycles.iterator(); for (String role : roles) { SmartLifecycle lifecycle = iterator.next(); addLifecycleToRole(role, lifecycle); } } /** * Construct an instance with the provided map of roles/instances. * @param lifcycles the {@link MultiValueMap} of beans in roles. */ public SmartLifecycleRoleController(MultiValueMap<String, SmartLifecycle> lifcycles) { for (Entry<String, List<SmartLifecycle>> lifecyclesInRole : lifcycles.entrySet()) { String role = lifecyclesInRole.getKey(); for (SmartLifecycle lifecycle : lifecyclesInRole.getValue()) { addLifecycleToRole(role, lifecycle); } } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * Add a {@link SmartLifecycle} to the role. * @param role the role. * @param lifecycle the {@link SmartLifecycle}. */ public final void addLifecycleToRole(String role, SmartLifecycle lifecycle) { this.lifecycles.add(role, lifecycle); } /** * Add a {@link SmartLifecycle} bean to the role using its name. * @param role the role. * @param lifecycleBeanName the bean name of the {@link SmartLifecycle}. */ public void addLifecycleToRole(String role, String lifecycleBeanName) { Assert.state(this.applicationContext != null, "An application context is required to use this method"); this.lazyLifecycles.add(role, lifecycleBeanName); } /** * Add a {@link SmartLifecycle} beans to the role using their names. * @param role the role. * @param lifecycleBeanNames the bean names of the {@link SmartLifecycle}s. */ public void addLifecyclesToRole(String role, List<String> lifecycleBeanNames) { Assert.state(this.applicationContext != null, "An application context is required to use this method"); for (String lifecycleBeanName : lifecycleBeanNames) { this.lazyLifecycles.add(role, lifecycleBeanName); } } /** * Start all registered {@link SmartLifecycle}s in the role. * @param role the role. */ public void startLifecyclesInRole(String role) { if (this.lazyLifecycles.size() > 0) { addLazyLifecycles(); } List<SmartLifecycle> lifecycles = this.lifecycles.get(role); if (lifecycles != null) { lifecycles = new ArrayList<SmartLifecycle>(lifecycles); Collections.sort(lifecycles, (o1, o2) -> o1.getPhase() < o2.getPhase() ? -1 : o1.getPhase() > o2.getPhase() ? 1 : 0); if (logger.isDebugEnabled()) { logger.debug("Starting " + lifecycles + " in role " + role); } for (SmartLifecycle lifecycle : lifecycles) { try { lifecycle.start(); } catch (Exception e) { logger.error("Failed to start " + lifecycle + " in role " + role, e); } } } else { if (logger.isDebugEnabled()) { logger.debug("No components in role " + role + ". Nothing to start"); } } } /** * Stop all registered {@link SmartLifecycle}s in the role. * @param role the role. */ public void stopLifecyclesInRole(String role) { if (this.lazyLifecycles.size() > 0) { addLazyLifecycles(); } List<SmartLifecycle> lifecycles = this.lifecycles.get(role); if (lifecycles != null) { lifecycles = new ArrayList<SmartLifecycle>(lifecycles); Collections.sort(lifecycles, (o1, o2) -> o1.getPhase() < o2.getPhase() ? 1 : o1.getPhase() > o2.getPhase() ? -1 : 0); if (logger.isDebugEnabled()) { logger.debug("Stopping " + lifecycles + " in role " + role); } for (SmartLifecycle lifecycle : lifecycles) { try { lifecycle.stop(); } catch (Exception e) { logger.error("Failed to stop " + lifecycle + " in role " + role, e); } } } else { if (logger.isDebugEnabled()) { logger.debug("No components in role " + role + ". Nothing to stop"); } } } /** * Return a collection of the roles currently managed by this controller. * @return the roles. * @since 4.3.8 */ public Collection<String> 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<String, Boolean> 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<String, Boolean> 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<String, Boolean> 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<String, List<String>> entry : this.lazyLifecycles.entrySet()) { doAddLifecyclesToRole(entry.getKey(), entry.getValue()); } this.lazyLifecycles.clear(); } private void doAddLifecyclesToRole(String role, List<String> lifecycleBeanNames) { for (String lifecycleBeanName : lifecycleBeanNames) { try { SmartLifecycle lifecycle = this.applicationContext.getBean(lifecycleBeanName, SmartLifecycle.class); addLifecycleToRole(role, lifecycle); } catch (NoSuchBeanDefinitionException e) { logger.warn("Skipped; no such bean: " + lifecycleBeanName); } } } @Override public void onApplicationEvent(AbstractLeaderEvent event) { if (event instanceof OnGrantedEvent) { startLifecyclesInRole(event.getRole()); } else if (event instanceof OnRevokedEvent) { stopLifecyclesInRole(event.getRole()); } } }