/* * #%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.helpers; import com.hazelcast.core.ICountDownLatch; import se.jguru.nazgul.core.cache.api.AbstractCacheListener; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; /** * Debug / Test CacheListenerAdapter implementation. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ public class DebugCacheListener<T> extends AbstractCacheListener<String, T> { // Internal state private final Object[] lock = new Object[0]; private AtomicInteger index = new AtomicInteger(1); private ICountDownLatch optionalLatch; public TreeMap<Integer, EventInfo> eventId2EventInfoMap = new TreeMap<Integer, EventInfo>(); /** * Creates a new DebugCacheListener with the given id. * * @param id a unique ID for this DebugCacheListener. */ public DebugCacheListener(String id) { super(id); } public DebugCacheListener() { super("This constructor is for Serialization only"); } /** * Creates a new DebugCacheListener with the given id and using an ICountDownLatch * to ensure that events are registered. * * @param id a unique ID for this DebugCacheListener. * @param optionalLatch an ICountDownLatch used to ensure that all expected events are received. */ public DebugCacheListener(final String id, final ICountDownLatch optionalLatch) { super(id); this.optionalLatch = optionalLatch; } /** * Callback method invoked when the object with the given key * is stored within the underlying cache implementation. * <strong>Note!</strong>. The key and value must not be modified * within this callback method. * * @param key The key of the object * @param value The value which was put. */ @Override public void doOnPut(String key, T value) { addEvent("put", key, value); } /** * Callback method invoked when the object with the given key * is updated within the underlying cache implementation. * <strong>Note!</strong>. The key and value must not be modified * within this callback method. * * @param key The cache key. * @param newValue The new value - after the update. * @param oldValue The former value - before the update. */ @Override public void doOnUpdate(String key, T newValue, T oldValue) { addEvent("update", key, newValue); } /** * Callback method invoked when the object with the given * key is actively removed from the underlying cache * implementation (by a user call). * <strong>Note!</strong>. The key and value must not be modified * within this callback method. * * @param key The key of the object which got evicted from the cache. * @param value The object that was removed. */ @Override public void doOnRemove(String key, T value) { addEvent("remove", key, value); } /** * Callback method invoked when the underlying cache * is cleared (i.e. its state destroyed and all cached * objects evicted). */ @Override public void onClear() { addEvent("clear", null, null); } /** * Callback method invoked when the object with the given key * is (re-)loaded into the underlying cache implementation. * This is assumed to be the result of an autonomous/internal * call within the underlying cache implementation, as opposed * to a call to <code>put(key, value)</code>. * <strong>Note!</strong>. The key and value must not be modified * within this callback method. * * @param key The key of the object which got loaded into the cache. * @param value The Object that was loaded. */ @Override public void doOnAutonomousLoad(String key, T value) { addEvent("autonomousLoad", key, value); } /** * Callback method invoked when the object with the given * key is evicted from the underlying cache implementation. * This is assumed to be the result of an autonomous/internal * call within the underlying cache implementation, as opposed * to a call to <code>remove(key)</code> from a server * implementation. * <strong>Note!</strong>. The key and value must not be modified * within this callback method. * * @param key The key of the object which got evicted from the cache. * @param value The object that was evicted. */ @Override public void doOnAutonomousEvict(String key, T value) { addEvent("autonomousEvict", key, value); } /** * Externalizable write template delegation method, invoked from * within writeExternal. You should write the internal state of * the Listener onto the ObjectOutput, adhering to the following * pattern: * <pre> * <code> * class SomeCacheListener extends CacheListenerAdapter * { * // Internal state * private int meaningOfLife = 42; * private String name = "Lennart"; * ... * * @Override * protected void performWriteExternal(ObjectOutput out) throws IOException * { * // Write your own state * out.writeInt(meaningOfLife); * out.writeUTF(name); * } * * @Override * protected void performReadExternal(ObjectInput in) throws IOException, ClassNotFoundException * { * // Then write your own state. * // Use the same order as you wrote the properties in writeExternal. * meaningOfLife = in.readInt(); * name = in.readUTF(); * } * } * </code> * </pre> * * @param out * @throws java.io.IOException */ @Override protected void performWriteExternal(ObjectOutput out) throws IOException { out.writeInt(index.get()); // Write the eventId2EventInfoMap out.writeInt(eventId2EventInfoMap.size()); for (Map.Entry<Integer, EventInfo> current : eventId2EventInfoMap.entrySet()) { out.writeInt(current.getKey()); current.getValue().writeExternal(out); } } /** * Externalizable read template delegation method, invoked from * within writeExternal. You should read the internal state of * the Listener from the ObjectInput, adhering to the following * pattern: * <pre> * <code> * class SomeCacheListener extends CacheListenerAdapter * { * // Internal state * private int meaningOfLife = 42; * private String name = "Lennart"; * ... * * @Override * protected void performWriteExternal(ObjectOutput out) throws IOException * { * // Write your own state * out.writeInt(meaningOfLife); * out.writeUTF(name); * } * * @Override * protected void performReadExternal(ObjectInput in) throws IOException, ClassNotFoundException * { * // Write your own state. * // Use the same order as you wrote the properties in writeExternal. * meaningOfLife = in.readInt(); * name = in.readUTF(); * } * } * </code> * </pre> * * @param in the stream to read data from in order to restore the object * @throws java.io.IOException if I/O errors occur * @throws ClassNotFoundException If the class for an object being * restored cannot be found. */ @Override protected void performReadExternal(ObjectInput in) throws IOException, ClassNotFoundException { index = new AtomicInteger(in.readInt()); final int size = in.readInt(); // Check sanity if (eventId2EventInfoMap == null) { eventId2EventInfoMap = new TreeMap<Integer, EventInfo>(); } for (int i = 0; i < size; i++) { Integer currentKey = in.readInt(); final EventInfo currentValue = new EventInfo(null, null, null, null); currentValue.readExternal(in); // ... and re-create the entry. eventId2EventInfoMap.put(currentKey, currentValue); } } public SortedMap<String, SortedMap<Integer, EventInfo>> getThreadName2SortedEventInfoMap() { final SortedMap<String, SortedMap<Integer, EventInfo>> toReturn = new TreeMap<String, SortedMap<Integer, EventInfo>>(); for (Map.Entry<Integer, EventInfo> current : eventId2EventInfoMap.entrySet()) { final EventInfo info = current.getValue(); final SortedMap<Integer, EventInfo> innerMap = toReturn.computeIfAbsent( info.threadName, k -> new TreeMap<>()); innerMap.put(current.getKey(), info); } return toReturn; } @Override public String toString() { StringBuilder builder = new StringBuilder("\n\n\n######################################\n"); for (Map.Entry<String, SortedMap<Integer, EventInfo>> current : getThreadName2SortedEventInfoMap().entrySet()) { final String threadName = current.getKey(); for (Map.Entry<Integer, EventInfo> currentMessageTuple : current.getValue().entrySet()) { builder.append("(" + threadName + ") [" + currentMessageTuple.getKey() + "]: " + currentMessageTuple.getValue() + "\n"); } } return builder.toString() + "######################################\n\n\n"; } // // private helpers // private synchronized void addEvent(String event, String key, Object value) { synchronized (lock) { eventId2EventInfoMap.put( index.getAndIncrement(), new EventInfo(event, key, Thread.currentThread().getName(), value)); if (optionalLatch != null) { optionalLatch.countDown(); } } } }