Skip to content

Commit 335ce0d

Browse files
committed
Remove the listener the Javacord command handler added on bean destroyal
1 parent 1f19aee commit 335ce0d

File tree

2 files changed

+120
-5
lines changed

2 files changed

+120
-5
lines changed

src/main/java/net/kautler/command/handler/CommandHandlerJavacord.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,22 @@
2727
import org.javacord.api.DiscordApi;
2828
import org.javacord.api.entity.message.Message;
2929
import org.javacord.api.event.message.MessageCreateEvent;
30+
import org.javacord.api.util.event.ListenerManager;
3031

3132
import javax.annotation.PostConstruct;
33+
import javax.annotation.PreDestroy;
3234
import javax.enterprise.context.ApplicationScoped;
3335
import javax.enterprise.event.Event;
3436
import javax.enterprise.inject.Instance;
3537
import javax.enterprise.inject.Produces;
3638
import javax.inject.Inject;
3739
import java.util.Collection;
40+
import java.util.StringJoiner;
3841
import java.util.stream.Stream;
3942

43+
import static java.util.Collections.emptyList;
4044
import static java.util.concurrent.CompletableFuture.runAsync;
45+
import static java.util.stream.Collectors.toList;
4146

4247
/**
4348
* A command handler that handles Javacord messages.
@@ -77,6 +82,12 @@ class CommandHandlerJavacord extends CommandHandler<Message> {
7782
@Inject
7883
private volatile Event<CommandNotFoundEventJavacord> commandNotFoundEvent;
7984

85+
/**
86+
* The listener managers for the added listeners,
87+
* so that they can easily be removed when the bean is destroyed.
88+
*/
89+
private Collection<ListenerManager<?>> listenerManagers = emptyList();
90+
8091
/**
8192
* Constructs a new Javacord command handler.
8293
*/
@@ -128,13 +139,24 @@ private void addListener() {
128139
} else {
129140
logger.info("DiscordApi and Collection<DiscordApi> injected, CommandHandlerJavacord will be used.");
130141
}
131-
Stream.concat(
142+
143+
listenerManagers = Stream.concat(
132144
discordApis.stream(),
133145
discordApiCollections.stream().flatMap(Collection::stream)
134-
).forEach(discordApi -> discordApi.addMessageCreateListener(this::handleMessage));
146+
)
147+
.map(discordApi -> discordApi.addMessageCreateListener(this::handleMessage))
148+
.collect(toList());
135149
}
136150
}
137151

