Skip to content

Commit 29b60c2

Browse files
artembilangaryrussell
authored andcommitted
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
1 parent 9ce329f commit 29b60c2

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractPollingEndpoint.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -239,7 +239,6 @@ protected void doStop() {
239239
this.runningTask.cancel(true);
240240
}
241241
this.runningTask = null;
242-
this.initialized = false;
243242
}
244243

245244
private boolean doPoll() {

spring-integration-core/src/main/java/org/springframework/integration/support/SmartLifecycleRoleController.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2017 the original author or authors.
2+
* Copyright 2015-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@
4343
import org.springframework.integration.leader.event.OnRevokedEvent;
4444
import org.springframework.integration.support.context.NamedComponent;
4545
import org.springframework.util.Assert;
46+
import org.springframework.util.CollectionUtils;
4647
import org.springframework.util.LinkedMultiValueMap;
4748
import org.springframework.util.MultiValueMap;
4849

@@ -101,7 +102,31 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
101102
* @param lifecycle the {@link SmartLifecycle}.
102103
*/
103104
public final void addLifecycleToRole(String role, SmartLifecycle lifecycle) {
104-
this.lifecycles.add(role, lifecycle);
105+
List<SmartLifecycle> lifecycles = this.lifecycles.get(role);
106+
if (CollectionUtils.isEmpty(lifecycles)) {
107+
this.lifecycles.add(role, lifecycle);
108+
}
109+
else {
110+
lifecycles
111+
.stream()
112+
.filter(e ->
113+
e == lifecycle ||
114+
(e instanceof NamedComponent && lifecycle instanceof NamedComponent
115+
&& ((NamedComponent) e).getComponentName()
116+
.equals(((NamedComponent) lifecycle).getComponentName())))
117+
.findFirst()
118+
.ifPresent(e -> {
119+
throw new IllegalArgumentException("Cannot add the Lifecycle '" +
120+
(lifecycle instanceof NamedComponent
121+
? ((NamedComponent) lifecycle).getComponentName()
122+
: lifecycle)
123+
+ "' to the role '" + role + "' because a Lifecycle with the name '"
124+
+ (e instanceof NamedComponent ? ((NamedComponent) e).getComponentName() : e)
125+
+ "' is already present.");
126+
});
127+
128+
lifecycles.add(lifecycle);
129+
}
105130
}
106131

107132
/**

spring-integration-core/src/test/java/org/springframework/integration/config/xml/EndpointRoleParserTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2016 the original author or authors.
2+
* Copyright 2015-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,8 @@
3333

3434
/**
3535
* @author Gary Russell
36+
* @author Artem Bilan
37+
*
3638
* @since 4.2
3739
*
3840
*/
@@ -105,6 +107,8 @@ public void test() {
105107
assertFalse(this.out3.isRunning());
106108
assertFalse(this.out4.isRunning());
107109
assertFalse(this.bridge.isRunning());
110+
111+
assertFalse(this.controller.allEndpointsRunning("cluster"));
108112
}
109113

110114
public static class Sink {

spring-integration-core/src/test/java/org/springframework/integration/support/SmartLifecycleRoleControllerTests.java

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2016 the original author or authors.
2+
* Copyright 2015-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.integration.support;
1818

19+
import static org.assertj.core.api.Assertions.assertThat;
1920
import static org.mockito.Mockito.inOrder;
2021
import static org.mockito.Mockito.mock;
2122
import static org.mockito.Mockito.when;
@@ -24,11 +25,14 @@
2425
import org.mockito.InOrder;
2526

2627
import org.springframework.context.SmartLifecycle;
28+
import org.springframework.integration.support.context.NamedComponent;
2729
import org.springframework.util.LinkedMultiValueMap;
2830
import org.springframework.util.MultiValueMap;
2931

3032
/**
3133
* @author Gary Russell
34+
* @author Artem Bilan
35+
*
3236
* @since 4.2
3337
*
3438
*/
@@ -39,8 +43,8 @@ public void testOrder() {
3943
SmartLifecycle lc1 = mock(SmartLifecycle.class);
4044
when(lc1.getPhase()).thenReturn(2);
4145
SmartLifecycle lc2 = mock(SmartLifecycle.class);
42-
when(lc1.getPhase()).thenReturn(1);
43-
MultiValueMap<String, SmartLifecycle> map = new LinkedMultiValueMap<String, SmartLifecycle>();
46+
when(lc2.getPhase()).thenReturn(1);
47+
MultiValueMap<String, SmartLifecycle> map = new LinkedMultiValueMap<>();
4448
map.add("foo", lc1);
4549
map.add("foo", lc2);
4650
SmartLifecycleRoleController controller = new SmartLifecycleRoleController(map);
@@ -53,4 +57,68 @@ public void testOrder() {
5357
inOrder.verify(lc2).stop();
5458
}
5559

60+
61+
@Test
62+
public void allEndpointRunning() {
63+
SmartLifecycle lc1 = new PseudoSmartLifecycle();
64+
SmartLifecycle lc2 = new PseudoSmartLifecycle();
65+
MultiValueMap<String, SmartLifecycle> map = new LinkedMultiValueMap<>();
66+
map.add("foo", lc1);
67+
map.add("foo", lc2);
68+
try {
69+
new SmartLifecycleRoleController(map);
70+
}
71+
catch (Exception e) {
72+
assertThat(e.getMessage())
73+
.contains("Cannot add the Lifecycle 'bar' to the role 'foo' " +
74+
"because a Lifecycle with the name 'bar' is already present.");
75+
}
76+
}
77+
78+
private static class PseudoSmartLifecycle implements SmartLifecycle, NamedComponent {
79+
80+
boolean running;
81+
82+
@Override
83+
public boolean isAutoStartup() {
84+
return false;
85+
}
86+
87+
@Override
88+
public void stop(Runnable callback) {
89+
running = false;
90+
}
91+
92+
@Override
93+
public void start() {
94+
running = true;
95+
}
96+
97+
@Override
98+
public void stop() {
99+
running = false;
100+
}
101+
102+
@Override
103+
public boolean isRunning() {
104+
return running;
105+
}
106+
107+
@Override
108+
public int getPhase() {
109+
return 0;
110+
}
111+
112+
@Override
113+
public String getComponentName() {
114+
return "bar";
115+
}
116+
117+
@Override
118+
public String getComponentType() {
119+
return PseudoSmartLifecycle.class.getName();
120+
}
121+
122+
}
123+
56124
}

0 commit comments

Comments
 (0)