INT-3617: ZK Leader Event Processing

JIRA: https://jira.spring.io/browse/INT-3617

Start/stop `SmartLifecyle` beans with leader election/revocation.

ZK Namespace Support

Add Annotation Support

INT-3617: Polishing

Add `SmartLifecycle` to listener parser.

INT-3617: Polishing; PR Comments

Revert SmartLifecycleRoleController

Remove reflection.

Remove Dependence on spring-cloud-cluster

Temporarily move the relevant classes here.

Polishing; PR Comments and Fix Test

Test was incorrectly stopping the LeaderInitiator before it was elected.
Set auto-startup="false" in the i-c-a so we wait for its start before
stopping the LeaderInitiator.

Fix JavaDocs
This commit is contained in:
Gary Russell
2015-06-18 16:33:35 -04:00
committed by Artem Bilan
parent 9cc0652b61
commit 814698fbb9
42 changed files with 1829 additions and 12 deletions

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-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.integration.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotate endpoints to assign them to a role. Such endpoints can be started/stopped as
* a group. See {@code SmartLifecycleRoleController}.
*
* @author Gary Russell
* @since 4.2
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Role {
/**
* @return the role for this endpoint. See {@code SmartLifecycleRoleController}.
*/
String value() default "";
}

View File

@@ -20,7 +20,11 @@ import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.context.Lifecycle;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.support.SmartLifecycleRoleController;
/**
* Shared utility methods for Integration configuration.
@@ -51,6 +55,17 @@ public final class IntegrationConfigUtils {
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
public static void registerRoleControllerDefinitionIfNecessary(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(
IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER)) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SmartLifecycleRoleController.class);
builder.addConstructorArgValue(new ManagedList<String>());
builder.addConstructorArgValue(new ManagedList<Lifecycle>());
registry.registerBeanDefinition(
IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER, builder.getBeanDefinition());
}
}
private IntegrationConfigUtils() {
}

View File

@@ -23,6 +23,9 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -49,9 +52,6 @@ import org.springframework.integration.support.converter.DefaultDatatypeChannelM
import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.util.ClassUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* {@link ImportBeanDefinitionRegistrar} implementation that configures integration infrastructure.
*
@@ -102,6 +102,7 @@ public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, Bean
this.registerMessagingAnnotationPostProcessors(importingClassMetadata, registry);
}
this.registerMessageBuilderFactory(registry);
IntegrationConfigUtils.registerRoleControllerDefinitionIfNecessary(registry);
}
/**

View File

@@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
@@ -35,6 +36,8 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationEvent;
@@ -49,15 +52,20 @@ import org.springframework.integration.annotation.BridgeFrom;
import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.annotation.Filter;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.Role;
import org.springframework.integration.annotation.Router;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Splitter;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.support.SmartLifecycleRoleController;
import org.springframework.integration.util.MessagingAnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -71,7 +79,8 @@ import org.springframework.util.StringUtils;
* @author Gary Russell
*/
public class MessagingAnnotationPostProcessor implements BeanPostProcessor, BeanFactoryAware,
InitializingBean, Lifecycle, ApplicationListener<ApplicationEvent>, EnvironmentAware {
InitializingBean, Lifecycle, ApplicationListener<ApplicationEvent>, EnvironmentAware,
SmartInitializingSingleton {
private final Log logger = LogFactory.getLog(this.getClass());
@@ -88,6 +97,7 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
private volatile boolean running = true;
private final MultiValueMap<String, String> lazyLifecyleRoles = new LinkedMultiValueMap<String, String>();
@Override
public void setBeanFactory(BeanFactory beanFactory) {
@@ -120,6 +130,21 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
return bean;
}
@Override
public void afterSingletonsInstantiated() {
SmartLifecycleRoleController roleController;
try {
roleController = beanFactory.getBean(IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER,
SmartLifecycleRoleController.class);
for (Entry<String, List<String>> entry : this.lazyLifecyleRoles.entrySet()) {
roleController.addLifecyclesToRole(entry.getKey(), entry.getValue());
}
}
catch (NoSuchBeanDefinitionException e) {
logger.error("No lifecyle role controller in context");
}
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
Assert.notNull(this.beanFactory, "BeanFactory must not be null");
@@ -200,6 +225,10 @@ public class MessagingAnnotationPostProcessor implements BeanPostProcessor, Bean
if (result instanceof ApplicationListener) {
listeners.add((ApplicationListener) result);
}
Role role = AnnotationUtils.findAnnotation(method, Role.class);
if (role != null) {
lazyLifecyleRoles.add(role.value(), endpointBeanName);
}
}
}
}