152+
/**
153+
* Removes this command handler from the injected {@code DiscordApi} instances as message create listener.
154+
*/
155+
@PreDestroy
156+
private void removeListener() {
157+
listenerManagers.forEach(ListenerManager::remove);
158+
}
159+
138160
/**
139161
* Handles the actual messages received.
140162
*
@@ -164,4 +186,11 @@ protected void executeAsync(Message message, Runnable commandExecutor) {
164186
}
165187
});
166188
}
189+
190+
@Override
191+
public String toString() {
192+
return new StringJoiner(", ", CommandHandlerJavacord.class.getSimpleName() + "[", "]")
193+
.add("listenerManagers=" + listenerManagers)
194+
.toString();
195+
}
167196
}

src/test/groovy/net/kautler/command/handler/CommandHandlerJavacordTest.groovy

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.javacord.api.entity.message.Message
3030
import org.javacord.api.event.message.MessageCreateEvent
3131
import org.javacord.api.listener.message.MessageCreateListener
3232
import org.javacord.api.util.concurrent.ThreadPool
33+
import org.javacord.api.util.event.ListenerManager
3334
import org.javacord.core.DiscordApiImpl
3435
import org.jboss.weld.junit.MockBean
3536
import org.jboss.weld.junit4.WeldInitiator
@@ -57,13 +58,24 @@ import static java.util.concurrent.TimeUnit.SECONDS
5758
import static org.apache.logging.log4j.Level.ERROR
5859
import static org.apache.logging.log4j.Level.INFO
5960
import static org.apache.logging.log4j.test.appender.ListAppender.getListAppender
61+
import static org.powermock.reflect.Whitebox.getAllInstanceFields
62+
import static org.powermock.reflect.Whitebox.getField
63+
import static org.powermock.reflect.Whitebox.newInstance
6064

6165
class CommandHandlerJavacordTest extends Specification {
62-
DiscordApi discordApi = Mock()
66+
ListenerManager<Object> listenerManager = Mock()
6367

64-
DiscordApi discordApiInCollection1 = Mock()
68+
DiscordApi discordApi = Mock {
69+
addMessageCreateListener(_) >> listenerManager
70+
}
71+
72+
DiscordApi discordApiInCollection1 = Mock {
73+
addMessageCreateListener(_) >> listenerManager
74+
}
6575

66-
DiscordApi discordApiInCollection2 = Mock()
76+
DiscordApi discordApiInCollection2 = Mock {
77+
addMessageCreateListener(_) >> listenerManager
78+
}
6779

6880
Restriction<Object> restriction = Stub {
6981
allowCommand(_) >> false
@@ -237,6 +249,7 @@ class CommandHandlerJavacordTest extends Specification {
237249
].each {
238250
1 * it.addMessageCreateListener(_) >> { MessageCreateListener listener ->
239251
listener.onMessageCreate(messageCreateEvent)
252+
listenerManager
240253
}
241254
}
242255
3 * commandHandlerJavacord.doHandleMessage(message, message.content) >> { }
@@ -405,6 +418,79 @@ class CommandHandlerJavacordTest extends Specification {
405418
discordApi?.disconnect()
406419
}
407420

421+
def 'shutting down the container should remove the listeners'() {
422+
when:
423+
weld.shutdown()
424+
425+
then:
426+
3 * listenerManager.remove()
427+
}
428+
429+
def 'shutting down the container without Javacord producer should not log an error'() {
430+
when:
431+
WeldInitiator
432+
.from(
433+
CommandHandlerJavacord,
434+
LoggerProducer
435+
)
436+
.addBeans(
437+
MockBean.builder()
438+
.scope(ApplicationScoped)
439+
.qualifiers(new AnnotationLiteral<Internal>() { })
440+
.types(new TypeLiteral<PrefixProvider<Object>>() { }.type)
441+
.creating(defaultPrefixProvider)
442+
.build()
443+
)
444+
.build()
445+
.apply({ }, null)
446+
.evaluate()
447+
448+
then:
449+
getListAppender('Test Appender')
450+
.events
451+
.findAll { it.level == ERROR }
452+
.empty
453+
}
454+
455+
@Use(ContextualInstanceCategory)
456+
def 'toString should start with class name'() {
457+
expect:
458+
commandHandlerJavacord.toString().startsWith("${commandHandlerJavacord.ci().getClass().simpleName}[")
459+
}
460+
461+
@Use(ContextualInstanceCategory)
462+
def 'toString should contain field name and value for "#field.name"'() {
463+
when:
464+
def toStringResult = commandHandlerJavacord.toString()
465+
466+
then:
467+
toStringResult.contains("$field.name=")
468+
field.type == String ?
469+
toStringResult.contains("'${field.get(commandHandlerJavacord.ci())}'") :
470+
toStringResult.contains(field.get(commandHandlerJavacord.ci()).toString())
471+
472+
where:
473+
field << getAllInstanceFields(newInstance(getField(getClass(), 'commandHandlerJavacord').type))
474+
.findAll {
475+
!(it.name in [
476+
'logger',
477+
'discordApis',
478+
'discordApiCollections',
479+
'commandNotAllowedEvent',
480+
'commandNotFoundEvent',
481+
'defaultPrefixProvider',
482+
'commandByAlias',
483+
'commandPattern',
484+
'customPrefixProvider',
485+
'prefixProvider',
486+
'availableRestrictions',
487+
'readLock',
488+
'writeLock',
489+
'executorService'
490+
])
491+
}
492+
}
493+
408494
@ApplicationScoped
409495
static class TestEventReceiver {
410496
@Inject

0 commit comments

Comments
 (0)