public static Multimap<String, String> generateReportForMucRooms(
        @Nonnull final Cache<String, MUCRoom> clusteredRoomCacheInput,
        @Nonnull final Map<String, MUCRoom> localRoomsInput,
        @Nonnull final Map<NodeID, Set<OccupantManager.Occupant>> occupantsByNodeInput,
        @Nonnull final Map<OccupantManager.Occupant, NodeID> nodeByOccupantInput,
        @Nonnull final Set<OccupantManager.Occupant> federatedOccupantsInput,
        @Nonnull final String mucServiceName
    ) {

        // Take snapshots of all data structures at as much the same time as possible.
        final ConcurrentMap<String, MUCRoom> cache = new ConcurrentHashMap<>(clusteredRoomCacheInput);
        final ConcurrentMap<String, MUCRoom> localRoomsCache = new ConcurrentHashMap<>(localRoomsInput);
        final ConcurrentMap<NodeID, Set<OccupantManager.Occupant>> occupantsByNode = new ConcurrentHashMap<>(occupantsByNodeInput);
        final ConcurrentMap<OccupantManager.Occupant, NodeID> nodeByOccupant = new ConcurrentHashMap<>(nodeByOccupantInput);
        final Set<OccupantManager.Occupant> federatedOccupants = new HashSet<>(federatedOccupantsInput);

        final Set<String> roomsOnlyInLocalCache = localRoomsCache.keySet().stream().filter(jid -> !cache.containsKey(jid)).collect(Collectors.toSet());

        final Set<OccupantManager.Occupant> allOccupantsFromOccupantsByNode = occupantsByNode.values().stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toSet());
        final Set<OccupantManager.Occupant> allOccupantsFromNodesByOccupant = nodeByOccupant.keySet();

        final Set<OccupantManager.Occupant> occupantsByNodeNotPresentInNodesByOccupant = allOccupantsFromOccupantsByNode.stream()
            .filter(o -> !allOccupantsFromNodesByOccupant.contains(o))
            .collect(Collectors.toSet());
        final Set<OccupantManager.Occupant> occupantsNotPresentInOccupantsByNode = allOccupantsFromNodesByOccupant.stream()
            .filter(o -> !allOccupantsFromOccupantsByNode.contains(o))
            .collect(Collectors.toSet());

        final List<OccupantManager.Occupant> allNonFederatedOccupants = allOccupantsFromOccupantsByNode.stream()
            .sorted(Comparator.comparing(OccupantManager.Occupant::toString))
            .collect(Collectors.toList());
        final List<String> allNonFederatedOccupantsJids = allNonFederatedOccupants
            .stream()
            .map(occupant -> occupant.getRealJID().toFullJID() + " (in room '" + occupant.getRoomName() + "' with nickname '" + occupant.getNickname() + "')")
            .sorted()
            .collect(Collectors.toList());

        final List<OccupantManager.Occupant> allFederatedOccupants = federatedOccupants.stream()
            .sorted(Comparator.comparing(OccupantManager.Occupant::toString))
            .collect(Collectors.toList());
        final List<String> allFederatedOccupantsJids = allFederatedOccupants
            .stream()
            .map(occupant -> occupant.getRealJID().toFullJID() + " (in room '" + occupant.getRoomName() + "' with nickname '" + occupant.getNickname() + "')")
            .sorted()
            .collect(Collectors.toList());

        final List<String> allOccupantJids = new LinkedList<>();
        allOccupantJids.addAll(allFederatedOccupantsJids);
        allOccupantJids.addAll(allNonFederatedOccupantsJids);

        final List<MUCOccupant> allMucOccupants = cache.values().stream()
            .flatMap(room -> room.getOccupants().stream())
            .sorted(Comparator.comparing(MUCOccupant::toString))
            .collect(Collectors.toList());
        final List<String> allMucOccupantsJids = allMucOccupants
            .stream()
            .map(occupant -> occupant.getUserAddress().toFullJID() + " (in room '" + occupant.getOccupantJID().getNode() + "' with nickname '" + occupant.getNickname() + "')")
            .sorted()
            .collect(Collectors.toList());

        // Generate report
        final Multimap<String, String> result = HashMultimap.create();

        result.put("intro", String.format("This section concerns the '%s' muc service.", mucServiceName));
        result.put("info", String.format("The cache named %s is used to share data in the cluster, which contains %d muc rooms.", clusteredRoomCacheInput.getName(), cache.size()));

        result.put("data", String.format("%s contains these entries (these are shared in the cluster):\n%s", clusteredRoomCacheInput.getName(), cache.keySet()
            .stream()
            .sorted()
            .collect(Collectors.joining("\n"))));
        result.put("data", String.format("Local rooms cache contains these entries :\n%s", localRoomsCache.keySet()
            .stream()
            .sorted()
            .collect(Collectors.joining("\n"))));
        result.put("data", String.format("All non-federated occupants from occupant registration :\n%s", String.join("\n", allNonFederatedOccupantsJids)));
        result.put("data", String.format("All federated occupants from occupant registration :\n%s", String.join("\n", allFederatedOccupantsJids)));
        result.put("data", String.format("All occupants from rooms in cache :\n%s", String.join("\n", allMucOccupantsJids)));

        if (roomsOnlyInLocalCache.isEmpty()) {
            result.put("pass", "All locally known rooms exist in clustered room cache.");
        } else {
            result.put("fail", String.format("Clustered room cache is missing entries that are present in the local room cache. These %d entries are missing: %s", roomsOnlyInLocalCache.size(), String.join(", ", roomsOnlyInLocalCache)));
        }

        if (occupantsByNodeNotPresentInNodesByOccupant.isEmpty()) {
            result.put("pass", "All occupants registered by node exist in the nodes registered by occupant.");
        } else {
            result.put("fail", String.format("The registration of nodes by occupant is missing entries that are present in the registration of occupants by node. These %d entries are missing: %s", occupantsByNodeNotPresentInNodesByOccupant.size(), occupantsByNodeNotPresentInNodesByOccupant.stream().map(OccupantManager.Occupant::getNickname).collect(Collectors.joining(", "))));
        }

        if (occupantsNotPresentInOccupantsByNode.isEmpty()) {
            result.put("pass", "All occupants in the nodes registered by occupant exist in the occupants registered by node.");
        } else {
            result.put("fail", String.format("The registration of occupants by node is missing entries that are present in the registration of nodes by occupant. These %d entries are missing: %s", occupantsNotPresentInOccupantsByNode.size(), occupantsNotPresentInOccupantsByNode.stream().map(OccupantManager.Occupant::getNickname).collect(Collectors.joining(", "))));
        }

        if (new HashSet<>(allOccupantJids).equals(new HashSet<>(allMucOccupantsJids))) {
            result.put("pass", "The list of occupants registered by node equals the list of occupants seen in rooms.");
        } else {
            result.put("fail", String.format("The sum of the collection of non-federated (%d) and federated (%d) occupants is %d, which does not equal the list of occupants seen in rooms, which has %d elements", allNonFederatedOccupantsJids.size(), allFederatedOccupantsJids.size(), allOccupantJids.size(), allMucOccupantsJids.size()));
        }

        return result;
    }
Verifies that #sessionInfoCache, #localSessionInfos and #sessionInfoKeysByClusterNode
of {@link org.jivesoftware.openfire.SessionManager} are in a consistent state.
<p>
Note that this operation can be costly in terms of resource usage. Use with caution in large / busy systems.
<p>
The returned multi-map can contain up to four keys: info, fail, pass, data. All entry values are a human readable
description of a checked characteristic. When the state is consistent, no 'fail' entries will be returned.

@param sessionInfoCache             The cache that is used to share data across cluster nodes. (note that unlike its siblings, this is empty when not clustering!)
@param localSessions                The data structure that keeps track of what data was added to the cache by the local cluster node.
@param sessionInfoKeysByClusterNode The data structure that keeps track of what data was added to the cache by the remote cluster nodes.
@return A consistency state report.
xmppserver/src/main/java/org/jivesoftware/util/cache/ConsistencyChecks.java