Search in sources :

Example 31 with NodeID

use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.

the class RoutingTableImpl method addComponentRoute.

@Override
public void addComponentRoute(JID route, RoutableChannelHandler destination) {
    DomainPair pair = new DomainPair("", route.getDomain());
    String address = route.getDomain();
    localRoutingTable.addRoute(pair, destination);
    Lock lock = componentsCache.getLock(address);
    lock.lock();
    try {
        HashSet<NodeID> nodes = componentsCache.get(address);
        if (nodes == null) {
            nodes = new HashSet<>();
        }
        nodes.add(server.getNodeID());
        componentsCache.put(address, nodes);
    } finally {
        lock.unlock();
    }
}
Also used : DomainPair(org.jivesoftware.openfire.session.DomainPair) NodeID(org.jivesoftware.openfire.cluster.NodeID) Lock(java.util.concurrent.locks.Lock)

Example 32 with NodeID

use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.

the class RoutingTableImpl method routeToComponent.

/**
 * Routes packets that are sent to components of the XMPP domain (which are
 * subdomains of the XMPP domain)
 *
 * @param jid
 *            the recipient of the packet to route.
 * @param packet
 *            the packet to route.
 * @throws PacketException
 *             thrown if the packet is malformed (results in the sender's
 *             session being shutdown).
 * @return {@code true} if the packet was routed successfully,
 *         {@code false} otherwise.
 */
private boolean routeToComponent(JID jid, Packet packet) {
    if (!hasComponentRoute(jid) && !ExternalComponentManager.hasConfiguration(jid.getDomain())) {
        return false;
    }
    // First check if the component is being hosted in this JVM
    boolean routed = false;
    RoutableChannelHandler route = localRoutingTable.getRoute(new JID(null, jid.getDomain(), null, true));
    if (route != null) {
        try {
            route.process(packet);
            routed = true;
        } catch (UnauthorizedException e) {
            Log.error("Unable to route packet " + packet.toXML(), e);
        }
    } else {
        // Check if other cluster nodes are hosting this component
        Set<NodeID> nodes = componentsCache.get(jid.getDomain());
        if (nodes != null) {
            for (NodeID nodeID : nodes) {
                if (server.getNodeID().equals(nodeID)) {
                    // could have been added after our previous check)
                    try {
                        RoutableChannelHandler localRoute = localRoutingTable.getRoute(new JID(null, jid.getDomain(), null, true));
                        if (localRoute != null) {
                            localRoute.process(packet);
                            routed = true;
                            break;
                        }
                    } catch (UnauthorizedException e) {
                        Log.error("Unable to route packet " + packet.toXML(), e);
                    }
                } else {
                    // This is a route to a local component hosted in other node
                    if (remotePacketRouter != null) {
                        routed = remotePacketRouter.routePacket(nodeID.toByteArray(), jid, packet);
                        if (routed) {
                            break;
                        }
                    }
                }
            }
        }
    }
    return routed;
}
Also used : JID(org.xmpp.packet.JID) UnauthorizedException(org.jivesoftware.openfire.auth.UnauthorizedException) NodeID(org.jivesoftware.openfire.cluster.NodeID) RoutableChannelHandler(org.jivesoftware.openfire.RoutableChannelHandler)

Example 33 with NodeID

use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.

the class RoutingTableImpl method joinedCluster.

