Skip to content

Commit 88519f6

Browse files
authored
Fix #1084 by enabling app.message method to manage multiple handlers (#1086)
1 parent d537d73 commit 88519f6

File tree

3 files changed

+105
-20
lines changed

3 files changed

+105
-20
lines changed

bolt-aws-lambda/src/test/java/test_locally/Issue419UseCaseTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ public void enableAdditionalListenersRuntime_issue_419() throws Exception {
7979
headers.put("additional-header", Arrays.asList("foo"));
8080
return Response.builder().statusCode(200).headers(headers).build();
8181
});
82-
app.message("Hello", (req, ctx) -> ctx.ack());
8382

8483
SampleHandler handler = new SampleHandler(app);
8584

@@ -95,7 +94,7 @@ public void enableAdditionalListenersRuntime_issue_419() throws Exception {
9594
// message event
9695
ApiGatewayRequest message1 = buildMessageEvent();
9796
ApiGatewayResponse message1Response = handler.handleRequest(message1, context);
98-
assertEquals(200, message1Response.getStatusCode());
97+
assertEquals(404, message1Response.getStatusCode());
9998
assertEquals(0, handler.messageCount.get());
10099

101100
// enable other listeners

bolt/src/main/java/com/slack/api/bolt/App.java

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,10 @@ public enum Status {
176176

177177
/**
178178
* Registered Event API handlers.
179+
* Note that only limited set of events allow having multiple handlers.
180+
* As of v1.27.1, only MessageEvent.class ones can be multiple.
179181
*/
180-
private final Map<String, BoltEventHandler<Event>> eventHandlers = new HashMap<>();
182+
private final Map<String, List<BoltEventHandler<Event>>> eventHandlers = new HashMap<>();
181183

182184
/**
183185
* Primitive Event API handler.
@@ -612,15 +614,32 @@ public App use(Middleware middleware) {
612614
// Events API
613615
// https://api.slack.com/events-api
614616

615-
public <E extends Event> App event(Class<E> eventClass, BoltEventHandler<E> handler) {
617+
public <E extends Event> App event(
618+
Class<E> eventClass,BoltEventHandler<E> handler) {
619+
// Note that having multiple handlers is allowed only for message event handlers.
620+
// If we revisit this to unlock the option to all event types, it should work well.
621+
// We didn't decide to do so in 2022 in respect of better backward compatibility.
622+
boolean allowMultipleEventHandlers = getEventTypeAndSubtype(eventClass) == MessageEvent.TYPE_NAME;
623+
return event(eventClass, allowMultipleEventHandlers, handler);
624+
}
625+
626+
protected <E extends Event> App event(
627+
Class<E> eventClass,
628+
boolean allowMultipleEventHandlers,
629+
BoltEventHandler<E> handler) {
616630
String eventTypeAndSubtype = getEventTypeAndSubtype(eventClass);
617631
if (eventTypeAndSubtype == null) {
618632
throw new IllegalArgumentException("Unexpectedly failed to register the handler");
619633
}
620-
if (eventHandlers.get(eventTypeAndSubtype) != null) {
634+
List<BoltEventHandler<Event>> handlers = eventHandlers.get(eventTypeAndSubtype);
635+
if (handlers == null) {
636+
handlers = new ArrayList<>();
637+
} else if (!allowMultipleEventHandlers) {
621638
log.warn("Replaced the handler for {}", eventTypeAndSubtype);
639+
handlers = new ArrayList<>();
622640
}
623-
eventHandlers.put(eventTypeAndSubtype, (BoltEventHandler<Event>) handler);
641+
handlers.add((BoltEventHandler<Event>) handler);
642+
eventHandlers.put(eventTypeAndSubtype, handlers);
624643
return this;
625644
}
626645

@@ -634,15 +653,15 @@ public App message(String pattern, BoltEventHandler<MessageEvent> messageHandler
634653
}
635654

636655
public App message(Pattern pattern, BoltEventHandler<MessageEvent> messageHandler) {
637-
event(MessageEvent.class, (event, ctx) -> {
656+
event(MessageEvent.class, true, (event, ctx) -> {
638657
String text = event.getEvent().getText();
639658
if (log.isDebugEnabled()) {
640659
log.debug("Run a message event handler (pattern: {}, text: {})", pattern, text);
641660
}
642661
if (text != null && pattern.matcher(text).matches()) {
643662
return messageHandler.apply(event, ctx);
644663
} else {
645-
return ctx.ack();
664+
return null;
646665
}
647666
});
648667
return this;
@@ -1103,10 +1122,15 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi
11031122
return Response.ok();
11041123
}
11051124
EventRequest request = (EventRequest) slackRequest;
1106-
BoltEventHandler<Event> handler = eventHandlers.get(request.getEventTypeAndSubtype());
1107-
if (handler != null) {
1108-
EventsApiPayload<Event> payload = buildEventPayload(request);
1109-
return handler.apply(payload, request.getContext());
1125+
List<BoltEventHandler<Event>> handlers = eventHandlers.get(request.getEventTypeAndSubtype());
1126+
if (handlers != null) {
1127+
for (BoltEventHandler<Event> handler : handlers) {
1128+
EventsApiPayload<Event> payload = buildEventPayload(request);
1129+
Response result = handler.apply(payload, request.getContext());
1130+
if (result != null && result.getStatusCode() == 200) {
1131+
return result;
1132+
}
1133+
}
11101134
}
11111135
if (config().isAllEventsApiAutoAckEnabled()) {
11121136
// If the flag is true, Bolt acknowledges all the events anyway
@@ -1346,10 +1370,15 @@ protected Response runHandler(Request slackRequest) throws IOException, SlackApi
13461370
return Response.ok();
13471371
}
13481372
EventRequest request = new EventRequest(stepRequest.getRequestBodyAsString(), stepRequest.getHeaders());
1349-
BoltEventHandler<Event> handler = eventHandlers.get(request.getEventTypeAndSubtype());
1350-
if (handler != null) {
1351-
EventsApiPayload<Event> payload = buildEventPayload(request);
1352-
return handler.apply(payload, request.getContext());
1373+
List<BoltEventHandler<Event>> handlers = eventHandlers.get(request.getEventTypeAndSubtype());
1374+
if (handlers != null) {
1375+
for (BoltEventHandler<Event> handler : handlers) {
1376+
EventsApiPayload<Event> payload = buildEventPayload(request);
1377+
Response result = handler.apply(payload, request.getContext());
1378+
if (result != null && result.getStatusCode() == 200) {
1379+
return result;
1380+
}
1381+
}
13531382
}
13541383
log.warn("No BoltEventHandler registered for event: {}\n{}",
13551384
request.getEventTypeAndSubtype(), ListenerCodeSuggestion.WORKFLOW_STEP);

bolt/src/test/java/test_locally/app/MessageTest.java

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import java.util.concurrent.atomic.AtomicBoolean;
3030
import java.util.regex.Pattern;
3131

32+
import static org.hamcrest.CoreMatchers.is;
33+
import static org.hamcrest.CoreMatchers.nullValue;
34+
import static org.hamcrest.MatcherAssert.assertThat;
3235
import static org.junit.Assert.*;
3336

3437
@Slf4j
@@ -80,7 +83,7 @@ public void withPayload_skipped() throws Exception {
8083

8184
EventRequest req = buildRequest(messagePayload);
8285
Response response = app.run(req);
83-
assertEquals(200L, response.getStatusCode().longValue());
86+
assertEquals(404L, response.getStatusCode().longValue());
8487
assertFalse(userMessageReceived.get());
8588
}
8689

@@ -133,7 +136,7 @@ public void user_word_not_matching() throws Exception {
133136

134137
EventRequest req = buildRequest(gson.toJson(payload));
135138
Response response = app.run(req);
136-
assertEquals(200L, response.getStatusCode().longValue());
139+
assertEquals(404L, response.getStatusCode().longValue());
137140
assertFalse(userMessageReceived.get());
138141
}
139142

@@ -171,7 +174,7 @@ public void user_regexp_not_matching() throws Exception {
171174

172175
EventRequest req = buildRequest(gson.toJson(payload));
173176
Response response = app.run(req);
174-
assertEquals(200L, response.getStatusCode().longValue());
177+
assertEquals(404L, response.getStatusCode().longValue());
175178
assertFalse(userMessageReceived.get());
176179
}
177180

@@ -188,10 +191,54 @@ public void user_skipped() throws Exception {
188191

189192
EventRequest req = buildRequest(gson.toJson(payload));
190193
Response response = app.run(req);
191-
assertEquals(200L, response.getStatusCode().longValue());
194+
assertEquals(404L, response.getStatusCode().longValue());
192195
assertFalse(userMessageReceived.get());
193196
}
194197

198+
@Test
199+
public void allowMultipleEventHandlers() throws Exception {
200+
App app = buildApp();
201+
AtomicBoolean pattern1Called = new AtomicBoolean(false);
202+
AtomicBoolean pattern2Called = new AtomicBoolean(false);
203+
app.message("pattern1", (req, ctx) -> {
204+
pattern1Called.set(true);
205+
return ctx.ack();
206+
});
207+
app.message("pattern2", (req, ctx) -> {
208+
pattern2Called.set(true);
209+
return ctx.ack();
210+
});
211+
212+
EventsApiPayload<MessageEvent> payload1 = buildPatternMessagePayload("This is pattern1");
213+
EventRequest req1 = buildRequest(gson.toJson(payload1));
214+
Response response1 = app.run(req1);
215+
assertEquals(200L, response1.getStatusCode().longValue());
216+
assertThat(response1.getBody(), is(nullValue()));
217+
218+
EventsApiPayload<MessageEvent> payload2 = buildPatternMessagePayload("This is pattern2");
219+
EventRequest req2 = buildRequest(gson.toJson(payload2));
220+
Response response2 = app.run(req2);
221+
assertEquals(200L, response2.getStatusCode().longValue());
222+
assertThat(response2.getBody(), is(nullValue()));
223+
224+
EventsApiPayload<MessageEvent> payload3 = buildPatternMessagePayload("This is pattern3");
225+
EventRequest req3 = buildRequest(gson.toJson(payload3));
226+
Response response3 = app.run(req3);
227+
assertEquals(404L, response3.getStatusCode().longValue());
228+
assertThat(response3.getBody(), is("{\"error\":\"no handler found\"}"));
229+
230+
assertThat("pattern1 message listener called", pattern1Called.get(), is(true));
231+
assertThat("pattern2 message listener called", pattern2Called.get(), is(true));
232+
233+
// test for ensuring the app.event(MessageEvent.class, ..) never overwrites above handlers
234+
pattern1Called.set(false); // this needs to be true again
235+
app.event(MessageEvent.class, (req, ctx) -> ctx.ack());
236+
Response response4 = app.run(req1);
237+
assertEquals(200L, response4.getStatusCode().longValue());
238+
239+
assertThat("pattern1 message listener called", pattern1Called.get(), is(true));
240+
}
241+
195242
@Test
196243
public void bot_subtype() throws Exception {
197244
App app = buildApp();
@@ -274,4 +321,14 @@ EventsApiPayload<MessageBotEvent> buildUserBotMessagePayload() {
274321
return payload;
275322
}
276323

324+
EventsApiPayload<MessageEvent> buildPatternMessagePayload(String text) {
325+
EventsApiPayload<MessageEvent> payload = new MessagePayload();
326+
MessageEvent event = new MessageEvent();
327+
event.setUser("U123");
328+
event.setText(text);
329+
payload.setEvent(event);
330+
payload.setTeamId("T123");
331+
return payload;
332+
}
333+
277334
}

0 commit comments

Comments
 (0)