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.
            // This also broadcasts to all federated occupants, as we've not yet identified a reliable way to make at least one cluster node inform these occupants. TODO: find such a reliable way, to prevent each (remaining) node sending presence updates.
            final Set<OccupantManager.Occupant> recipients;
            if (nodeID == null) {
                recipients = occupantManager.occupantsForRoomByNode(occupant.getRoomName(), XMPPServer.getInstance().getNodeID(), true);
                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, true);
                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 MUCOccupant for sending this stanza, as that has some additional logic (eg: FMUC).
                    final MUCOccupant recipientOccupantData = chatRoom.getOccupantByFullJID(recipient.getRealJID());
                    Log.debug("Stanza now being sent: {}", presence.toXML());
                    if (recipientOccupantData != null) {
                        recipientOccupantData.send(presence);
                    } else {
                        Log.warn("Unable to find MUCOccupant for recipient '{}' in room {} while broadcasting 'leave' presence for occupants on disconnected cluster node(s).", recipient.getRealJID(), chatRoom.getJID());
                        XMPPServer.getInstance().getPacketRouter().route(presence); // 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).
                    }
                } 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.");
    }
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
@param nodeID the node that left the cluster. Null if the local node left the cluster.
xmppserver/src/main/java/org/jivesoftware/openfire/muc/spi/MultiUserChatServiceImpl.java