@Override
public void joinedCluster() {
    // The local node joined a cluster.
    // 
    // Upon joining a cluster, clustered caches are reset to their clustered equivalent (by the swap from the local
    // cache implementation to the clustered cache implementation that's done in the implementation of
    // org.jivesoftware.util.cache.CacheFactory.joinedCluster). This means that they now hold data that's
    // available on all other cluster nodes. Data that's available on the local node needs to be added again.
    restoreCacheContent();
    Log.debug("Add the entry listeners to the corresponding caches.");
    // Register a cache entry event listeners that will collect data for entries added by all other cluster nodes,
    // which is intended to be used (only) in the event of a cluster split.
    final ClusteredCacheEntryListener<String, ClientRoute> userCacheEntryListener = new ReverseLookupUpdatingCacheEntryListener<>(routeOwnersByClusterNode);
    final ClusteredCacheEntryListener<DomainPair, NodeID> serversCacheEntryListener = new ReverseLookupUpdatingCacheEntryListener<>(s2sDomainPairsByClusterNode);
    final ClusteredCacheEntryListener<String, HashSet<NodeID>> componentsCacheEntryListener = new ReverseLookupComputingCacheEntryListener<>(componentsByClusterNode, nodeIDS -> nodeIDS.stream().filter(n -> !n.equals(XMPPServer.getInstance().getNodeID())).collect(Collectors.toSet()));
    // Note that, when #joinedCluster() fired, the cache will _always_ have been replaced, meaning that it won't
    // have old event listeners. When #leaveCluster() fires, the cache will be destroyed. This takes away the need
    // to explicitly deregister the listener in that case.
    // Ensure that event listeners have been registered with the caches, before starting to simulate 'entryAdded' events,
    // to prevent the possibility of having entries that are missed by the simulation because of bad timing.
    usersCache.addClusteredCacheEntryListener(userCacheEntryListener, false, false);
    anonymousUsersCache.addClusteredCacheEntryListener(userCacheEntryListener, false, false);
    serversCache.addClusteredCacheEntryListener(serversCacheEntryListener, false, false);
    componentsCache.addClusteredCacheEntryListener(componentsCacheEntryListener, true, true);
    // This is not necessary for the usersSessions cache, because its content is being managed while the content
    // of users cache and anonymous users cache is being managed.
    Log.debug("Simulate 'entryAdded' for all data that already exists elsewhere in the cluster.");
    Stream.concat(usersCache.entrySet().stream(), anonymousUsersCache.entrySet().stream()).filter(entry -> !entry.getValue().getNodeID().equals(XMPPServer.getInstance().getNodeID())).forEach(entry -> userCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), entry.getValue().getNodeID()));
    serversCache.entrySet().stream().filter(entry -> !entry.getValue().equals(XMPPServer.getInstance().getNodeID())).forEach(entry -> serversCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), entry.getValue()));
    componentsCache.entrySet().forEach(entry -> {
        entry.getValue().forEach(nodeIdForComponent -> {
            // Iterate over all node ids on which the component is known
            if (!nodeIdForComponent.equals(XMPPServer.getInstance().getNodeID())) {
                // Here we pretend that the component has been added by the node id on which it is reported to
                // be available. This might not have been the case, but it is probably accurate. An alternative
                // approach is not easily available.
                componentsCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), nodeIdForComponent);
            }
        });
    });
    // Broadcast presence of local sessions to remote sessions when subscribed to presence.
    // Probe presences of remote sessions when subscribed to presence of local session.
    // Send pending subscription requests to local sessions from remote sessions.
    // Deliver offline messages sent to local sessions that were unavailable in other nodes.
    // Send available presences of local sessions to other resources of the same user.
    PresenceUpdateHandler presenceUpdateHandler = XMPPServer.getInstance().getPresenceUpdateHandler();
    for (LocalClientSession session : localRoutingTable.getClientRoutes()) {
        // Simulate that the local session has just become available
        session.setInitialized(false);
        // Simulate that current session presence has just been received
        presenceUpdateHandler.process(session.getPresence());
    }
