@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, true);
        final ClusteredCacheEntryListener<DomainPair, NodeID> serversCacheEntryListener = new ReverseLookupUpdatingCacheEntryListener<>(s2sDomainPairsByClusterNode, false);
        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);
        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 is being managed.

        Log.debug("Simulate 'entryAdded' for all data that already exists elsewhere in the cluster.");
        usersCache.entrySet().stream()
            // this filter isn't needed if we do this before restoreCacheContent.
            .filter(entry -> !entry.getValue().getNodeID().equals(XMPPServer.getInstance().getNodeID()))
            .forEach(entry -> userCacheEntryListener.entryAdded(entry.getKey(), entry.getValue(), entry.getValue().getNodeID()));

        serversCache.entrySet().stream()
            // this filter isn't needed if we do this before restoreCacheContent.
            .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 : localClientRoutingTable.getRoutes()) {
            // 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?
    }
Verifies that {@link #usersSessionsCache} and {@link #usersCache} are in a consistent state.

Note that this operation can be costly in terms of resource usage. Use with caution in large / busy systems.

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.

@return A consistency state report.
@see #usersSessionsCache which tracks user sessions.
@see #usersCache which the cache that is used tho share data with other cluster nodes.
xmppserver/src/main/java/org/jivesoftware/openfire/spi/RoutingTableImpl.java