/* * #%L * Nazgul Project: nazgul-core-cache-impl-hazelcast * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% * */ package se.jguru.nazgul.core.cache.impl.hazelcast; import com.hazelcast.core.DistributedObject; import com.hazelcast.core.DistributedObjectEvent; import com.hazelcast.core.DistributedObjectListener; import com.hazelcast.core.EntryEvent; import com.hazelcast.core.EntryListener; import com.hazelcast.core.ItemEvent; import com.hazelcast.core.ItemListener; import com.hazelcast.core.MapEvent; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.jguru.nazgul.core.cache.api.CacheListener; import java.io.Serializable; /** * Adapter delegating Hazelcast event calls to the CacheListener interface. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ public abstract class AbstractHazelcastCacheListenerAdapter<K, V> implements DistributedObjectListener, ItemListener<V>, EntryListener<K, V>, Serializable { // Our log private static final Logger log = LoggerFactory.getLogger(AbstractHazelcastCacheListenerAdapter.class); /** * Key used for callback methods whenever this cacheListenerAdapter is wired to a distributed Collection, and a key * argument is expected. Since Collections do not use keys, this illusory key is used instead. */ public static final String ILLUSORY_KEY_FOR_COLLECTIONS = "collectionsDontUseKeys-OnlyValues"; // Internal state private CacheListener<K, V> listener; /** * Creates a new HazelcastCacheListenerAdapter for a Cache instance. * * @param listener A cacheListener to which we the events of this HazelcastCacheListenerAdapter will be delegated. * @throws IllegalArgumentException if the listener argument is null. */ public AbstractHazelcastCacheListenerAdapter(final CacheListener<K, V> listener) throws IllegalArgumentException { // Check sanity Validate.notNull(listener, "Cannot handle null listener argument."); // Assign internal state this.listener = listener; } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { // Check sanity if (obj == null || !(obj instanceof AbstractHazelcastCacheListenerAdapter)) { return false; } // Delegate to our identifier. final AbstractHazelcastCacheListenerAdapter that = (AbstractHazelcastCacheListenerAdapter) obj; return getId().equals(that.getId()); } /** * {@inheritDoc} */ @Override public int hashCode() { int toReturn = 0; if (listener != null) { toReturn = listener.getClusterId().hashCode(); } // All done return toReturn; } /** * @return The identifier of the contained CacheListener. */ public final String getId() { return listener.getClusterId(); } /** * Invoked when an entry is added. * * @param entryEvent entry event */ @Override public void entryAdded(final EntryEvent<K, V> entryEvent) { logEntryEvent(entryEvent, "Added"); listener.onPut(entryEvent.getKey(), entryEvent.getValue()); } /** * Invoked when an entry is removed. * * @param entryEvent entry event */ @Override public void entryRemoved(final EntryEvent<K, V> entryEvent) { logEntryEvent(entryEvent, "Removed"); listener.onRemove(entryEvent.getKey(), entryEvent.getValue()); } /** * Invoked when an entry is updated. * * @param entryEvent entry event */ @Override public void entryUpdated(final EntryEvent<K, V> entryEvent) { logEntryEvent(entryEvent, "Updated"); listener.onUpdate(entryEvent.getKey(), entryEvent.getValue(), entryEvent.getOldValue()); } /** * Invoked when an entry is evicted. * * @param entryEvent entry event */ @Override public void entryEvicted(final EntryEvent<K, V> entryEvent) { logEntryEvent(entryEvent, "Evicted"); listener.onAutonomousEvict(entryEvent.getKey(), entryEvent.getValue()); } /** * Invoked when an item is added. * * @param itemEvent event with the removed item. */ @Override public void itemAdded(final ItemEvent<V> itemEvent) { final V item = itemEvent.getItem(); log.debug("Added item of type [" + item.getClass().getName() + "]"); // Create the synthetic key and delegate the put to the listener.onPut(convertFrom(ILLUSORY_KEY_FOR_COLLECTIONS), item); } /** * Acquires a synthetic key of type K used to pad the Cache listener specification when a cache key * is not available from an event generated by the cache. Typically, this is the case when * * @param distributedObjectId The ID of the distributed object for which this synthetic key should be retrieved. * @return a synthetic key of type K used to satisfy the Cache listener API when a cache key is not available. */ protected abstract K convertFrom(final String distributedObjectId); /** * Creates a V type value from the supplied source object. * * @param source a non-null source. * @return A cache-compliant value source. */ protected abstract V createFrom(final Object source); /** * Invoked when an item is removed. * * @param itemEvent event with the removed item. */ @Override public void itemRemoved(final ItemEvent<V> itemEvent) { final V item = itemEvent.getItem(); log.debug("Removed item of type [" + item.getClass().getName() + "]"); listener.onRemove(convertFrom(ILLUSORY_KEY_FOR_COLLECTIONS), item); } /** * {@inheritDoc} */ @Override public void distributedObjectCreated(final DistributedObjectEvent event) { log.debug("Created DistributedObject of type [" + event.getDistributedObject().getClass().getName() + "]"); final DistributedObject theInstance = event.getDistributedObject(); listener.onPut(convertFrom(theInstance.getName()), createFrom(theInstance)); } /** * {@inheritDoc} */ @Override public void distributedObjectDestroyed(final DistributedObjectEvent event) { log.debug("Destroyed DistributedObject of type [" + event.getDistributedObject().getClass().getName() + "]"); final DistributedObject theInstance = event.getDistributedObject(); listener.onRemove(convertFrom(theInstance.getName()), createFrom(theInstance)); } /** * {@inheritDoc} */ @Override public void mapEvicted(final MapEvent event) { if (log.isDebugEnabled()) { log.debug("Evicted IMap [" + event.getName() + "], affecting [" + event.getNumberOfEntriesAffected() + "] entries."); } listener.onRemove(convertFrom(event.getName()), createFrom(event.getSource())); } /** * {@inheritDoc} */ @Override public void mapCleared(final MapEvent event) { log.debug("Cleared IMap [" + event.getName() + "]"); listener.onRemove(convertFrom(event.getName()), createFrom(event.getSource())); } /** * @return the wrapped CacheListener instance. */ public final CacheListener getCacheListener() { return listener; } /** * Returns a string representation of this object. * * @return the ID of this object. */ @Override public String toString() { return "[" + getClass().getSimpleName() + " wrapping CacheListener (" + listener.getClusterId() + ")]"; } // // Private helpers // /** * Internal logger method. * * @param entryEvent The EntryEvent from Hazelcast. * @param action The EntryEvent action, such as "Added". */ private void logEntryEvent(final EntryEvent<K, V> entryEvent, final String action) { if (log.isDebugEnabled()) { String localMember = entryEvent.getMember() == null ? "<no member>" : entryEvent.getMember().localMember() ? " (local member)" : ""; final String memberIP = entryEvent.getMember() == null ? "" : entryEvent.getMember().getSocketAddress().toString(); log.debug("(Listener " + getId() + "): " + action + " entry [" + entryEvent.getName() + "] from member [" + memberIP + "]" + localMember + ". Key: " + entryEvent.getKey() + ", Value: " + entryEvent.getValue()); } } }