// TODO OF-2067: the above also (re)generates events on the local node, where these events had already occurred. Ideally, that should not happen.
// TODO OF-2066: shouldn't a similar action be done on the other nodes, so that the node that just joined gets informed about all sessions living on other cluster nodes?
}
Also used : Presence(org.xmpp.packet.Presence) LocalClientSession(org.jivesoftware.openfire.session.LocalClientSession) Forwarded(org.jivesoftware.openfire.forward.Forwarded) ClientSession(org.jivesoftware.openfire.session.ClientSession) Received(org.jivesoftware.openfire.carbons.Received) BasicModule(org.jivesoftware.openfire.container.BasicModule) CacheFactory(org.jivesoftware.util.cache.CacheFactory) ClusteredCacheEntryListener(org.jivesoftware.openfire.cluster.ClusteredCacheEntryListener) LoggerFactory(org.slf4j.LoggerFactory) JiveGlobals(org.jivesoftware.util.JiveGlobals) MessageRouter(org.jivesoftware.openfire.MessageRouter) ReverseLookupUpdatingCacheEntryListener(org.jivesoftware.util.cache.ReverseLookupUpdatingCacheEntryListener) PresenceUpdateHandler(org.jivesoftware.openfire.handler.PresenceUpdateHandler) Message(org.xmpp.packet.Message) OutgoingServerSession(org.jivesoftware.openfire.session.OutgoingServerSession) CacheUtil(org.jivesoftware.util.cache.CacheUtil) RemoteServerManager(org.jivesoftware.openfire.server.RemoteServerManager) Cache(org.jivesoftware.util.cache.Cache) RoutingTable(org.jivesoftware.openfire.RoutingTable) PresenceRouter(org.jivesoftware.openfire.PresenceRouter) ClusterManager(org.jivesoftware.openfire.cluster.ClusterManager) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) Collectors(java.util.stream.Collectors) Stream(java.util.stream.Stream) ConnectionSettings(org.jivesoftware.openfire.session.ConnectionSettings) RemoteSessionLocator(org.jivesoftware.openfire.session.RemoteSessionLocator) java.util(java.util) PacketException(org.jivesoftware.openfire.PacketException) Multimap(com.google.common.collect.Multimap) ReverseLookupComputingCacheEntryListener(org.jivesoftware.util.cache.ReverseLookupComputingCacheEntryListener) JID(org.xmpp.packet.JID) Function(java.util.function.Function) UnauthorizedException(org.jivesoftware.openfire.auth.UnauthorizedException) ConcurrentMap(java.util.concurrent.ConcurrentMap) NodeID(org.jivesoftware.openfire.cluster.NodeID) XMPPServer(org.jivesoftware.openfire.XMPPServer) RoutableChannelHandler(org.jivesoftware.openfire.RoutableChannelHandler) RemotePacketRouter(org.jivesoftware.openfire.RemotePacketRouter) ClusterEventListener(org.jivesoftware.openfire.cluster.ClusterEventListener) DomainPair(org.jivesoftware.openfire.session.DomainPair) LocalOutgoingServerSession(org.jivesoftware.openfire.session.LocalOutgoingServerSession) Logger(org.slf4j.Logger) ConsistencyChecks(org.jivesoftware.util.cache.ConsistencyChecks) ExternalComponentManager(org.jivesoftware.openfire.component.ExternalComponentManager) AtomicLong(java.util.concurrent.atomic.AtomicLong) Lock(java.util.concurrent.locks.Lock) Packet(org.xmpp.packet.Packet) OutgoingSessionPromise(org.jivesoftware.openfire.server.OutgoingSessionPromise) Element(org.dom4j.Element) QName(org.dom4j.QName) IQRouter(org.jivesoftware.openfire.IQRouter) IQ(org.xmpp.packet.IQ) ReverseLookupUpdatingCacheEntryListener(org.jivesoftware.util.cache.ReverseLookupUpdatingCacheEntryListener) PresenceUpdateHandler(org.jivesoftware.openfire.handler.PresenceUpdateHandler) LocalClientSession(org.jivesoftware.openfire.session.LocalClientSession) DomainPair(org.jivesoftware.openfire.session.DomainPair) NodeID(org.jivesoftware.openfire.cluster.NodeID) ReverseLookupComputingCacheEntryListener(org.jivesoftware.util.cache.ReverseLookupComputingCacheEntryListener)

Example 34 with NodeID

use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.

the class MultiUserChatServiceImpl method makeOccupantsOnConnectedClusterNodeJoin.

/**
 * Sends presence 'join' stanzas on behalf of chatroom occupants that are now connected to the cluster (by virtue
 * of a cluster node joining the cluster) to other room occupants are local to this cluster node.
 *
 * Note that this method does not change state (in either the clustered cache, or local equivalents): it only sends stanzas.
 *
 * @param occupantsOnConnectedNodes The occupants for which to send stanzas.
 * @param remoteNodeID The cluster node ID of the server that joined the cluster.
 */