View File

@@ -73,6 +73,13 @@ public abstract class AbstractChannelAdapterParser extends AbstractBeanDefinitio
if (StringUtils.hasText(phase)) {
propertyValues.add("phase", new TypedStringValue(phase));
}
String role = element.getAttribute(IntegrationNamespaceUtils.ROLE);
if (StringUtils.hasText(role)) {
if (!StringUtils.hasText(element.getAttribute(ID_ATTRIBUTE))) {
parserContext.getReaderContext().error("When using 'role', 'id' is required", element);
}
IntegrationNamespaceUtils.putLifecycleInRole(role, element.getAttribute(ID_ATTRIBUTE), parserContext);
}
return beanDefinition;
}

View File

@@ -155,6 +155,13 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.AUTO_STARTUP);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, IntegrationNamespaceUtils.PHASE);
String role = element.getAttribute(IntegrationNamespaceUtils.ROLE);
if (StringUtils.hasText(role)) {
if (!StringUtils.hasText(element.getAttribute(ID_ATTRIBUTE))) {
parserContext.getReaderContext().error("When using 'role', 'id' is required", element);
}
IntegrationNamespaceUtils.putLifecycleInRole(role, element.getAttribute(ID_ATTRIBUTE), parserContext);
}
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
String beanName = this.resolveId(element, beanDefinition, parserContext);
parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName));

View File

