• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

igniterealtime / Smack / #2862

pending completion
#2862

push

github-actions

web-flow
Merge pull request #535 from MF1-MS/mf1-ms/xep_0249_support

Add partial support for XEP-0249 Direct MUC Invitations

45 of 45 new or added lines in 3 files covered. (100.0%)

16371 of 41836 relevant lines covered (39.13%)

0.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.39
/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java
1
/**
2
 *
3
 * Copyright ยฉ 2014-2021 Florian Schmaus
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *     http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.jivesoftware.smackx.muc;
18

19
import java.lang.ref.WeakReference;
20
import java.util.ArrayList;
21
import java.util.Collections;
22
import java.util.HashMap;
23
import java.util.HashSet;
24
import java.util.List;
25
import java.util.Map;
26
import java.util.Set;
27
import java.util.WeakHashMap;
28
import java.util.concurrent.CopyOnWriteArrayList;
29
import java.util.concurrent.CopyOnWriteArraySet;
30
import java.util.logging.Level;
31
import java.util.logging.Logger;
32

33
import org.jivesoftware.smack.ConnectionCreationListener;
34
import org.jivesoftware.smack.ConnectionListener;
35
import org.jivesoftware.smack.Manager;
36
import org.jivesoftware.smack.SmackException.NoResponseException;
37
import org.jivesoftware.smack.SmackException.NotConnectedException;
38
import org.jivesoftware.smack.StanzaListener;
39
import org.jivesoftware.smack.XMPPConnection;
40
import org.jivesoftware.smack.XMPPConnectionRegistry;
41
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
42
import org.jivesoftware.smack.filter.AndFilter;
43
import org.jivesoftware.smack.filter.ExtensionElementFilter;
44
import org.jivesoftware.smack.filter.MessageTypeFilter;
45
import org.jivesoftware.smack.filter.NotFilter;
46
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
47
import org.jivesoftware.smack.filter.StanzaFilter;
48
import org.jivesoftware.smack.filter.StanzaTypeFilter;
49
import org.jivesoftware.smack.packet.Message;
50
import org.jivesoftware.smack.packet.MessageBuilder;
51
import org.jivesoftware.smack.packet.Stanza;
52
import org.jivesoftware.smack.util.Async;
53
import org.jivesoftware.smack.util.CleaningWeakReferenceMap;
54

55
import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider;
56
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
57
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
58
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
59
import org.jivesoftware.smackx.muc.MultiUserChatException.MucNotJoinedException;
60
import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;
61
import org.jivesoftware.smackx.muc.packet.GroupChatInvitation;
62
import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
63
import org.jivesoftware.smackx.muc.packet.MUCUser;
64

65
import org.jxmpp.jid.DomainBareJid;
66
import org.jxmpp.jid.EntityBareJid;
67
import org.jxmpp.jid.EntityFullJid;
68
import org.jxmpp.jid.EntityJid;
69
import org.jxmpp.jid.Jid;
70
import org.jxmpp.jid.parts.Resourcepart;
71
import org.jxmpp.util.cache.ExpirationCache;
72

73
/**
74
 * A manager for Multi-User Chat rooms.
75
 * <p>
76
 * Use {@link #getMultiUserChat(EntityBareJid)} to retrieve an object representing a Multi-User Chat room.
77
 * </p>
78
 * <p>
79
 * <b>Automatic rejoin:</b> The manager supports automatic rejoin of MultiUserChat rooms once the connection got
80
 * re-established. This mechanism is disabled by default. To enable it, use {@link #setAutoJoinOnReconnect(boolean)}.
81
 * You can set a {@link AutoJoinFailedCallback} via {@link #setAutoJoinFailedCallback(AutoJoinFailedCallback)} to get
82
 * notified if this mechanism failed for some reason. Note that as soon as rejoining for a single room failed, no
83
 * further attempts will be made for the other rooms.
84
 * </p>
85
 *
86
 * @see <a href="http://xmpp.org/extensions/xep-0045.html">XEP-0045: Multi-User Chat</a>
87
 */
