package org.infinispan.atomic.impl; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Set; import org.infinispan.AdvancedCache; import org.infinispan.Cache; import org.infinispan.atomic.AtomicMap; import org.infinispan.atomic.AtomicMapLookup; import org.infinispan.atomic.CopyableDeltaAware; import org.infinispan.atomic.Delta; import org.infinispan.commons.marshall.AbstractExternalizer; import org.infinispan.commons.util.FastCopyHashMap; import org.infinispan.commons.util.Util; import org.infinispan.marshall.core.Ids; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import net.jcip.annotations.NotThreadSafe; /** * The default implementation of {@link AtomicMap}. Note that this map cannot be constructed directly, and callers * should obtain references to AtomicHashMaps via the {@link AtomicMapLookup} helper. This helper will ensure proper * concurrent construction and registration of AtomicMaps in Infinispan's data container. E.g.: * <br /><br /> * <code> * AtomicMap<String, Integer> map = AtomicMapLookup.getAtomicMap(cache, "my_atomic_map_key"); * </code> * <br /><br /> * Note that for replication to work properly, AtomicHashMap updates <b><i>must always</i></b> take place within the * scope of an ongoing JTA transaction or batch (see {@link Cache#startBatch()}). * <p/> * * @author (various) * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values * @see AtomicMap * @see AtomicMapLookup * @since 4.0 */ @NotThreadSafe public final class AtomicHashMap<K, V> implements AtomicMap<K, V>, CopyableDeltaAware, Cloneable { private static final Log log = LogFactory.getLog(AtomicHashMap.class); private static final boolean trace = log.isTraceEnabled(); protected final FastCopyHashMap<K, V> delegate; private AtomicHashMapDelta delta = null; private volatile AtomicHashMapProxy<K, V> proxy; volatile boolean copied = false; volatile boolean removed = false; private final ProxyMode proxyMode; /** * Construction only allowed through this factory method. This factory is intended for use internally by the * CacheDelegate. User code should use {@link AtomicMapLookup#getAtomicMap(Cache, Object)}. */ @SuppressWarnings("unchecked") public static <K, V> AtomicHashMap<K, V> newInstance(Cache<Object, Object> cache, Object cacheKey, ProxyMode proxyMode) { AtomicHashMap<K, V> value = new AtomicHashMap<>(proxyMode); Object oldValue = cache.putIfAbsent(cacheKey, value); if (oldValue != null) value = (AtomicHashMap<K, V>) oldValue; return value; } //used in tests only public AtomicHashMap() { this(new FastCopyHashMap<>(), ProxyMode.COARSE); } public AtomicHashMap(ProxyMode proxyMode) { this(new FastCopyHashMap<>(), Objects.requireNonNull(proxyMode)); } private AtomicHashMap(FastCopyHashMap<K, V> delegate, ProxyMode proxyMode) { this.delegate = delegate; this.proxyMode = proxyMode; } public AtomicHashMap(boolean isCopy, ProxyMode proxyMode) { this(new FastCopyHashMap<>(), proxyMode); this.copied = isCopy; } private AtomicHashMap(FastCopyHashMap<K, V> newDelegate, AtomicHashMapProxy<K, V> proxy, ProxyMode proxyMode) { this.delegate = newDelegate; this.proxy = proxy; this.copied = true; this.proxyMode = proxyMode; } @Override public void commit() { copied = false; delta = null; } @Override public int size() { return delegate.size(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } @Override public boolean containsKey(Object key) { return delegate.containsKey(key); } @Override public boolean containsValue(Object value) { return delegate.containsValue(value); } @Override public V get(Object key) { V v = delegate.get(key); if (trace) log.tracef("Atomic hash map get(key=%s) returns %s", key, v); return v; } @Override public Set<K> keySet() { return delegate.keySet(); } @Override public Collection<V> values() { return delegate.values(); } @Override public Set<Entry<K, V>> entrySet() { return delegate.entrySet(); } @Override public V put(K key, V value) { V oldValue = delegate.put(key, value); PutOperation<K, V> op = new PutOperation<>(key, oldValue, value); getDelta().addOperation(op); return oldValue; } @Override @SuppressWarnings("unchecked") public V remove(Object key) { V oldValue = delegate.remove(key); RemoveOperation<K, V> op = new RemoveOperation<>((K) key, oldValue); getDelta().addOperation(op); return oldValue; } @Override public void putAll(Map<? extends K, ? extends V> t) { // this is crappy - need to do this more efficiently! for (Entry<? extends K, ? extends V> e : t.entrySet()) put(e.getKey(), e.getValue()); } @Override @SuppressWarnings("unchecked") public void clear() { FastCopyHashMap<K, V> originalEntries = delegate.clone(); ClearOperation<K, V> op = new ClearOperation<>(originalEntries); getDelta().addOperation(op); delegate.clear(); } /** * Builds a thread-safe proxy for this instance so that concurrent reads are isolated from writes. * @return an instance of AtomicHashMapProxy */ public AtomicHashMapProxy<K, V> getProxy(AdvancedCache<Object, Object> cache, Object mapKey) { // construct the proxy lazily if (proxy == null) // DCL is OK here since proxy is volatile (and we live in a post-JDK 5 world) { synchronized (this) { if (proxy == null) switch (proxyMode) { case FINE: proxy = new FineGrainedAtomicHashMapProxy<>(cache, mapKey); break; case COARSE: proxy = new AtomicHashMapProxy<>(cache, mapKey); break; default: throw new IllegalStateException("Unknown proxy mode: " + proxyMode); } } } return proxy; } public void markRemoved(boolean b) { removed = b; } @Override public Delta delta() { Delta toReturn = delta == null ? new AtomicHashMapDelta(proxyMode) : delta; delta = null; // reset return toReturn; } @SuppressWarnings("unchecked") public AtomicHashMap<K, V> copy() { FastCopyHashMap<K, V> newDelegate = delegate.clone(); return new AtomicHashMap(newDelegate, proxy, proxyMode); } @Override public String toString() { // Sanne: Avoid iterating on the delegate as that might lead to // exceptions from concurrent iterators: not nice to have during a toString! // // Galder: Sure, but we need a way to track the contents of the atomic // hash map somehow, so, we need to log each operation that affects its // contents, and when its state is restored. return "AtomicHashMap{size=" + size() + "}"; } /** * Initializes the delta instance to start recording changes. */ public void initForWriting() { delta = new AtomicHashMapDelta(proxyMode); } AtomicHashMapDelta getDelta() { if (delta == null) delta = new AtomicHashMapDelta(proxyMode); return delta; } public static class Externalizer extends AbstractExternalizer<AtomicHashMap> { @Override public void writeObject(ObjectOutput output, AtomicHashMap map) throws IOException { output.writeObject(map.delegate); output.writeByte(map.proxyMode.ordinal()); } @Override @SuppressWarnings("unchecked") public AtomicHashMap readObject(ObjectInput input) throws IOException, ClassNotFoundException { FastCopyHashMap<?, ?> delegate = (FastCopyHashMap<?, ?>) input.readObject(); ProxyMode proxyMode = ProxyMode.CACHED_VALUES[input.readByte()]; if (trace) log.tracef("Restore atomic hash map from %s", delegate); return new AtomicHashMap(delegate, proxyMode); } @Override public Integer getId() { return Ids.ATOMIC_HASH_MAP; } @Override @SuppressWarnings("unchecked") public Set<Class<? extends AtomicHashMap>> getTypeClasses() { return Util.<Class<? extends AtomicHashMap>>asSet(AtomicHashMap.class); } } public enum ProxyMode { FINE, COARSE; private static final ProxyMode[] CACHED_VALUES = values(); public static ProxyMode valueOf(int ordinal) { return CACHED_VALUES[ordinal]; } } }