package org.jivesoftware.openfire.spi;

import org.jivesoftware.openfire.cluster.ClusteredCacheEntryListener;
import org.jivesoftware.openfire.cluster.NodeID;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;

/**
 * Cache entry listener implementation that maintains a reverse lookup map for the cache that is being observed.
 */
public class ReverseLookupUpdatingCacheEntryListener<K, V> implements ClusteredCacheEntryListener<K, V> {
    private final ConcurrentMap<NodeID, Set<K>> reverseCacheRepresentation;

    public ReverseLookupUpdatingCacheEntryListener(@Nonnull final ConcurrentMap<NodeID, Set<K>> reverseCacheRepresentation) {
        this.reverseCacheRepresentation = reverseCacheRepresentation;
    }

    @Override
    public void entryAdded(@Nonnull final K key, @Nullable final V value, @Nonnull final NodeID nodeID) {
        reverseCacheRepresentation.computeIfAbsent(nodeID, k -> new HashSet<>()).add(key);
    }

    @Override
    public void entryRemoved(@Nonnull final K key, @Nullable final V oldValue, @Nonnull final NodeID nodeID) {
        reverseCacheRepresentation.computeIfPresent(nodeID, (k, v) -> {
            v.remove(key);
            return v;
        });
    }

    @Override
    public void entryUpdated(@Nonnull final K key, @Nullable final V oldValue, @Nullable final V newValue, @Nonnull final NodeID nodeID) {
        // Don't do anything, because we only care about keys.
    }

    @Override
    public void entryEvicted(@Nonnull final K key, @Nullable final V oldValue, @Nonnull final NodeID nodeID) {
        entryRemoved(key, oldValue, nodeID);
    }

    @Override
    public void mapCleared(@Nonnull final NodeID nodeID) {
        reverseCacheRepresentation.remove(nodeID);
    }

    @Override
    public void mapEvicted(@Nonnull final NodeID nodeID) {
        reverseCacheRepresentation.remove(nodeID);
    }
}

@@ -1,8 +1,644 @@
+/*%0A * Copyright (C) 2021 Ignite Realtime Foundation. All rights reserved.%0A *%0A * Licensed under the Apache License, Version 2.0 (the %22License%22);%0A * you may not use this file except in compliance with the License.%0A * You may obtain a copy of the License at%0A *%0A *     http://www.apache.org/licenses/LICENSE-2.0%0A *%0A * Unless required by applicable law or agreed to in writing, software%0A * distributed under the License is distributed on an %22AS IS%22 BASIS,%0A * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.%0A * See the License for the specific language governing permissions and%0A * limitations under the License.%0A */%0A
 package 
@@ -2106,59 +2106,263 @@
  // 
-Don't do anything, because we only care about keys.
+Although we only care about keys, we do need to process this for caches in which values are not uniquely 'owned' by a particular node, such as the #componentsCache.%0A        reverseCacheRepresentation.computeIfAbsent(nodeID, k -%3E new HashSet%3C%3E()).add(key);
 %0A   

Make AbstractCacheEntryListener work for cache entries not owned by one cluster node