88
public final class MultiUserChatManager extends Manager {
1✔
89
    private static final String DISCO_NODE = MUCInitialPresence.NAMESPACE + "#rooms";
90

91
    private static final Logger LOGGER = Logger.getLogger(MultiUserChatManager.class.getName());
1✔
92

93
    static {
94
        XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
1✔
95
            @Override
96
            public void connectionCreated(final XMPPConnection connection) {
97
                // Set on every established connection that this client supports the Multi-User
98
                // Chat protocol. This information will be used when another client tries to
99
                // discover whether this client supports MUC or not.
100
                ServiceDiscoveryManager.getInstanceFor(connection).addFeature(MUCInitialPresence.NAMESPACE);
1✔
101

102
                // Set the NodeInformationProvider that will provide information about the
103
                // joined rooms whenever a disco request is received
104
                final WeakReference<XMPPConnection> weakRefConnection = new WeakReference<XMPPConnection>(connection);
1✔
105
                ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(DISCO_NODE,
1✔
106
                                new AbstractNodeInformationProvider() {
1✔
107
                                    @Override
108
                                    public List<DiscoverItems.Item> getNodeItems() {
109
                                        XMPPConnection connection = weakRefConnection.get();
×
110
                                        if (connection == null)
×
111
                                            return Collections.emptyList();
×
112
                                        Set<EntityBareJid> joinedRooms = MultiUserChatManager.getInstanceFor(connection).getJoinedRooms();
×
113
                                        List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>();
×
114
                                        for (EntityBareJid room : joinedRooms) {
×
115
                                            answer.add(new DiscoverItems.Item(room));
×
116
                                        }
×
117
                                        return answer;
×
118
                                    }
119
                                });
120
            }
1✔
121
        });
122
    }
123

124
    private static final Map<XMPPConnection, MultiUserChatManager> INSTANCES = new WeakHashMap<XMPPConnection, MultiUserChatManager>();
1✔
125

126
    /**
127
     * Get a instance of a multi user chat manager for the given connection.
128
     *
129
     * @param connection TODO javadoc me please
130
     * @return a multi user chat manager.
131
     */
132
    public static synchronized MultiUserChatManager getInstanceFor(XMPPConnection connection) {
133
        MultiUserChatManager multiUserChatManager = INSTANCES.get(connection);
1✔
134
        if (multiUserChatManager == null) {
1✔
135
            multiUserChatManager = new MultiUserChatManager(connection);
1✔
136
            INSTANCES.put(connection, multiUserChatManager);
1✔
137
        }
138
        return multiUserChatManager;
1✔
139
    }
140

141
    private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
1✔
142
                    new NotFilter(MessageTypeFilter.ERROR));
143

144
    private static final StanzaFilter DIRECT_INVITATION_FILTER =
1✔
145
        new AndFilter(StanzaTypeFilter.MESSAGE,
146
                      new ExtensionElementFilter<GroupChatInvitation>(GroupChatInvitation.class),
147
                      new NotFilter(MessageTypeFilter.ERROR));
148

149
    private static final ExpirationCache<DomainBareJid, DiscoverInfo> KNOWN_MUC_SERVICES = new ExpirationCache<>(
1✔
150
        100, 1000 * 60 * 60 * 24);
151

152
    private static final Set<MucMessageInterceptor> DEFAULT_MESSAGE_INTERCEPTORS = new HashSet<>();
1✔
153

154
    private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
1✔
155

156
    /**
157
     * The XMPP addresses of currently joined rooms.
158
     */
159
    private final Set<EntityBareJid> joinedRooms = new CopyOnWriteArraySet<>();
1✔
160

161
    /**
162
     * A Map of MUC JIDs to {@link MultiUserChat} instances. We use weak references for the values in order to allow
163
     * those instances to get garbage collected. Note that MultiUserChat instances can not get garbage collected while
164
     * the user is joined, because then the MUC will have PacketListeners added to the XMPPConnection.
165
     */
166
    private final Map<EntityBareJid, WeakReference<MultiUserChat>> multiUserChats = new CleaningWeakReferenceMap<>();
1✔
167

