diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/IoTProvisioningManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/IoTProvisioningManager.java index 077a068827..d9264dfca1 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/IoTProvisioningManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/provisioning/IoTProvisioningManager.java @@ -151,7 +151,7 @@ public IQ handleIQRequest(IQ iqRequest) { }); roster = Roster.getInstanceFor(connection); - roster.setSubscribeListener(new SubscribeListener() { + roster.addSubscribeListener(new SubscribeListener() { @Override public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) { // First check if the subscription request comes from a known registry and accept the request if so. diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java index 59513f454c..52a18dad29 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java @@ -199,7 +199,9 @@ private enum RosterState { private SubscriptionMode subscriptionMode = getDefaultSubscriptionMode(); - private SubscribeListener subscribeListener; + private final Set subscribeListeners = new CopyOnWriteArraySet<>(); + + private SubscriptionMode previousSubscriptionMode; /** * Returns the default subscription processing mode to use when a new Roster is created. The @@ -249,11 +251,12 @@ public void processPacket(Stanza stanza) throws NotConnectedException, SubscribeAnswer subscribeAnswer = null; switch (subscriptionMode) { case manual: - final SubscribeListener subscribeListener = Roster.this.subscribeListener; - if (subscribeListener == null) { - return; + for (SubscribeListener subscribeListener : subscribeListeners) { + subscribeAnswer = subscribeListener.processSubscribe(from, presence); + if (subscribeAnswer != null) { + break; + } } - subscribeAnswer = subscribeListener.processSubscribe(from, presence); if (subscribeAnswer == null) { return; } @@ -666,19 +669,37 @@ public void sendSubscriptionRequest(BareJid jid) throws NotLoggedInException, No } /** - * Set the subscribe listener, which is invoked on incoming subscription requests and if - * {@link SubscriptionMode} is set to {@link SubscriptionMode#manual}. If - * subscribeListener is not null, then this also sets subscription + * Add a subscribe listener, which is invoked on incoming subscription requests and if + * {@link SubscriptionMode} is set to {@link SubscriptionMode#manual}. This also sets subscription * mode to {@link SubscriptionMode#manual}. * - * @param subscribeListener the subscribe listener to set. + * @param subscribeListener the subscribe listener to add. + * @return true if the listener was not already added. + * @since 4.2 + */ + public boolean addSubscribeListener(SubscribeListener subscribeListener) { + Objects.requireNonNull(subscribeListener, "SubscribeListener argument must not be null"); + if (subscriptionMode != SubscriptionMode.manual) { + previousSubscriptionMode = subscriptionMode; + } + return subscribeListeners.add(subscribeListener); + } + + /** + * Remove a subscribe listener. Also restores the previous subscription mode + * state, if the last listener got removed. + * + * @param subscribeListener + * the subscribe listener to remove. + * @return true if the listener registered and got removed. * @since 4.2 */ - public void setSubscribeListener(SubscribeListener subscribeListener) { - if (subscribeListener != null) { - setSubscriptionMode(SubscriptionMode.manual); + public boolean removeSubscribeListener(SubscribeListener subscribeListener) { + boolean removed = subscribeListeners.remove(subscribeListener); + if (removed && subscribeListeners.isEmpty()) { + setSubscriptionMode(previousSubscriptionMode); } - this.subscribeListener = subscribeListener; + return removed; } /** diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java index d5a6394d30..8691bacaca 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java @@ -50,7 +50,7 @@ public RosterIntegrationTest(SmackIntegrationTestEnvironment environment) { public void subscribeRequestListenerTest() throws TimeoutException, Exception { ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); - rosterTwo.setSubscribeListener(new SubscribeListener() { + final SubscribeListener subscribeListener = new SubscribeListener() { @Override public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) { if (from.equals(conOne.getUser().asBareJid())) { @@ -58,7 +58,8 @@ public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) { } return SubscribeAnswer.Deny; } - }); + }; + rosterTwo.addSubscribeListener(subscribeListener); final String conTwosRosterName = "ConTwo " + testRunId; final SimpleResultSyncPoint addedAndSubscribed = new SimpleResultSyncPoint(); @@ -88,9 +89,15 @@ private void checkIfAddedAndSubscribed(Collection addresses) { } } }); - rosterOne.createEntry(conTwo.getUser().asBareJid(), conTwosRosterName, null); - assertTrue(addedAndSubscribed.waitForResult(2 * connection.getPacketReplyTimeout())); + try { + rosterOne.createEntry(conTwo.getUser().asBareJid(), conTwosRosterName, null); + + assertTrue(addedAndSubscribed.waitForResult(2 * connection.getPacketReplyTimeout())); + } + finally { + rosterTwo.removeSubscribeListener(subscribeListener); + } } public static void ensureBothAccountsAreNotInEachOthersRoster(XMPPConnection conOne, XMPPConnection conTwo) throws NotLoggedInException, @@ -124,7 +131,7 @@ private static void ensureSubscribedTo(final XMPPConnection conOne, final XMPPCo return; } - rosterOne.setSubscribeListener(new SubscribeListener() { + final SubscribeListener subscribeListener = new SubscribeListener() { @Override public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) { if (from.equals(conTwo.getUser().asBareJid())) { @@ -132,7 +139,8 @@ public SubscribeAnswer processSubscribe(Jid from, Presence subscribeRequest) { } return SubscribeAnswer.Deny; } - }); + }; + rosterOne.addSubscribeListener(subscribeListener); final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint(); rosterTwo.addPresenceEventListener(new AbstractPresenceEventListener() { @@ -149,7 +157,7 @@ public void presenceSubscribed(BareJid address, Presence subscribedPresence) { try { syncPoint.waitForResult(timeout); } finally { - rosterOne.setSubscribeListener(null); + rosterOne.removeSubscribeListener(subscribeListener); } } }