private void makeOccupantsOnConnectedClusterNodeJoin(@Nullable final Set<OccupantManager.Occupant> occupantsOnConnectedNodes, @Nonnull NodeID remoteNodeID) {
    Log.debug("Going to send 'join' presence stanzas on the local cluster node for {} occupant(s) of cluster node {} that is now part of our cluster.", occupantsOnConnectedNodes == null || occupantsOnConnectedNodes.isEmpty() ? 0 : occupantsOnConnectedNodes.size(), remoteNodeID);
    if (occupantsOnConnectedNodes == null || occupantsOnConnectedNodes.isEmpty()) {
        return;
    }
    if (!MUCRoom.JOIN_PRESENCE_ENABLE.getValue()) {
        Log.trace("Skipping, as presences are disabled by configuration.");
        return;
    }
    // Find all occupants that were not already on any other node in the cluster. This intends to prevent sending 'join'
    // presences for occupants that are in the same room, using the same nickname, but using a client that is
    // connected to a cluster node that already was in the cluster.
    final Set<OccupantManager.Occupant> toAdd = occupantsOnConnectedNodes.stream().filter(occupant -> !occupantManager.exists(occupant, remoteNodeID)).collect(Collectors.toSet());
    Log.trace("After accounting for occupants already in the room by virtue of having another connected device using the same nickname, {} presence stanza(s) are to be sent.", toAdd.size());
    // For each, broadcast a 'join' presence in the room(s).
    for (final OccupantManager.Occupant occupant : toAdd) {
        Log.trace("Preparing to send 'join' stanza for occupant '{}' (using nickname '{}') of room '{}'", occupant.getRealJID(), occupant.getNickname(), occupant.getRoomName());
        final MUCRoom chatRoom = getChatRoom(occupant.roomName);
        if (chatRoom == null) {
            Log.info("User {} seems to be an occupant (using nickname '{}') of a non-existent room named '{}' on newly connected cluster node(s).", occupant.realJID, occupant.nickname, occupant.roomName);
            continue;
        }
        // At this stage, the occupant information must already be available through the clustered cache.
        final MUCRole occupantRole = chatRoom.getOccupantByFullJID(occupant.getRealJID());
        if (occupantRole == null) {
            Log.warn("A remote cluster node ({}) tells us that user {} is supposed to be an occupant (using nickname '{}') of a room named '{}' but the data in the cluster cache does not indicate that this is true.", remoteNodeID, occupant.realJID, occupant.nickname, occupant.roomName);
            continue;
        }
        if (!chatRoom.canBroadcastPresence(occupantRole.getRole())) {
            Log.trace("Skipping join stanza, as room is configured to not broadcast presence for role {}", occupantRole.getRole());
            continue;
        }
        // To prevent each (remaining) cluster node from broadcasting the same presence to all occupants of all cluster nodes,
        // this broadcasts only to occupants on the local node.
        final Set<OccupantManager.Occupant> recipients = occupantManager.occupantsForRoomByNode(occupant.roomName, XMPPServer.getInstance().getNodeID());
        for (OccupantManager.Occupant recipient : recipients) {
            Log.trace("Preparing stanza for recipient {} (nickname: {})", recipient.getRealJID(), recipient.getNickname());
            try {
                // Note that we cannot use org.jivesoftware.openfire.muc.MUCRoom.broadcastPresence() as this would attempt to
                // broadcast to the user that is 'joining' to all users. We only want to broadcast locally here.
                // We _need_ to go through the MUCRole for sending this stanza, as that has some additional logic (eg: FMUC).
                final MUCRole recipientRole = chatRoom.getOccupantByFullJID(recipient.getRealJID());
                if (recipientRole != null) {
                    recipientRole.send(occupantRole.getPresence());
                } else {
                    Log.warn("Unable to find MUCRole for recipient '{}' in room {} while broadcasting 'join' presence for occupants on joining cluster node {}.", recipient.getRealJID(), chatRoom.getJID(), remoteNodeID);
                    // This is a partial fix that will _probably_ work if FMUC is not used. Better than nothing? (although an argument for failing-fast can be made).
                    XMPPServer.getInstance().getPacketRouter().route(occupantRole.getPresence());
                }
            } catch (Exception e) {
                Log.warn("A problem occurred while notifying local occupant that user '{}' joined room '{}' as a result of a cluster node {} joining the cluster.", occupant.nickname, occupant.roomName, remoteNodeID, e);
            }
        }
    }
    Log.debug("Finished sending 'join' presence stanzas on the local cluster node.");
}
Also used : Presence(org.xmpp.packet.Presence) GroupJID(org.jivesoftware.openfire.group.GroupJID) CacheFactory(org.jivesoftware.util.cache.CacheFactory) LoggerFactory(org.slf4j.LoggerFactory) Type(org.xmpp.forms.DataForm.Type) ComponentManager(org.xmpp.component.ComponentManager) JiveGlobals(org.jivesoftware.util.JiveGlobals) Message(org.xmpp.packet.Message) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Archiver(org.jivesoftware.openfire.archive.Archiver) ForbiddenException(org.jivesoftware.openfire.muc.ForbiddenException) Duration(java.time.Duration) SyncLocalOccupantsAndSendJoinPresenceTask(org.jivesoftware.openfire.muc.cluster.SyncLocalOccupantsAndSendJoinPresenceTask) ResultSet(org.xmpp.resultsetmanagement.ResultSet) Cache(org.jivesoftware.util.cache.Cache) RoutingTable(org.jivesoftware.openfire.RoutingTable) IQHandler(org.jivesoftware.openfire.handler.IQHandler) StanzaIDUtil(org.jivesoftware.openfire.stanzaid.StanzaIDUtil) ClusterManager(org.jivesoftware.openfire.cluster.ClusterManager) MUCRoom(org.jivesoftware.openfire.muc.MUCRoom) Instant(java.time.Instant) Collectors(java.util.stream.Collectors) AutoCloseableReentrantLock(org.jivesoftware.util.AutoCloseableReentrantLock) Executors(java.util.concurrent.Executors) DiscoItem(org.jivesoftware.openfire.disco.DiscoItem) MUCRole(org.jivesoftware.openfire.muc.MUCRole) Stream(java.util.stream.Stream) FormField(org.xmpp.forms.FormField) IQDiscoInfoHandler(org.jivesoftware.openfire.disco.IQDiscoInfoHandler) RoomLockedException(org.jivesoftware.openfire.muc.RoomLockedException) LocaleUtils(org.jivesoftware.util.LocaleUtils) HistoryRequest(org.jivesoftware.openfire.muc.HistoryRequest) java.util(java.util) CannotBeInvitedException(org.jivesoftware.openfire.muc.CannotBeInvitedException) MUCEventDelegate(org.jivesoftware.openfire.muc.MUCEventDelegate) MultiUserChatService(org.jivesoftware.openfire.muc.MultiUserChatService) PacketError(org.xmpp.packet.PacketError) TaskEngine(org.jivesoftware.util.TaskEngine) DocumentHelper(org.dom4j.DocumentHelper) MUCEventDispatcher(org.jivesoftware.openfire.muc.MUCEventDispatcher) NotAllowedException(org.jivesoftware.openfire.muc.NotAllowedException) PacketException(org.jivesoftware.openfire.PacketException) IQPingHandler(org.jivesoftware.openfire.handler.IQPingHandler) JID(org.xmpp.packet.JID) UnauthorizedException(org.jivesoftware.openfire.auth.UnauthorizedException) DiscoItemsProvider(org.jivesoftware.openfire.disco.DiscoItemsProvider) XMPPDateTimeFormat(org.jivesoftware.util.XMPPDateTimeFormat) UserManager(org.jivesoftware.openfire.user.UserManager) NodeID(org.jivesoftware.openfire.cluster.NodeID) NotFoundException(org.jivesoftware.util.NotFoundException) XMPPServerListener(org.jivesoftware.openfire.XMPPServerListener) XMPPServer(org.jivesoftware.openfire.XMPPServer) DiscoInfoProvider(org.jivesoftware.openfire.disco.DiscoInfoProvider) Nonnull(javax.annotation.Nonnull) ClusterEventListener(org.jivesoftware.openfire.cluster.ClusterEventListener) Nullable(javax.annotation.Nullable) ExecutorService(java.util.concurrent.ExecutorService) ConflictException(org.jivesoftware.openfire.muc.ConflictException) HistoryStrategy(org.jivesoftware.openfire.muc.HistoryStrategy) Logger(org.slf4j.Logger) RegistrationRequiredException(org.jivesoftware.openfire.muc.RegistrationRequiredException) ConcurrentGroupList(org.jivesoftware.openfire.group.ConcurrentGroupList) UserAlreadyExistsException(org.jivesoftware.openfire.user.UserAlreadyExistsException) ServerItemsProvider(org.jivesoftware.openfire.disco.ServerItemsProvider) UserNotFoundException(org.jivesoftware.openfire.user.UserNotFoundException) TimeUnit(java.util.concurrent.TimeUnit) DiscoServerItem(org.jivesoftware.openfire.disco.DiscoServerItem) AtomicLong(java.util.concurrent.atomic.AtomicLong) Lock(java.util.concurrent.locks.Lock) Component(org.xmpp.component.Component) Packet(org.xmpp.packet.Packet) GroupAwareList(org.jivesoftware.openfire.group.GroupAwareList) NotAcceptableException(org.jivesoftware.openfire.muc.NotAcceptableException) ServiceUnavailableException(org.jivesoftware.openfire.muc.ServiceUnavailableException) Element(org.dom4j.Element) QName(org.dom4j.QName) JiveProperties(org.jivesoftware.util.JiveProperties) IQ(org.xmpp.packet.IQ) DataForm(org.xmpp.forms.DataForm) MUCRole(org.jivesoftware.openfire.muc.MUCRole) MUCRoom(org.jivesoftware.openfire.muc.MUCRoom) ForbiddenException(org.jivesoftware.openfire.muc.ForbiddenException) RoomLockedException(org.jivesoftware.openfire.muc.RoomLockedException) CannotBeInvitedException(org.jivesoftware.openfire.muc.CannotBeInvitedException) NotAllowedException(org.jivesoftware.openfire.muc.NotAllowedException) PacketException(org.jivesoftware.openfire.PacketException) UnauthorizedException(org.jivesoftware.openfire.auth.UnauthorizedException) NotFoundException(org.jivesoftware.util.NotFoundException) ConflictException(org.jivesoftware.openfire.muc.ConflictException) RegistrationRequiredException(org.jivesoftware.openfire.muc.RegistrationRequiredException) UserAlreadyExistsException(org.jivesoftware.openfire.user.UserAlreadyExistsException) UserNotFoundException(org.jivesoftware.openfire.user.UserNotFoundException) NotAcceptableException(org.jivesoftware.openfire.muc.NotAcceptableException) ServiceUnavailableException(org.jivesoftware.openfire.muc.ServiceUnavailableException)