168
    private boolean autoJoinOnReconnect;
169

170
    private AutoJoinFailedCallback autoJoinFailedCallback;
171

172
    private AutoJoinSuccessCallback autoJoinSuccessCallback;
173

174
    private final ServiceDiscoveryManager serviceDiscoveryManager;
175

176
    private MultiUserChatManager(XMPPConnection connection) {
177
        super(connection);
1✔
178
        serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
1✔
179
        // Listens for all messages that include a MUCUser extension and fire the invitation
180
        // listeners if the message includes an invitation.
181
        StanzaListener invitationPacketListener = new StanzaListener() {
1✔
182
            @Override
183
            public void processStanza(Stanza packet) {
184
                final Message message = (Message) packet;
×
185
                // Get the MUCUser extension
186
                final MUCUser mucUser = MUCUser.from(message);
×
187
                // Check if the MUCUser extension includes an invitation
188
                if (mucUser.getInvite() != null) {
×
189
                    EntityBareJid mucJid = message.getFrom().asEntityBareJidIfPossible();
×
190
                    if (mucJid == null) {
×
191
                        LOGGER.warning("Invite to non bare JID: '" + message.toXML() + "'");
×
192
                        return;
×
193
                    }
194
                    // Fire event for invitation listeners
195
                    final MultiUserChat muc = getMultiUserChat(mucJid);
×
196
                    final XMPPConnection connection = connection();
×
197
                    final MUCUser.Invite invite = mucUser.getInvite();
×
198
                    final EntityJid from = invite.getFrom();
×
199
                    final String reason = invite.getReason();
×
200
                    final String password = mucUser.getPassword();
×
201
                    for (final InvitationListener listener : invitationsListeners) {
×
202
                        listener.invitationReceived(connection, muc, from, reason, password, message, invite);
×
203
                    }
×
204
                }
205
            }
×
206
        };
207
        connection.addAsyncStanzaListener(invitationPacketListener, INVITATION_FILTER);
1✔
208

209
        // Listens for all messages that include an XEP-0249 GroupChatInvitation extension and fire the invitation
210
        // listeners
211
        StanzaListener directInvitationStanzaListener = new StanzaListener() {
1✔
212
            @Override
213
            public void processStanza(Stanza stanza) {
214
                final Message message = (Message) stanza;
1✔
215
                GroupChatInvitation invite =
1✔
216
                    stanza.getExtension(GroupChatInvitation.class);
1✔
217

218
                // Fire event for invitation listeners
219
                final MultiUserChat muc = getMultiUserChat(invite.getRoomAddress());
1✔
220
                final XMPPConnection connection = connection();
1✔
221
                final EntityJid from = message.getFrom().asEntityJidIfPossible();
1✔
222
                if (from == null) {
1✔
223
                    LOGGER.warning("Group Chat Invitation from non entity JID in '" + message + "'");
×
224
                    return;
×
225
                }
226
                final String reason = invite.getReason();
1✔
227
                final String password = invite.getPassword();
1✔
228
                final MUCUser.Invite mucInvite = new MUCUser.Invite(reason, from, connection.getUser().asEntityBareJid());
1✔
229
                for (final InvitationListener listener : invitationsListeners) {
1✔
230
                    listener.invitationReceived(connection, muc, from, reason, password, message, mucInvite);
1✔
231
                }
1✔
232
            }
1✔
233
        };
234
        connection.addAsyncStanzaListener(directInvitationStanzaListener, DIRECT_INVITATION_FILTER);
1✔
235

236
        connection.addConnectionListener(new ConnectionListener() {
1✔
237
            @Override
238
            public void authenticated(XMPPConnection connection, boolean resumed) {
239
                if (resumed) return;
×
240
                if (!autoJoinOnReconnect) return;
×
241

242
                final Set<EntityBareJid> mucs = getJoinedRooms();
×
243
                if (mucs.isEmpty()) return;
×
244

245
                Async.go(new Runnable() {
×
246
                    @Override
247
                    public void run() {
248
                        final AutoJoinFailedCallback failedCallback = autoJoinFailedCallback;
×
249
                        final AutoJoinSuccessCallback successCallback = autoJoinSuccessCallback;
×
250
                        for (EntityBareJid mucJid : mucs) {
×
251
                            MultiUserChat muc = getMultiUserChat(mucJid);
×
252

253
                            if (!muc.isJoined()) return;
×
254

255
                            Resourcepart nickname = muc.getNickname();
×
256
                            if (nickname == null) return;
×
257

258
                            try {
259
                                muc.leave();
×
260
                            } catch (NotConnectedException | InterruptedException | MucNotJoinedException
×
261
                                            | NoResponseException | XMPPErrorException e) {
262
                                if (failedCallback != null) {
×
263
                                    failedCallback.autoJoinFailed(muc, e);
×
264
                                } else {
265
                                    LOGGER.log(Level.WARNING, "Could not leave room", e);
×
266
                                }
267
                                return;
×
268
                            }
×
269
                            try {
270
                                muc.join(nickname);
×
271
                                if (successCallback != null) {
×
272
                                    successCallback.autoJoinSuccess(muc, nickname);
×
273
                                }
274
                            } catch (NotAMucServiceException | NoResponseException | XMPPErrorException
×
275
                                    | NotConnectedException | InterruptedException e) {
276
                                if (failedCallback != null) {
×
277
                                    failedCallback.autoJoinFailed(muc, e);
×
278
                                } else {
279
                                    LOGGER.log(Level.WARNING, "Could not leave room", e);
×
280
                                }
281
                                return;
×
282
                            }
×
283
                        }
×
284
                    }
×
285

286
                });
287
            }
×
288
        });
289
    }
