public static Multimap<String, String> generateReportForRoutingTableClientRoutes(
        @Nonnull final Cache<String, ClientRoute> usersCache,
        @Nonnull final Collection<LocalClientSession> localClientRoutes,
        @Nonnull final Map<NodeID, Set<String>> routeOwnersByClusterNode) {
        final Set<NodeID> clusterNodeIDs = ClusterManager.getNodesInfo().stream().map(ClusterNodeInfo::getNodeID).collect(Collectors.toSet());

        // Take snapshots of all data structures at as much the same time as possible.
        final Set<String> usersCacheKeySet = usersCache.keySet();

        final List<String> localClientRoutesOwners = localClientRoutes.stream().map(r -> r.getAddress().toString()).collect(Collectors.toList());
        final Set<String> localClientRoutesOwnersDuplicates = CollectionUtils.findDuplicates(localClientRoutesOwners);

        final List<String> remoteClientRoutesOwners = routeOwnersByClusterNode.values().stream().flatMap(Collection::stream).collect(Collectors.toList());
        final List<String> remoteClientRoutesOwnersWithNodeId = new ArrayList<>();
        for (Map.Entry<NodeID, Set<String>> entry : routeOwnersByClusterNode.entrySet()) {
            for (String item : entry.getValue()) {
                remoteClientRoutesOwnersWithNodeId.add(item + " (" + entry.getKey() + ")");
            }
        }

        final Set<String> remoteClientRoutesOwnersDuplicates = CollectionUtils.findDuplicates(remoteClientRoutesOwners);

        final Set<String> clientRoutesBothLocalAndRemote = CollectionUtils.findDuplicates(localClientRoutesOwners, remoteClientRoutesOwners);

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

        result.put("info", String.format("The cache named %s is used to share data in the cluster, which contains %d user routes.", usersCache.getName(), usersCacheKeySet.size()));
        result.put("info", String.format("LocalClientRoutingTable's response is used to track 'local' data to be restored after a cache switch-over. It tracks %d routes.", localClientRoutes.size()));
        result.put("info", String.format("The field routeOwnersByClusterNode is used to track data in the cache from every other cluster node. It contains %d routes for %d cluster nodes.", routeOwnersByClusterNode.values().stream().reduce(0, (subtotal, values) -> subtotal + values.size(), Integer::sum), routeOwnersByClusterNode.keySet().size()));

        result.put("data", String.format("%s contains these entries (these are shared in the cluster):\n%s", usersCache.getName(), String.join("\n", usersCacheKeySet)));
        result.put("data", String.format("LocalClientRoutingTable's response contains these entries (these represent 'local' data):\n%s", String.join("\n", localClientRoutesOwners)));
        result.put("data", String.format("routeOwnersByClusterNode contains these entries (these represent 'remote' data):\n%s", String.join("\n", remoteClientRoutesOwnersWithNodeId)));

        if (localClientRoutesOwnersDuplicates.isEmpty()) {
            result.put("pass", "There is no overlap in route owners of LocalClientRoutingTable's response (They are all unique values).");
        } else {
            result.put("fail", String.format("There is overlap in route owners of LocalClientRoutingTable's response (They are not all unique values). These %d values are duplicated: %s", localClientRoutesOwnersDuplicates.size(), String.join(", ", localClientRoutesOwnersDuplicates)));
        }

        if (remoteClientRoutesOwnersDuplicates.isEmpty()) {
            result.put("pass", "There is no overlap in routeOwnersByClusterNode (They are all unique values).");
        } else {
            result.put("fail", String.format("There is overlap in routeOwnersByClusterNode (They are not all unique values). These %d values are duplicated: %s", remoteClientRoutesOwnersDuplicates.size(), String.join(", ", remoteClientRoutesOwnersDuplicates)));
        }

        if (!routeOwnersByClusterNode.containsKey(XMPPServer.getInstance().getNodeID())) {
            result.put("pass", "routeOwnersByClusterNode does not track data for the local cluster node.");
        } else {
            result.put("fail", "routeOwnersByClusterNode tracks data for the local cluster node.");
        }

        if (clusterNodeIDs.containsAll(routeOwnersByClusterNode.keySet())) {
            result.put("pass", "routeOwnersByClusterNode tracks data for cluster nodes that are recognized in the cluster.");
        } else {
            result.put("fail", String.format("routeOwnersByClusterNode tracks data for cluster nodes that are not recognized. All cluster nodeIDs as recognized: %s All cluster nodeIDs for which data is tracked: %s.", clusterNodeIDs.stream().map(NodeID::toString).collect(Collectors.joining(", ")), routeOwnersByClusterNode.keySet().stream().map(NodeID::toString).collect(Collectors.joining(", "))));
        }

        if (clientRoutesBothLocalAndRemote.isEmpty()) {
            result.put("pass", "There are no elements that are both 'remote' (in routeOwnersByClusterNode) as well as 'local' (in LocalClientRoutingTable's).");
        } else {
            result.put("fail", String.format("There are %d elements that are both 'remote' (in routeOwnersByClusterNode) as well as 'local' (in LocalClientRoutingTable's): %s", clientRoutesBothLocalAndRemote.size(), String.join(", ", clientRoutesBothLocalAndRemote)));
        }

        final Set<String> nonCachedLocalClientRoutesOwners = localClientRoutesOwners.stream().filter(v -> !usersCacheKeySet.contains(v)).collect(Collectors.toSet());
        if (nonCachedLocalClientRoutesOwners.isEmpty()) {
            result.put("pass", String.format("All route owners of LocalClientRoutingTable's response exist in %s.", usersCache.getName()));
        } else {
            result.put("fail", String.format("Not all route owners of LocalClientRoutingTable's response exist in %s. These %d entries do not: %s", usersCache.getName(), nonCachedLocalClientRoutesOwners.size(), String.join(", ", nonCachedLocalClientRoutesOwners)));
        }

        final Set<String> nonCachedRemoteClientRouteOwners = remoteClientRoutesOwners.stream().filter(v -> !usersCacheKeySet.contains(v)).collect(Collectors.toSet());
        if (nonCachedRemoteClientRouteOwners.isEmpty()) {
            result.put("pass", String.format("All route owners in routeOwnersByClusterNode exist in %s.", usersCache.getName()));
        } else {
            result.put("fail", String.format("Not all route owners in routeOwnersByClusterNode exist in %s. These %d entries do not: %s", usersCache.getName(), nonCachedRemoteClientRouteOwners.size(), String.join(", ", nonCachedRemoteClientRouteOwners)));
        }

        final Set<String> nonLocallyStoredCachedRouteOwners = usersCacheKeySet.stream().filter(v -> !localClientRoutesOwners.contains(v)).filter(v -> !remoteClientRoutesOwners.contains(v)).collect(Collectors.toSet());
        if (nonLocallyStoredCachedRouteOwners.isEmpty()) {
            result.put("pass", String.format("All cache entries of %s exist in routeOwnersByClusterNode and/or LocalClientRoutingTable's response.", usersCache.getName()));
        } else {
            result.put("fail", String.format("Not all cache entries of %s exist in routeOwnersByClusterNode and/or LocalClientRoutingTable's response. These %d entries do not: %s", usersCache.getName(), nonLocallyStoredCachedRouteOwners.size(), String.join(", ", nonLocallyStoredCachedRouteOwners)));
        }

        return result;
    }
Verifies that usersCache, #localClientRoutingTable and routeOwnersByClusterNode
of {@link org.jivesoftware.openfire.spi.RoutingTableImpl} 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 usersCache               The cache that is used to share data across cluster nodes
@param localClientRoutes        The data structure that keeps track of what data was added to the cache by the local cluster node.
@param routeOwnersByClusterNode 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