INT-4438: No lifecycle twice in the same role

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

The `SmartLifecycleRoleController` is based on the `MultiValueMap`
which used internally a `List` for the values.
With such an architecture we can add the same value several times.
On the other hand we are iterating over `Lifecycle`s in the role and
build a `Map` for their running status.
In this case when `NamesComponent`s return the same name the Java
`Collectors.toMap()` fails with a duplicate key error.

In any cases it would be better do not allow to add the same lifecylce
several time to the role or different with the same name.

* Add search logic to the `addLifecycleToRole()` to fail fast with the
`IllegalArgumentException` because a lifecycle with the same name is
already present in the role

**Cherry-pick to 5.0.x**

* Remove redundant `this.initialized = false` from the
`AbstractPollingEndpoint.doStop()`

Add `allEndpointsRunning()` verification to the `EndpointRoleParserTests`

Polishing
This commit is contained in:
Artem Bilan
2018-03-23 15:21:06 -04:00
committed by Gary Russell
parent 9ce329f1c6
commit 29b60c2932
4 changed files with 104 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -239,7 +239,6 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
this.runningTask.cancel(true);
}
this.runningTask = null;
this.initialized = false;
}
private boolean doPoll() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2017 the original author or authors.
* Copyright 2015-2018 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.
@@ -43,6 +43,7 @@ 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.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -101,7 +102,31 @@ public class SmartLifecycleRoleController implements ApplicationListener<Abstrac
* @param lifecycle the {@link SmartLifecycle}.
*/
public final void addLifecycleToRole(String role, SmartLifecycle lifecycle) {
this.lifecycles.add(role, lifecycle);
List<SmartLifecycle> lifecycles = this.lifecycles.get(role);
if (CollectionUtils.isEmpty(lifecycles)) {
this.lifecycles.add(role, lifecycle);
}
else {
lifecycles
.stream()
.filter(e ->
e == lifecycle ||
(e instanceof NamedComponent && lifecycle instanceof NamedComponent
&& ((NamedComponent) e).getComponentName()
.equals(((NamedComponent) lifecycle).getComponentName())))
.findFirst()
.ifPresent(e -> {
throw new IllegalArgumentException("Cannot add the Lifecycle '" +
(lifecycle instanceof NamedComponent
? ((NamedComponent) lifecycle).getComponentName()
: lifecycle)
+ "' to the role '" + role + "' because a Lifecycle with the name '"
+ (e instanceof NamedComponent ? ((NamedComponent) e).getComponentName() : e)
+ "' is already present.");
});
lifecycles.add(lifecycle);
}
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2018 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.
@@ -33,6 +33,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.2
*
*/
@@ -105,6 +107,8 @@ public class EndpointRoleParserTests {
assertFalse(this.out3.isRunning());
assertFalse(this.out4.isRunning());
assertFalse(this.bridge.isRunning());
assertFalse(this.controller.allEndpointsRunning("cluster"));
}
public static class Sink {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2018 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.
@@ -16,6 +16,7 @@
package org.springframework.integration.support;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -24,11 +25,14 @@ import org.junit.Test;
import org.mockito.InOrder;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.2
*
*/
@@ -39,8 +43,8 @@ public class SmartLifecycleRoleControllerTests {
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>();
when(lc2.getPhase()).thenReturn(1);
MultiValueMap<String, SmartLifecycle> map = new LinkedMultiValueMap<>();
map.add("foo", lc1);
map.add("foo", lc2);
SmartLifecycleRoleController controller = new SmartLifecycleRoleController(map);
@@ -53,4 +57,68 @@ public class SmartLifecycleRoleControllerTests {
inOrder.verify(lc2).stop();
}
@Test
public void allEndpointRunning() {
SmartLifecycle lc1 = new PseudoSmartLifecycle();
SmartLifecycle lc2 = new PseudoSmartLifecycle();
MultiValueMap<String, SmartLifecycle> map = new LinkedMultiValueMap<>();
map.add("foo", lc1);
map.add("foo", lc2);
try {
new SmartLifecycleRoleController(map);
}
catch (Exception e) {
assertThat(e.getMessage())
.contains("Cannot add the Lifecycle 'bar' to the role 'foo' " +
"because a Lifecycle with the name 'bar' is already present.");
}
}
private static class PseudoSmartLifecycle implements SmartLifecycle, NamedComponent {
boolean running;
@Override
public boolean isAutoStartup() {
return false;
}
@Override
public void stop(Runnable callback) {
running = false;
}
@Override
public void start() {
running = true;
}
@Override
public void stop() {
running = false;
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return 0;
}
@Override
public String getComponentName() {
return "bar";
}
@Override
public String getComponentType() {
return PseudoSmartLifecycle.class.getName();
}
}
}