1✔
290

291
    /**
292
     * Creates a multi user chat. Note: no information is sent to or received from the server until you attempt to
293
     * {@link MultiUserChat#join(org.jxmpp.jid.parts.Resourcepart) join} the chat room. On some server implementations, the room will not be
294
     * created until the first person joins it.
295
     * <p>
296
     * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com for the XMPP server example.com).
297
     * You must ensure that the room address you're trying to connect to includes the proper chat sub-domain.
298
     * </p>
299
     *
300
     * @param jid the name of the room in the form "roomName@service", where "service" is the hostname at which the
301
     *        multi-user chat service is running. Make sure to provide a valid JID.
302
     * @return MultiUserChat instance of the room with the given jid.
303
     */
304
    public synchronized MultiUserChat getMultiUserChat(EntityBareJid jid) {
305
        WeakReference<MultiUserChat> weakRefMultiUserChat = multiUserChats.get(jid);
1✔
306
        if (weakRefMultiUserChat == null) {
1✔
307
            return createNewMucAndAddToMap(jid);
1✔
308
        }
309
        MultiUserChat multiUserChat = weakRefMultiUserChat.get();
×
310
        if (multiUserChat == null) {
×
311
            return createNewMucAndAddToMap(jid);
×
312
        }
313
        return multiUserChat;
×
314
    }
315

316
    public static boolean addDefaultMessageInterceptor(MucMessageInterceptor messageInterceptor) {
317
        synchronized (DEFAULT_MESSAGE_INTERCEPTORS) {
1✔
318
            return DEFAULT_MESSAGE_INTERCEPTORS.add(messageInterceptor);
1✔
319
        }
320
    }
321

322
    public static boolean removeDefaultMessageInterceptor(MucMessageInterceptor messageInterceptor) {
323
        synchronized (DEFAULT_MESSAGE_INTERCEPTORS) {
×
324
            return DEFAULT_MESSAGE_INTERCEPTORS.remove(messageInterceptor);
×
325
        }
326
    }
327

328
    private MultiUserChat createNewMucAndAddToMap(EntityBareJid jid) {
329
        MultiUserChat multiUserChat = new MultiUserChat(connection(), jid, this);
1✔
330
        multiUserChats.put(jid, new WeakReference<MultiUserChat>(multiUserChat));
1✔
331
        return multiUserChat;
1✔
332
    }
333