Example 35 with NodeID

use of org.jivesoftware.openfire.cluster.NodeID in project Openfire by igniterealtime.

the class MultiUserChatServiceImpl method makeOccupantsOnDisconnectedClusterNodesLeave.

/**
 * Sends presence 'leave' stanzas on behalf of chatroom occupants that are no longer connected to the cluster to
 * room occupants that are local to this cluster node.
 *
 * Note that this method does not change state (in either the clustered cache, or local equivalents): it only sends stanzas.
 *
 * @param occupantsOnRemovedNodes The occupants for which to send stanzas
 */
private void makeOccupantsOnDisconnectedClusterNodesLeave(@Nullable final Set<OccupantManager.Occupant> occupantsOnRemovedNodes, NodeID nodeID) {
    Log.debug("Going to send 'leave' presence stanzas on the local cluster node for {} occupant(s) of one or more cluster nodes that are no longer part of our cluster.", occupantsOnRemovedNodes == null || occupantsOnRemovedNodes.isEmpty() ? 0 : occupantsOnRemovedNodes.size());
    if (occupantsOnRemovedNodes == null || occupantsOnRemovedNodes.isEmpty()) {
        return;
    }
    if (!MUCRoom.JOIN_PRESENCE_ENABLE.getValue()) {
        Log.trace("Skipping, as presences are disabled by configuration.");
        return;
    }
    // Find all occupants that are now no longer on any node in the cluster. This intends to prevent sending 'leave'
    // presences for occupants that are in the same room, using the same nickname, but using a client that is
    // connected to a cluster node that is still in the cluster.
    final Set<OccupantManager.Occupant> toRemove = occupantsOnRemovedNodes.stream().filter(occupant -> !occupantManager.exists(occupant)).collect(Collectors.toSet());
    Log.trace("After accounting for occupants still in the room by virtue of having another connected device using the same nickname, {} presence stanza(s) are to be sent.", toRemove.size());
    // For each, broadcast a 'leave' presence in the room(s).
    for (final OccupantManager.Occupant occupant : toRemove) {
        Log.trace("Preparing to send 'leave' stanza for occupant '{}' (using nickname '{}') of room '{}'", occupant.getRealJID(), occupant.getNickname(), occupant.getRoomName());
        final MUCRoom chatRoom = getChatRoom(occupant.getRoomName());
        if (chatRoom == null) {
            Log.info("User {} seems to be an occupant (using nickname '{}') of a non-existent room named '{}' on disconnected cluster node(s).", occupant.getRealJID(), occupant.getNickname(), occupant.getRoomName());
            continue;
        }
        // To prevent each (remaining) cluster node from broadcasting the same presence to all occupants of all remaining nodes,
        // this broadcasts only to occupants on the local node.
        final Set<OccupantManager.Occupant> recipients;
        if (nodeID == null) {
            recipients = occupantManager.occupantsForRoomByNode(occupant.getRoomName(), XMPPServer.getInstance().getNodeID());
            Log.trace("Intended recipients, count: {} (occupants of the same room, on local node): {}", recipients.size(), recipients.stream().map(OccupantManager.Occupant::getRealJID).map(JID::toString).collect(Collectors.joining(", ")));
        } else {
            recipients = occupantManager.occupantsForRoomExceptForNode(occupant.getRoomName(), nodeID);
            Log.trace("Intended recipients, count: {} (occupants of the same room, on all remaining cluster nodes): {}", recipients.size(), recipients.stream().map(OccupantManager.Occupant::getRealJID).map(JID::toString).collect(Collectors.joining(", ")));
        }
        for (OccupantManager.Occupant recipient : recipients) {
            try {
                Log.trace("Preparing stanza for recipient {} (nickname: {})", recipient.getRealJID(), recipient.getNickname());
                // Note that we cannot use chatRoom.sendLeavePresenceToExistingOccupants(leaveRole) as this would attempt to
                // broadcast to the user that is leaving. That user is clearly unreachable in this instance (as it lives on
                // a now disconnected cluster node.
                final Presence presence = new Presence(Presence.Type.unavailable);
                presence.setTo(recipient.getRealJID());
                presence.setFrom(new JID(chatRoom.getJID().getNode(), chatRoom.getJID().getDomain(), occupant.nickname));
                final Element childElement = presence.addChildElement("x", "http://jabber.org/protocol/muc#user");
                final Element item = childElement.addElement("item");
                item.addAttribute("role", "none");
                if (chatRoom.canAnyoneDiscoverJID() || chatRoom.getModerators().stream().anyMatch(m -> m.getUserAddress().asBareJID().equals(recipient.getRealJID().asBareJID()))) {
                    // Send non-anonymous - add JID.
                    item.addAttribute("jid", occupant.getRealJID().toString());
                }
                childElement.addElement("status").addAttribute("code", "333");
                // We _need_ to go through the MUCRole for sending this stanza, as that has some additional logic (eg: FMUC).
                final MUCRole recipientRole = chatRoom.getOccupantByFullJID(recipient.getRealJID());
                Log.debug("Stanza now being sent: {}", presence.toXML());
                if (recipientRole != null) {
                    recipientRole.send(presence);
                } else {
                    Log.warn("Unable to find MUCRole for recipient '{}' in room {} while broadcasting 'leave' presence for occupants on disconnected cluster node(s).", recipient.getRealJID(), chatRoom.getJID());
                    // This is a partial fix that will _probably_ work if FMUC is not used. Better than nothing? (although an argument for failing-fast can be made).
                    XMPPServer.getInstance().getPacketRouter().route(presence);
                }
            } catch (Exception e) {
                Log.warn("A problem occurred while notifying local occupant that user '{}' left room '{}' as a result of a cluster disconnect.", occupant.nickname, occupant.roomName, e);
            }
        }
    }
    Log.debug("Finished sending 'leave' presence stanzas on the local cluster node.");
}
Also used : Presence(org.xmpp.packet.Presence) GroupJID(org.jivesoftware.openfire.group.GroupJID) CacheFactory(org.jivesoftware.util.cache.CacheFactory) LoggerFactory(org.slf4j.LoggerFactory) Type(org.xmpp.forms.DataForm.Type) ComponentManager(org.xmpp.component.ComponentManager) JiveGlobals(org.jivesoftware.util.JiveGlobals) Message(org.xmpp.packet.Message) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Archiver(org.jivesoftware.openfire.archive.Archiver) ForbiddenException(org.jivesoftware.openfire.muc.ForbiddenException) Duration(java.time.Duration) SyncLocalOccupantsAndSendJoinPresenceTask(org.jivesoftware.openfire.muc.cluster.SyncLocalOccupantsAndSendJoinPresenceTask) ResultSet(org.xmpp.resultsetmanagement.ResultSet) Cache(org.jivesoftware.util.cache.Cache) RoutingTable(org.jivesoftware.openfire.RoutingTable) IQHandler(org.jivesoftware.openfire.handler.IQHandler) StanzaIDUtil(org.jivesoftware.openfire.stanzaid.StanzaIDUtil) ClusterManager(org.jivesoftware.openfire.cluster.ClusterManager) MUCRoom(org.jivesoftware.openfire.muc.MUCRoom) Instant(java.time.Instant) Collectors(java.util.stream.Collectors) AutoCloseableReentrantLock(org.jivesoftware.util.AutoCloseableReentrantLock) Executors(java.util.concurrent.Executors) DiscoItem(org.jivesoftware.openfire.disco.DiscoItem) MUCRole(org.jivesoftware.openfire.muc.MUCRole) Stream(java.util.stream.Stream) FormField(org.xmpp.forms.FormField) IQDiscoInfoHandler(org.jivesoftware.openfire.disco.IQDiscoInfoHandler) RoomLockedException(org.jivesoftware.openfire.muc.RoomLockedException) LocaleUtils(org.jivesoftware.util.LocaleUtils) HistoryRequest(org.jivesoftware.openfire.muc.HistoryRequest) java.util(java.util) CannotBeInvitedException(org.jivesoftware.openfire.muc.CannotBeInvitedException) MUCEventDelegate(org.jivesoftware.openfire.muc.MUCEventDelegate) MultiUserChatService(org.jivesoftware.openfire.muc.MultiUserChatService) PacketError(org.xmpp.packet.PacketError) TaskEngine(org.jivesoftware.util.TaskEngine) DocumentHelper(org.dom4j.DocumentHelper) MUCEventDispatcher(org.jivesoftware.openfire.muc.MUCEventDispatcher) NotAllowedException(org.jivesoftware.openfire.muc.NotAllowedException) PacketException(org.jivesoftware.openfire.PacketException) IQPingHandler(org.jivesoftware.openfire.handler.IQPingHandler) JID(org.xmpp.packet.JID) UnauthorizedException(org.jivesoftware.openfire.auth.UnauthorizedException) DiscoItemsProvider(org.jivesoftware.openfire.disco.DiscoItemsProvider) XMPPDateTimeFormat(org.jivesoftware.util.XMPPDateTimeFormat) UserManager(org.jivesoftware.openfire.user.UserManager) NodeID(org.jivesoftware.openfire.cluster.NodeID) NotFoundException(org.jivesoftware.util.NotFoundException) XMPPServerListener(org.jivesoftware.openfire.XMPPServerListener) XMPPServer(org.jivesoftware.openfire.XMPPServer) DiscoInfoProvider(org.jivesoftware.openfire.disco.DiscoInfoProvider) Nonnull(javax.annotation.Nonnull) ClusterEventListener(org.jivesoftware.openfire.cluster.ClusterEventListener) Nullable(javax.annotation.Nullable) ExecutorService(java.util.concurrent.ExecutorService) ConflictException(org.jivesoftware.openfire.muc.ConflictException) HistoryStrategy(org.jivesoftware.openfire.muc.HistoryStrategy) Logger(org.slf4j.Logger) RegistrationRequiredException(org.jivesoftware.openfire.muc.RegistrationRequiredException) ConcurrentGroupList(org.jivesoftware.openfire.group.ConcurrentGroupList) UserAlreadyExistsException(org.jivesoftware.openfire.user.UserAlreadyExistsException) ServerItemsProvider(org.jivesoftware.openfire.disco.ServerItemsProvider) UserNotFoundException(org.jivesoftware.openfire.user.UserNotFoundException) TimeUnit(java.util.concurrent.TimeUnit) DiscoServerItem(org.jivesoftware.openfire.disco.DiscoServerItem) AtomicLong(java.util.concurrent.atomic.AtomicLong) Lock(java.util.concurrent.locks.Lock) Component(org.xmpp.component.Component) Packet(org.xmpp.packet.Packet) GroupAwareList(org.jivesoftware.openfire.group.GroupAwareList) NotAcceptableException(org.jivesoftware.openfire.muc.NotAcceptableException) ServiceUnavailableException(org.jivesoftware.openfire.muc.ServiceUnavailableException) Element(org.dom4j.Element) QName(org.dom4j.QName) JiveProperties(org.jivesoftware.util.JiveProperties) IQ(org.xmpp.packet.IQ) DataForm(org.xmpp.forms.DataForm) MUCRole(org.jivesoftware.openfire.muc.MUCRole) MUCRoom(org.jivesoftware.openfire.muc.MUCRoom) GroupJID(org.jivesoftware.openfire.group.GroupJID) JID(org.xmpp.packet.JID) Element(org.dom4j.Element) Presence(org.xmpp.packet.Presence) ForbiddenException(org.jivesoftware.openfire.muc.ForbiddenException) RoomLockedException(org.jivesoftware.openfire.muc.RoomLockedException) CannotBeInvitedException(org.jivesoftware.openfire.muc.CannotBeInvitedException) NotAllowedException(org.jivesoftware.openfire.muc.NotAllowedException) PacketException(org.jivesoftware.openfire.PacketException) UnauthorizedException(org.jivesoftware.openfire.auth.UnauthorizedException) NotFoundException(org.jivesoftware.util.NotFoundException) ConflictException(org.jivesoftware.openfire.muc.ConflictException) RegistrationRequiredException(org.jivesoftware.openfire.muc.RegistrationRequiredException) UserAlreadyExistsException(org.jivesoftware.openfire.user.UserAlreadyExistsException) UserNotFoundException(org.jivesoftware.openfire.user.UserNotFoundException) NotAcceptableException(org.jivesoftware.openfire.muc.NotAcceptableException) ServiceUnavailableException(org.jivesoftware.openfire.muc.ServiceUnavailableException)

Aggregations

NodeID (org.jivesoftware.openfire.cluster.NodeID)41 JID (org.xmpp.packet.JID)18 Lock (java.util.concurrent.locks.Lock)15 java.util (java.util)12 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)12 ConcurrentMap (java.util.concurrent.ConcurrentMap)12 Collectors (java.util.stream.Collectors)11 Nonnull (javax.annotation.Nonnull)10 XMPPServer (org.jivesoftware.openfire.XMPPServer)10 UnauthorizedException (org.jivesoftware.openfire.auth.UnauthorizedException)9 MUCRole (org.jivesoftware.openfire.muc.MUCRole)8 MUCRoom (org.jivesoftware.openfire.muc.MUCRoom)8 Logger (org.slf4j.Logger)8 LoggerFactory (org.slf4j.LoggerFactory)8 Nullable (javax.annotation.Nullable)7 ClusterManager (org.jivesoftware.openfire.cluster.ClusterManager)7 CacheFactory (org.jivesoftware.util.cache.CacheFactory)7 Map (java.util.Map)6 PacketException (org.jivesoftware.openfire.PacketException)6 DomainPair (org.jivesoftware.openfire.session.DomainPair)6