@@ -24,6 +24,7 @@ import org.w3c.dom.NodeList;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -72,6 +73,7 @@ public abstract class IntegrationNamespaceUtils {
public static final String REQUEST_HANDLER_ADVICE_CHAIN = "request-handler-advice-chain";
public static final String AUTO_STARTUP = "auto-startup";
public static final String PHASE = "phase";
public static final String ROLE = "role";
/**
* Configures the provided bean definition builder with a property value corresponding to the attribute whose name
@@ -547,4 +549,19 @@ public abstract class IntegrationNamespaceUtils {
}
}
public static void putLifecycleInRole(String role, String beanName, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
IntegrationConfigUtils.registerRoleControllerDefinitionIfNecessary(registry);
BeanDefinition controllerDef = registry.getBeanDefinition(
IntegrationContextUtils.INTEGRATION_LIFECYCLE_ROLE_CONTROLLER);
@SuppressWarnings("unchecked")
ManagedList<String> roles = (ManagedList<String>) controllerDef.getConstructorArgumentValues()
.getArgumentValue(0, ManagedList.class).getValue();
@SuppressWarnings("unchecked")
ManagedList<BeanReference> lifecycles = (ManagedList<BeanReference>) controllerDef.getConstructorArgumentValues()
.getArgumentValue(1, ManagedList.class).getValue();
roles.add(role);
lifecycles.add(new RuntimeBeanReference(beanName));
}
}

View File

@@ -77,6 +77,8 @@ public abstract class IntegrationContextUtils {
public static final String TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME =
"toStringFriendlyJsonNodeToStringConverter";
public static final String INTEGRATION_LIFECYCLE_ROLE_CONTROLLER = "integrationLifecycleRoleController";
/**
* @param beanFactory BeanFactory for lookup, must not be null.
* @return The {@link MetadataStore} bean whose name is "metadataStore".

View File

@@ -0,0 +1,70 @@
/*
* Copyright 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.integration.leader;
import java.util.UUID;
import org.springframework.util.StringUtils;
/**
* Base implementation of a {@link Candidate}.
*
* @author Janne Valkealahti
*
*/
public abstract class AbstractCandidate implements Candidate {
private static final String DEFAULT_ROLE = "leader";
private final String id;
private final String role;
/**
* Instantiate a abstract candidate.
*/
public AbstractCandidate() {
this(null, null);
}
/**
* Instantiate a abstract candidate.
*
* @param id the identifier
* @param role the role
*/
public AbstractCandidate(String id, String role) {
this.id = StringUtils.hasText(id) ? id : UUID.randomUUID().toString();
this.role = StringUtils.hasText(role) ? role : DEFAULT_ROLE;
}
@Override
public String getRole() {
return this.role;
}
@Override
public String getId() {
return this.id;
}
@Override
public abstract void onGranted(Context ctx) throws InterruptedException;
@Override
public abstract void onRevoked(Context ctx);
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2014-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.integration.leader;
/**
* Interface that defines the contract for candidates to participate
* in a leader election. The callback methods {@link #onGranted(Context)}
* and {@link #onRevoked(Context)} are invoked when leadership is
* granted and revoked.
*
* @author Patrick Peralta
* @author Janne Valkealahti
*
*/
public interface Candidate {
/**
* Gets the role.
*
* @return a string indicating the name of the leadership role
* this candidate is participating in; other candidates
* present in the system with the same name will contend
* for leadership
*/
String getRole();
/**
* Gets the identifier.
*
* @return a unique ID for this candidate; no other candidate for
* leader election should return the same id
*/
String getId();
/**
* Callback method invoked when this candidate is elected leader.
* Implementations may chose to launch a background thread to
* perform leadership roles and return immediately. Another option
* is for implementations to perform all leadership work in the
* thread invoking this method. In the latter case, the
* method <em>must</em> respond to thread interrupts by throwing
* {@link java.lang.InterruptedException}. When the thread
* is interrupted, this indicates that this candidate is no
* longer leader.
*
* @param ctx leadership context
* @throws InterruptedException when this candidate is no longer leader
*/
void onGranted(Context ctx) throws InterruptedException;
/**
* Callback method invoked when this candidate is no longer leader.
* Implementations should use this to shut down any resources
* (threads, network connections, etc) used to perform leadership work.
*
* @param ctx leadership context
*/
void onRevoked(Context ctx);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2014-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.integration.leader;
/**
* Interface that defines the context for candidate leadership.
* Instances of this object are passed to {@link Candidate candidates}
* upon granting and revoking of leadership.
*
* @author Patrick Peralta
* @author Janne Valkealahti
*
*/
public interface Context {
/**
* Checks if the {@link Candidate} this context was
* passed to is the leader.
*
* @return true if the {@link Candidate} this context was
* passed to is the leader
*/
boolean isLeader();
/**
* Causes the {@link Candidate} this context was passed to
* to relinquish leadership. This method has no effect
* if the candidate is not currently the leader.
*/
void yield();
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2014-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.integration.leader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple {@link Candidate} for leadership.
* This implementation simply logs when it is elected and when its leadership is revoked.
*/
public class DefaultCandidate extends AbstractCandidate {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private volatile Context leaderContext;
/**
* Instantiate a default candidate.
*/
public DefaultCandidate() {
super();
}
/**
* Instantiate a default candidate.
*
* @param id the identifier
* @param role the role
*/
public DefaultCandidate(String id, String role) {
super(id, role);
}
@Override
public void onGranted(Context ctx) {
logger.info("{} has been granted leadership; context: {}", this, ctx);
leaderContext = ctx;
}
@Override
public void onRevoked(Context ctx) {
logger.info("{} leadership has been revoked", this, ctx);
}
/**
* Voluntarily yield leadership if held. If leader context is not
* yet known this method does nothing. Leader context becomes available
* only after {@link #onGranted(Context)} method is called by the
* leader initiator.
*/
public void yieldLeadership() {
if (leaderContext != null) {
leaderContext.yield();
}
}
@Override
public String toString() {
return String.format("DefaultCandidate{role=%s, id=%s}", getRole(), getId());
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 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.integration.leader.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.integration.leader.Context;
/**
* Base {@link ApplicationEvent} class for leader based events. All custom event
* classes should be derived from this class.
*
* @author Janne Valkealahti
* @author Gary Russell
*
*/
@SuppressWarnings("serial")
public abstract class AbstractLeaderEvent extends ApplicationEvent {
private final Context context;
private final String role;
/**
* Create a new ApplicationEvent.
*
* @param source the component that published the event (never {@code null})
*/
public AbstractLeaderEvent(Object source) {
this(source, null, null);
}
/**
* Create a new ApplicationEvent.
*
* @param source the component that published the event (never {@code null})
* @param context the context associated with this event
* @param role the role of the leader
*/
public AbstractLeaderEvent(Object source, Context context, String role) {
super(source);
this.context = context;
this.role = role;
}
/**
* Get the {@link Context} associated with this event.
*
* @return the context
*/
public Context getContext() {
return context;
}
/**
* Get the role of the leader.
*
* @return the role
*/
public String getRole() {
return role;
}
@Override
public String toString() {
return getClass().getSimpleName() + " [role=" + role + ", context=" + context + ", source=" + source
+ "]";
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 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.integration.leader.event;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.integration.leader.Context;
/**
* Default implementation of {@link LeaderEventPublisher}.
*
* @author Janne Valkealahti
* @author Gary Russell
*
*/
public class DefaultLeaderEventPublisher implements LeaderEventPublisher, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
/**
* Instantiates a new leader event publisher.
*/
public DefaultLeaderEventPublisher() {
}
/**
* Instantiates a new leader event publisher.
*
* @param applicationEventPublisher the application event publisher
*/
public DefaultLeaderEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void publishOnGranted(Object source, Context context, String role) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new OnGrantedEvent(source, context, role));
}
}
@Override
public void publishOnRevoked(Object source, Context context, String role) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new OnRevokedEvent(source, context, role));
}
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 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.integration.leader.event;
import org.springframework.integration.leader.Context;
/**
* Interface for publishing leader based application events.
*
* @author Janne Valkealahti
* @author Gary Russell
*
*/
public interface LeaderEventPublisher {
/**
* Publish a granted event.
*
* @param source the component generated this event
* @param context the context associated with event
* @param role the role of the leader
*/
void publishOnGranted(Object source, Context context, String role);
/**
* Publish a revoked event.
*
* @param source the component generated this event
* @param context the context associated with event
* @param role the role of the leader
*/
void publishOnRevoked(Object source, Context context, String role);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 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.integration.leader.event;
import org.springframework.integration.leader.Context;
/**
* Generic event representing that leader has been granted.
*
* @author Janne Valkealahti
* @author Gary Russell
*
*/
@SuppressWarnings("serial")
public class OnGrantedEvent extends AbstractLeaderEvent {
/**
* Instantiates a new granted event.
*
* @param source the component that published the event (never {@code null})
* @param context the context associated with this event
* @param role the role of the leader
*/
public OnGrantedEvent(Object source, Context context, String role) {
super(source, context, role);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 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.integration.leader.event;
import org.springframework.integration.leader.Context;
/**
* Generic event representing that leader has been revoked.
*
* @author Janne Valkealahti
* @author Gary Russell
*
*/
@SuppressWarnings("serial")
public class OnRevokedEvent extends AbstractLeaderEvent {
/**
* Instantiates a new revoked event.
*
* @param source the component that published the event (never {@code null})
* @param context the context associated with this event
* @param role the role of the leader
*/
public OnRevokedEvent(Object source, Context context, String role) {
super(source, context, role);
}
}

View File

@@ -0,0 +1,4 @@
/**
* Temporary package until s-c-c-core is released.
*/
package org.springframework.integration.leader.event;

View File

@@ -0,0 +1,4 @@
/**
* Temporary package until s-c-c-core is released.
*/
package org.springframework.integration.leader;

View File

@@ -0,0 +1,215 @@
/*
* Copyright 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.integration.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
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.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.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
* @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 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, new Comparator<SmartLifecycle>() {
@Override
public int compare(SmartLifecycle o1, SmartLifecycle o2) {
return o1.getPhase() < o2.getPhase() ? -1
: o1.getPhase() > o2.getPhase() ? 1 : 0;
}
});
for (SmartLifecycle lifecycle : lifecycles) {
try {
lifecycle.start();
}
catch (Exception e) {
logger.error("Failed to start " + lifecycle + " in role " + role);
}
}
}
}
/**
* 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, new Comparator<SmartLifecycle>() {
@Override
public int compare(SmartLifecycle o1, SmartLifecycle o2) {
return o1.getPhase() < o2.getPhase() ? 1
: o1.getPhase() > o2.getPhase() ? -1 : 0;
}
});
for (SmartLifecycle lifecycle : lifecycles) {
try {
lifecycle.stop();
}
catch (Exception e) {
logger.error("Failed to stop " + lifecycle + " in role " + role);
}
}
}
}
private 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());
}
}
}

View File

@@ -4728,6 +4728,14 @@ default is 0. Values can be negative. See SmartLifeCycle.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="role" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Assigns this endpoint to a role. Endpoints in a role can be started/stopped as a group.
See 'SmartLifecycleRoleController'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:simpleType name="loggingLevel">

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<context:property-placeholder properties-ref="props" />
<util:properties id="props">
<prop key="cluster.1">cluster</prop>
<prop key="cluster.2">cluster</prop>
<prop key="cluster.3">clusterX</prop>
</util:properties>
<int:inbound-channel-adapter id="in" channel="foo" expression="'foo'" role="cluster" auto-startup="false">
<int:poller fixed-delay="60000" />
</int:inbound-channel-adapter>
<int:channel id="foo" />
<int:outbound-channel-adapter id="out1" channel="foo" role="cluster" auto-startup="false" ref="sink" method="foo" />
<int:outbound-channel-adapter id="out2" channel="foo" role="${cluster.1}" auto-startup="false" ref="sink" method="foo" />
<int:outbound-channel-adapter id="out3" channel="foo" role="${cluster.2}" auto-startup="false" ref="sink" method="foo" />
<int:outbound-channel-adapter id="out4" channel="foo" role="${cluster.3}" auto-startup="false" ref="sink" method="foo" />
<bean id="sink" class="org.springframework.integration.config.xml.EndpointRoleParserTests$Sink" />
<int:bridge id="bridge" input-channel="foo" output-channel="nullChannel" role="cluster" auto-startup="false" />
</beans>

View File

@@ -0,0 +1,115 @@
/*
* Copyright 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.integration.config.xml;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.leader.event.OnGrantedEvent;
import org.springframework.integration.leader.event.OnRevokedEvent;
import org.springframework.integration.support.SmartLifecycleRoleController;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @since 4.2
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class EndpointRoleParserTests {
@Autowired
private SourcePollingChannelAdapter in;
@Autowired
private EventDrivenConsumer out1;
@Autowired
private EventDrivenConsumer out2;
@Autowired
private EventDrivenConsumer out3;
@Autowired
private EventDrivenConsumer out4;
@Autowired
private EventDrivenConsumer bridge;
@Autowired
private SmartLifecycleRoleController controller;
@Test
public void test() {
assertFalse(this.in.isRunning());
assertFalse(this.out1.isRunning());
assertFalse(this.out2.isRunning());
assertFalse(this.out3.isRunning());
assertFalse(this.out4.isRunning());
assertFalse(this.bridge.isRunning());
this.controller.startLifecyclesInRole("cluster");
assertTrue(this.in.isRunning());
assertTrue(this.out1.isRunning());
assertTrue(this.out2.isRunning());
assertTrue(this.out3.isRunning());
assertFalse(this.out4.isRunning());
assertTrue(this.bridge.isRunning());
this.controller.stopLifecyclesInRole("cluster");
assertFalse(this.in.isRunning());
assertFalse(this.out1.isRunning());
assertFalse(this.out2.isRunning());
assertFalse(this.out3.isRunning());
assertFalse(this.out4.isRunning());
assertFalse(this.bridge.isRunning());
this.controller.onApplicationEvent(new OnGrantedEvent("foo", null, "cluster"));
assertTrue(this.in.isRunning());
assertTrue(this.out1.isRunning());
assertTrue(this.out2.isRunning());
assertTrue(this.out3.isRunning());
assertFalse(this.out4.isRunning());
assertTrue(this.bridge.isRunning());
this.controller.onApplicationEvent(new OnRevokedEvent("foo", null, "cluster"));
assertFalse(this.in.isRunning());
assertFalse(this.out1.isRunning());
assertFalse(this.out2.isRunning());
assertFalse(this.out3.isRunning());
assertFalse(this.out4.isRunning());
assertFalse(this.bridge.isRunning());
}
public static class Sink {
public void foo(String s) {}
}
}

View File

@@ -74,6 +74,7 @@ import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.annotation.Publisher;
import org.springframework.integration.annotation.Role;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.channel.AbstractMessageChannel;
@@ -88,6 +89,7 @@ import org.springframework.integration.config.ExpressionControlBusFactoryBean;
import org.springframework.integration.config.GlobalChannelInterceptor;
import org.springframework.integration.config.IntegrationConverter;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
@@ -96,6 +98,7 @@ import org.springframework.integration.history.MessageHistoryConfigurer;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.MutableMessageBuilder;
import org.springframework.integration.support.SmartLifecycleRoleController;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -118,6 +121,7 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.util.MultiValueMap;
import reactor.Environment;
import reactor.rx.Promise;
@@ -140,6 +144,9 @@ public class EnableIntegrationTests {
@Autowired
private PollableChannel input;
@Autowired
private SmartLifecycleRoleController roleController;
@Autowired
@Qualifier("annotationTestService.handle.serviceActivator")
private PollingConsumer serviceActivatorEndpoint;
@@ -252,6 +259,10 @@ public class EnableIntegrationTests {
@Autowired
private MessageChannel controlBusChannel;
@Autowired
@Qualifier("enableIntegrationTests.ContextConfiguration2.sendAsyncHandler.serviceActivator")
private AbstractEndpoint sendAsyncHandler;
@Test
public void testAnnotatedServiceActivator() {
assertEquals(10L, TestUtils.getPropertyValue(this.serviceActivatorEndpoint, "maxMessagesPerPoll"));
@@ -588,6 +599,21 @@ public class EnableIntegrationTests {
assertThat(integers, Matchers.<Integer>contains(2, 4, 6, 8, 10));
}
@Test
public void testRoles() {
this.roleController.stopLifecyclesInRole("foo");
@SuppressWarnings("unchecked")
MultiValueMap<String, SmartLifecycle> lifecycles = TestUtils.getPropertyValue(this.roleController,
"lifecycles", MultiValueMap.class);
assertEquals(2, lifecycles.size());
assertEquals(2, lifecycles.get("foo").size());
assertEquals(1, lifecycles.get("bar").size());
assertFalse(this.serviceActivatorEndpoint.isRunning());
assertFalse(this.sendAsyncHandler.isRunning());
assertEquals(2, lifecycles.size());
assertEquals(2, lifecycles.get("foo").size());
}
@Configuration
@ComponentScan
@IntegrationComponentScan
@@ -849,6 +875,7 @@ public class EnableIntegrationTests {
@Bean
@ServiceActivator(inputChannel = "sendAsyncChannel")
@Role("foo")
public MessageHandler sendAsyncHandler() {
return new MessageHandler() {
@@ -863,6 +890,7 @@ public class EnableIntegrationTests {
@Bean
@ServiceActivator(inputChannel = "controlBusChannel")
@Role("bar")
public ExpressionControlBusFactoryBean controlBus() throws Exception {
return new ExpressionControlBusFactoryBean();
}
@@ -1031,6 +1059,7 @@ public class EnableIntegrationTests {
poller = @Poller(maxMessagesPerPoll = "${poller.maxMessagesPerPoll}", fixedDelay = "${poller.interval}"))
@Publisher
@Payload("#args[0].toLowerCase()")
@Role("foo")
public String handle(String payload) {
return payload.toUpperCase();
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 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.integration.support;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.context.SmartLifecycle;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @author Gary Russell
* @since 4.2
*
*/
public class SmartLifecycleRoleControllerTests {
@Test
public void testOrder() {
SmartLifecycle lc1 = mock(SmartLifecycle.class);
when(lc1.getPhase()).thenReturn(2);
SmartLifecycle lc2 = mock(SmartLifecycle.class);
when(lc1.getPhase()).thenReturn(1);
MultiValueMap<String , SmartLifecycle> map = new LinkedMultiValueMap<String, SmartLifecycle>();
map.add("foo", lc1);
map.add("foo", lc2);
SmartLifecycleRoleController controller = new SmartLifecycleRoleController(map);
controller.startLifecyclesInRole("foo");
controller.stopLifecyclesInRole("foo");
InOrder inOrder = inOrder(lc1, lc2);
inOrder.verify(lc2).start();
inOrder.verify(lc1).start();
inOrder.verify(lc1).stop();
inOrder.verify(lc2).stop();
}
}