334
    /**
335
     * Returns true if the specified user supports the Multi-User Chat protocol.
336
     *
337
     * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com.
338
     * @return a boolean indicating whether the specified user supports the MUC protocol.
339
     * @throws XMPPErrorException if there was an XMPP error returned.
340
     * @throws NoResponseException if there was no response from the remote entity.
341
     * @throws NotConnectedException if the XMPP connection is not connected.
342
     * @throws InterruptedException if the calling thread was interrupted.
343
     */
344
    public boolean isServiceEnabled(Jid user) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
345
        return serviceDiscoveryManager.supportsFeature(user, MUCInitialPresence.NAMESPACE);
×
346
    }
347

348
    /**
349
     * Returns a Set of the rooms where the user has joined. The Iterator will contain Strings where each String
350
     * represents a room (e.g. room@muc.jabber.org).
351
     *
352
     * Note: In order to get a list of bookmarked (but not necessarily joined) conferences, use
353
     * {@link org.jivesoftware.smackx.bookmarks.BookmarkManager#getBookmarkedConferences()}.
354
     *
355
     * @return a List of the rooms where the user has joined using a given connection.
356
     */
357
    public Set<EntityBareJid> getJoinedRooms() {
358
        return Collections.unmodifiableSet(joinedRooms);
×
359
    }
360

361
    /**
362
     * Returns a List of the rooms where the requested user has joined. The Iterator will contain Strings where each
363
     * String represents a room (e.g. room@muc.jabber.org).
364
     *
365
     * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com.
366
     * @return a List of the rooms where the requested user has joined.
367
     * @throws XMPPErrorException if there was an XMPP error returned.
368
     * @throws NoResponseException if there was no response from the remote entity.
369
     * @throws NotConnectedException if the XMPP connection is not connected.
370
     * @throws InterruptedException if the calling thread was interrupted.
371
     */
372
    public List<EntityBareJid> getJoinedRooms(EntityFullJid user) throws NoResponseException, XMPPErrorException,
373
                    NotConnectedException, InterruptedException {
374
        // Send the disco packet to the user
375
        DiscoverItems result = serviceDiscoveryManager.discoverItems(user, DISCO_NODE);
×
376
        List<DiscoverItems.Item> items = result.getItems();
×
377
        List<EntityBareJid> answer = new ArrayList<>(items.size());
×
378
        // Collect the entityID for each returned item
379
        for (DiscoverItems.Item item : items) {
×
380
            EntityBareJid muc = item.getEntityID().asEntityBareJidIfPossible();
×
381
            if (muc == null) {
×
382
                LOGGER.warning("Not a bare JID: " + item.getEntityID());
×
383
                continue;
×
384
            }
385
            answer.add(muc);
×
386
        }
×
387
        return answer;
×
388
    }
389

390
    /**
391
     * Returns the discovered information of a given room without actually having to join the room. The server will
392
     * provide information only for rooms that are public.
393
     *
394
     * @param room the name of the room in the form "roomName@service" of which we want to discover its information.
395
     * @return the discovered information of a given room without actually having to join the room.
396
     * @throws XMPPErrorException if there was an XMPP error returned.
397
     * @throws NoResponseException if there was no response from the remote entity.
398
     * @throws NotConnectedException if the XMPP connection is not connected.
399
     * @throws InterruptedException if the calling thread was interrupted.
400
     */
401
    public RoomInfo getRoomInfo(EntityBareJid room) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
402
        DiscoverInfo info = serviceDiscoveryManager.discoverInfo(room);
×
403
        return new RoomInfo(info);
×
404
    }
405

406
    /**
407
     * Returns a collection with the XMPP addresses of the Multi-User Chat services.
408
     *
409
     * @return a collection with the XMPP addresses of the Multi-User Chat services.
410
     * @throws XMPPErrorException if there was an XMPP error returned.
411
     * @throws NoResponseException if there was no response from the remote entity.
412
     * @throws NotConnectedException if the XMPP connection is not connected.
413
     * @throws InterruptedException if the calling thread was interrupted.
414
     */
415
    public List<DomainBareJid> getMucServiceDomains() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
416
        return serviceDiscoveryManager.findServices(MUCInitialPresence.NAMESPACE, false, false);
×
417
    }
418

419
    /**
420
     * Returns a collection with the XMPP addresses of the Multi-User Chat services.
421
     *
422
     * @return a collection with the XMPP addresses of the Multi-User Chat services.
423
     * @throws XMPPErrorException if there was an XMPP error returned.
424
     * @throws NoResponseException if there was no response from the remote entity.
425
     * @throws NotConnectedException if the XMPP connection is not connected.
426
     * @throws InterruptedException if the calling thread was interrupted.
427
     * @deprecated use {@link #getMucServiceDomains()} instead.
428
     */
429
    // TODO: Remove in Smack 4.5
430
    @Deprecated
431
    public List<DomainBareJid> getXMPPServiceDomains() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
432
        return getMucServiceDomains();
×
433
    }
434

435
    /**
436
     * Check if the provided domain bare JID provides a MUC service.
437
     *
438
     * @param domainBareJid the domain bare JID to check.
439
     * @return <code>true</code> if the provided JID provides a MUC service, <code>false</code> otherwise.
440
     * @throws NoResponseException if there was no response from the remote entity.
441
     * @throws XMPPErrorException if there was an XMPP error returned.
442
     * @throws NotConnectedException if the XMPP connection is not connected.
443
     * @throws InterruptedException if the calling thread was interrupted.
444
     * @see <a href="http://xmpp.org/extensions/xep-0045.html#disco-service-features">XEP-45 ยง 6.2 Discovering the Features Supported by a MUC Service</a>
445
     * @since 4.2
446
     */
447
    public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException,
448
                    XMPPErrorException, NotConnectedException, InterruptedException {
449
        return getMucServiceDiscoInfo(domainBareJid) != null;
×
450
    }
451

452
    DiscoverInfo getMucServiceDiscoInfo(DomainBareJid mucServiceAddress)
453
                    throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
454
        DiscoverInfo discoInfo = KNOWN_MUC_SERVICES.get(mucServiceAddress);
×
455
        if (discoInfo != null) {
×
456
            return discoInfo;
×
457
        }
458

459
        discoInfo = serviceDiscoveryManager.discoverInfo(mucServiceAddress);
×
460
        if (!discoInfo.containsFeature(MUCInitialPresence.NAMESPACE)) {
×
461
            return null;
×
462
        }
463

464
        KNOWN_MUC_SERVICES.put(mucServiceAddress, discoInfo);
×
465
        return discoInfo;
×
466
    }
467

468
    /**
469
     * Returns a Map of HostedRooms where each HostedRoom has the XMPP address of the room and the room's name.
470
     * Once discovered the rooms hosted by a chat service it is possible to discover more detailed room information or
471
     * join the room.
472
     *
473
     * @param serviceName the service that is hosting the rooms to discover.
474
     * @return a map from the room's address to its HostedRoom information.
475
     * @throws XMPPErrorException if there was an XMPP error returned.
476
     * @throws NoResponseException if there was no response from the remote entity.
477
     * @throws NotConnectedException if the XMPP connection is not connected.
478
     * @throws InterruptedException if the calling thread was interrupted.
479
     * @throws NotAMucServiceException if the entity is not a MUC serivce.
480
     * @since 4.3.1
481
     */
482
    public Map<EntityBareJid, HostedRoom> getRoomsHostedBy(DomainBareJid serviceName) throws NoResponseException, XMPPErrorException,
483
                    NotConnectedException, InterruptedException, NotAMucServiceException {
484
        if (!providesMucService(serviceName)) {
×
485
            throw new NotAMucServiceException(serviceName);
×
486
        }
487
        DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(serviceName);
×
488
        List<DiscoverItems.Item> items = discoverItems.getItems();
×
489

490
        Map<EntityBareJid, HostedRoom> answer = new HashMap<>(items.size());
×
491
        for (DiscoverItems.Item item : items) {
×
492
            HostedRoom hostedRoom = new HostedRoom(item);
×
493
            HostedRoom previousRoom = answer.put(hostedRoom.getJid(), hostedRoom);
×
494
            assert previousRoom == null;
×
495
        }
×
496

497
        return answer;
×
498
    }
499

500
    /**
501
     * Informs the sender of an invitation that the invitee declines the invitation. The rejection will be sent to the
502
     * room which in turn will forward the rejection to the inviter.
503
     *
504
     * @param room the room that sent the original invitation.
505
     * @param inviter the inviter of the declined invitation.
506
     * @param reason the reason why the invitee is declining the invitation.
507
     * @throws NotConnectedException if the XMPP connection is not connected.
508
     * @throws InterruptedException if the calling thread was interrupted.
509
     */
510
    public void decline(EntityBareJid room, EntityBareJid inviter, String reason) throws NotConnectedException, InterruptedException {
511
        XMPPConnection connection = connection();
×
512

513
        MessageBuilder messageBuilder = connection.getStanzaFactory().buildMessageStanza().to(room);
×
514

515
        // Create the MUCUser packet that will include the rejection
516
        MUCUser mucUser = new MUCUser();
×
517
        MUCUser.Decline decline = new MUCUser.Decline(reason, inviter);
×
518
        mucUser.setDecline(decline);
×
519
        // Add the MUCUser packet that includes the rejection
520
        messageBuilder.addExtension(mucUser);
×
521

522
        connection.sendStanza(messageBuilder.build());
×
523
    }
×
524

525
    /**
526
     * Adds a listener to invitation notifications. The listener will be fired anytime an invitation is received.
527
     *
528
     * @param listener an invitation listener.
529
     */
530
    public void addInvitationListener(InvitationListener listener) {
531
        invitationsListeners.add(listener);
1✔
532
    }
1✔
533

534
    /**
535
     * Removes a listener to invitation notifications. The listener will be fired anytime an invitation is received.
536
     *
537
     * @param listener an invitation listener.
538
     */
539
    public void removeInvitationListener(InvitationListener listener) {
540
        invitationsListeners.remove(listener);
×
541
    }
×
542

543
    /**
544
     * If automatic join on reconnect is enabled, then the manager will try to auto join MUC rooms after the connection
545
     * got re-established.
546
     *
547
     * @param autoJoin <code>true</code> to enable, <code>false</code> to disable.
548
     */
549
    public void setAutoJoinOnReconnect(boolean autoJoin) {
550
        autoJoinOnReconnect = autoJoin;
×
551
    }
×
552

553
    /**
554
     * Set a callback invoked by this manager when automatic join on reconnect failed. If failedCallback is not
555
     * <code>null</code>, then automatic rejoin get also enabled.
556
     *
557
     * @param failedCallback the callback.
558
     */
559
    public void setAutoJoinFailedCallback(AutoJoinFailedCallback failedCallback) {
560
        autoJoinFailedCallback = failedCallback;
×
561
        if (failedCallback != null) {
×
562
            setAutoJoinOnReconnect(true);
×
563
        }
564
    }
×
565

566
    /**
567
     * Set a callback invoked by this manager when automatic join on reconnect success.
568
     * If successCallback is not <code>null</code>, automatic rejoin will also
569
     * be enabled.
570
     *
571
     * @param successCallback the callback
572
     */
573
    public void setAutoJoinSuccessCallback(AutoJoinSuccessCallback successCallback) {
574
        autoJoinSuccessCallback = successCallback;
×
575
        if (successCallback != null) {
×
576
            setAutoJoinOnReconnect(true);
×
577
        }
578
    }
×
579

580

581
    void addJoinedRoom(EntityBareJid room) {
582
        joinedRooms.add(room);
×
583
    }
×
584

585
    void removeJoinedRoom(EntityBareJid room) {
586
        joinedRooms.remove(room);
×
587
    }
×
588

589
    static CopyOnWriteArrayList<MucMessageInterceptor> getMessageInterceptors() {
590
        synchronized (DEFAULT_MESSAGE_INTERCEPTORS) {
1✔
591
            return new CopyOnWriteArrayList<>(DEFAULT_MESSAGE_INTERCEPTORS);
1✔
592
        }
593
    }
594
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc