All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.infinispan.atomic.impl.AtomicMapProxyImpl Maven / Gradle / Ivy

There is a newer version: 9.1.7.Final
Show newest version
package org.infinispan.atomic.impl;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Function;

import org.infinispan.Cache;
import org.infinispan.atomic.AtomicMap;
import org.infinispan.commons.CacheException;
import org.infinispan.functional.EntryView;
import org.infinispan.functional.FunctionalMap;
import org.infinispan.commons.marshall.AdvancedExternalizer;
import org.infinispan.commons.marshall.Ids;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.util.FastCopyHashMap;
import org.infinispan.commons.util.Util;
import org.infinispan.functional.impl.FunctionalMapImpl;
import org.infinispan.functional.impl.ReadWriteMapImpl;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;

/**
 * This is the proxy class driving access to the entry in cache using functional API. The value in cache is a map
 * of type defined by {@link #dataClass()} which is accompanied by {@link #dataInstance()} and {@link #dataCopy(Map)}.
 * Currently it is implemented by {@link FastCopyHashMap} as we need to create a defensive copy of the value in context.
 *
 * The map is expected to be used with transactional caches; upon map creation or read, the whole map is cached
 * in the transactional context and subsequent reads are served from here. When the map is modified, the change is
 * applied to the local copy in the context. Modifications are added in the form of functional commands to the transaction
 * and executed opon commit.
 *
 * In non-transactional mode this map works correctly, but since any read results in retrieving the whole map, its use
 * might not be very efficient. This behaviour may be addressed in the future.
 *
 * Collections returned by {@link #keySet()}, {@link #values()} and {@link #entrySet()} are backed by the map.
 * Iterators work on a snapshot obtained at the moment when this iterator is created, and support
 * the {@link Iterator#remove()} operation.
 *
 * This implementation does not support fine-grained locking nor fine-grained write-skew check.
 *
 * @param 
 * @param 
 * @param 
 */
public class AtomicMapProxyImpl implements AtomicMap {
   private enum EF {
      TOUCH, PUT, REMOVE, PUT_ALL, CLEAR, REMOVE_VALUE, REMOVE_COND
   }
   private static final EF[] FUNCTIONS = EF.values();
   private static Log log = LogFactory.getLog(AtomicMap.class);
   private final Cache cache;
   private final FunctionalMap.ReadWriteMap rw;
   private final MK key;

   public AtomicMapProxyImpl(Cache cache, FunctionalMap.ReadWriteMap rw, MK key) {
      this.cache = cache;
      this.rw = rw;
      this.key = key;
   }

   public static  AtomicMap newInstance(Cache cache, MK key, boolean createIfAbsent) {
      FunctionalMapImpl fmap = FunctionalMapImpl.create(cache.getAdvancedCache());
      FunctionalMap.ReadWriteMap rw = ReadWriteMapImpl.create(fmap);
      // We have to first check if the map is present, because touch acquires a lock on the entry and we don't
      // want to block two concurrent map retrievals with createIfPresent
      // Here we do common get() to put the cache into context if we're in transactional cache
      Object value = cache.get(key);
      if (value != null) {
         if (value.getClass() != dataClass()) {
            throw log.atomicMapHasWrongType(value, dataClass());
         }
         return new AtomicMapProxyImpl<>(cache, rw, key);
      } else if (createIfAbsent) {
         wait(rw.eval(key, Touch.instance()));
         return new AtomicMapProxyImpl<>(cache, rw, key);
      } else {
         return null;
      }
   }

   private static Class dataClass() {
      // TODO: once we implement fine grained, we'll have to distinguish the implementation, too
      return FastCopyHashMap.class;
   }

   private static  Map dataInstance() {
      return new FastCopyHashMap<>();
   }

   private static  Map dataCopy(Map original) {
      return new FastCopyHashMap<>(original);
   }

   private static IllegalStateException mapDoesNotExist() {
      return log.atomicMapDoesNotExist();
   }

   private static  Map getMap(EntryView.ReadEntryView view) {
      return view.find().map(Map.class::cast).orElseThrow(AtomicMapProxyImpl::mapDoesNotExist);
   }

   private static  T wait(CompletableFuture cf) {
      try {
         return cf.join();
      } catch (CompletionException ce) {
         Throwable e = ce.getCause();
         while (e instanceof CacheException && e.getCause() != null) {
            e = e.getCause();
         }
         if (e instanceof RuntimeException) {
            throw (RuntimeException) e;
         } else {
            throw ce; // we would wrap that anyway
         }
      }
   }

   private Map map() {
      Object value = cache.get(key);
      if (value == null) {
         throw log.atomicMapDoesNotExist();
      } else if (value instanceof Map) {
         return (Map) value;
      } else {
         throw log.atomicMapHasWrongType(value, dataClass());
      }
   }

   @Override
   public int size() {
      return map().size();
   }

   @Override
   public boolean isEmpty() {
      return map().isEmpty();
   }

   @Override
   public boolean containsKey(Object subkey) {
      return map().containsKey(subkey);
   }

   @Override
   public boolean containsValue(Object value) {
      return map().containsValue(value);
   }

   @Override
   public V get(Object subkey) {
      return map().get(subkey);
   }

   @Override
   public V put(K subkey, V value) {
      return wait(rw.eval(this.key, new Put<>(subkey, value)));
   }

   @Override
   public V remove(Object subkey) {
      return wait(rw.eval(this.key, new Remove(subkey)));
   }

   @Override
   public void putAll(Map m) {
      wait(rw.eval(this.key, new PutAll<>(m)));
   }

   @Override
   public void clear() {
      wait(rw.eval(this.key, Clear.instance()));
   }

   @Override
   public Set keySet() {
      return new KeySetView();
   }

   @Override
   public Collection values() {
      return new ValuesView();
   }

   @Override
   public Set> entrySet() {
      return new EntrySetView();
   }

   @Override
   public String toString() {
      return "AtomicMapProxyImpl{" + key + "}";
   }

   private interface ExternalizableFunction {
      EF id();

      void writeParams(ObjectOutput output) throws IOException;
   }

   private static class Touch implements Function, Boolean>, ExternalizableFunction {
      private static final Touch INSTANCE = new Touch();

      public static  Touch instance() {
         return INSTANCE;
      }

      @Override
      public Boolean apply(EntryView.ReadWriteEntryView view) {
         if (view.find().isPresent()) {
            if (dataClass() != view.get().getClass()) {
               throw log.atomicMapHasWrongType(view.get(), dataClass());
            }
            return true;
         } else {
            view.set(dataInstance());
            return false;
         }
      }

      @Override
      public EF id() {
         return EF.TOUCH;
      }

      @Override
      public void writeParams(ObjectOutput output) throws IOException {
      }
   }

   private static class Put implements Function, V>, ExternalizableFunction {
      private final K key;
      private final V value;

      public Put(K key, V value) {
         this.key = key;
         this.value = value;
      }

      public static  Put readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
         return new Put<>((K) input.readObject(), (V) input.readObject());
      }

      @Override
      public V apply(EntryView.ReadWriteEntryView view) {
         Map copy = dataCopy(getMap(view));
         view.set(copy);
         return copy.put(key, value);
      }

      @Override
      public EF id() {
         return EF.PUT;
      }

      @Override
      public void writeParams(ObjectOutput output) throws IOException {
         output.writeObject(key);
         output.writeObject(value);
      }
   }

   private static class Remove implements Function, V>, ExternalizableFunction {
      private final Object key;

      public Remove(Object key) {
         this.key = key;
      }

      public static  Remove readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
         return new Remove<>(input.readObject());
      }

      @Override
      public V apply(EntryView.ReadWriteEntryView view) {
         Map copy = dataCopy(getMap(view));
         view.set(copy);
         return copy.remove(key);
      }

      @Override
      public EF id() {
         return EF.REMOVE;
      }

      @Override
      public void writeParams(ObjectOutput output) throws IOException {
         output.writeObject(key);
      }
   }

   private static class PutAll implements Function, Void>, ExternalizableFunction {
      private final Map m;

      private PutAll(Map m) {
         this.m = m;
      }

      private static  PutAll readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
         return new PutAll<>(MarshallUtil.unmarshallMap(input, HashMap::new));
      }

      @Override
      public Void apply(EntryView.ReadWriteEntryView view) {
         Map copy = dataCopy(getMap(view));
         copy.putAll(m);
         view.set(copy);
         return null;
      }

      @Override
      public EF id() {
         return EF.PUT_ALL;
      }

      @Override
      public void writeParams(ObjectOutput output) throws IOException {
         MarshallUtil.marshallMap(m, output);
      }
   }

   private static class Clear implements Function, Void>, ExternalizableFunction {
      private static final Clear INSTANCE = new Clear();

      public static  Clear instance() {
         return INSTANCE;
      }

      @Override
      public Void apply(EntryView.ReadWriteEntryView view) {
         getMap(view); // just check existence
         view.set(dataInstance());
         return null;
      }

      @Override
      public EF id() {
         return EF.CLEAR;
      }

      @Override
      public void writeParams(ObjectOutput output) throws IOException {
      }
   }

   private static class RemoveValue implements Function, Boolean>, ExternalizableFunction {
      private final Object value;

      public static  RemoveValue readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
         return new RemoveValue<>(input.readObject());
      }

      public RemoveValue(Object value) {
         this.value = value;
      }

      @Override
      public Boolean apply(EntryView.ReadWriteEntryView view) {
         Map map = dataCopy(getMap(view));
         return map.values().remove(value);
      }

      @Override
      public EF id() {
         return EF.REMOVE_VALUE;
      }

      @Override
      public void writeParams(ObjectOutput output) throws IOException {
         output.writeObject(value);
      }
   }

   private static class RemoveCond implements Function, Boolean>, ExternalizableFunction {
      private final Object key;
      private final Object value;

      public static  RemoveCond readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
         return new RemoveCond<>(input.readObject(), input.readObject());
      }

      public RemoveCond(Object key, Object value) {
         this.key = key;
         this.value = value;
      }

      @Override
      public Boolean apply(EntryView.ReadWriteEntryView view) {
         Map map = dataCopy(getMap(view));
         return map.remove(key, value);
      }

      @Override
      public EF id() {
         return EF.REMOVE_COND;
      }

      @Override
      public void writeParams(ObjectOutput output) throws IOException {
         output.writeObject(key);
         output.writeObject(value);
      }
   }

   public static class Externalizer implements AdvancedExternalizer {
      @Override
      public Set> getTypeClasses() {
         return Util.asSet(Touch.class, Put.class, Remove.class, PutAll.class, Clear.class,
               RemoveValue.class, RemoveCond.class);
      }

      @Override
      public Integer getId() {
         return Ids.ATOMIC_MAP_FUNCTIONS;
      }

      @Override
      public void writeObject(ObjectOutput output, ExternalizableFunction object) throws IOException {
         output.writeByte(object.id().ordinal());
         object.writeParams(output);
      }

      @Override
      public ExternalizableFunction readObject(ObjectInput input) throws IOException, ClassNotFoundException {
         int id = input.read();
         switch (FUNCTIONS[id]) {
            case TOUCH:
               return Touch.instance();
            case PUT:
               return Put.readFrom(input);
            case REMOVE:
               return Remove.readFrom(input);
            case PUT_ALL:
               return PutAll.readFrom(input);
            case CLEAR:
               return Clear.instance();
            case REMOVE_VALUE:
               return RemoveValue.readFrom(input);
            case REMOVE_COND:
               return RemoveCond.readFrom(input);
            default:
               throw new IllegalArgumentException("Unknown function id " + id);
         }
      }
   }

   private class KeySetView extends AbstractSet {
      private Set keys() {
         return map().keySet();
      }

      @Override
      public int size() {
         return AtomicMapProxyImpl.this.size();
      }

      @Override
      public boolean contains(Object o) {
         return AtomicMapProxyImpl.this.containsKey(o);
      }

      @Override
      public Iterator iterator() {
         return new KeyIterator(keys().iterator());
      }

      @Override
      public Object[] toArray() {
         return keys().toArray();
      }

      @Override
      public  T[] toArray(T[] a) {
         return keys().toArray(a);
      }

      @Override
      public boolean remove(Object o) {
         return AtomicMapProxyImpl.this.remove(o) != null;
      }

      @Override
      public boolean containsAll(Collection c) {
         return keys().containsAll(c);
      }

      @Override
      public void clear() {
         AtomicMapProxyImpl.this.clear();
      }
   }

   private class KeyIterator implements Iterator {
      private final Iterator iterator;
      private K last;

      public KeyIterator(Iterator iterator) {
         this.iterator = iterator;
      }

      @Override
      public boolean hasNext() {
         return iterator.hasNext();
      }

      @Override
      public K next() {
         return last = iterator.next();
      }

      @Override
      public void remove() {
         if (last == null) {
            throw new IllegalStateException();
         }
         AtomicMapProxyImpl.this.remove(last);
      }
   }

   private class ValuesView extends AbstractCollection {
      private Collection values() {
         return map().values();
      }

      @Override
      public int size() {
         return AtomicMapProxyImpl.this.size();
      }

      @Override
      public boolean contains(Object o) {
         return AtomicMapProxyImpl.this.containsValue(o);
      }

      @Override
      public Iterator iterator() {
         return new ValuesIterator(values().iterator());
      }

      @Override
      public Object[] toArray() {
         return values().toArray();
      }

      @Override
      public  T[] toArray(T[] a) {
         return values().toArray(a);
      }

      @Override
      public boolean remove(Object o) {
         return AtomicMapProxyImpl.wait(rw.eval(key, new RemoveValue<>(o)));
      }

      @Override
      public boolean containsAll(Collection c) {
         return values().containsAll(c);
      }

      @Override
      public void clear() {
         AtomicMapProxyImpl.this.clear();
      }

      private class ValuesIterator implements Iterator {
         private final Iterator iterator;
         private V last;

         public ValuesIterator(Iterator iterator) {
            this.iterator = iterator;
         }

         @Override
         public boolean hasNext() {
            return iterator.hasNext();
         }

         @Override
         public V next() {
            return last = iterator.next();
         }

         @Override
         public void remove() {
            if (last == null) {
               throw new IllegalStateException();
            }
            AtomicMapProxyImpl.wait(rw.eval(key, new RemoveValue<>(last)));
         }
      }
   }

   private class EntrySetView extends AbstractSet> {
      private Set> entrySet() {
         return map().entrySet();
      }

      @Override
      public int size() {
         return AtomicMapProxyImpl.this.size();
      }

      @Override
      public boolean contains(Object o) {
         if (o instanceof Map.Entry) {
            Entry entry = (Entry) o;
            V v = AtomicMapProxyImpl.this.get(entry.getKey());
            return Objects.equals(v, entry.getValue());
         } else {
            return false;
         }
      }

      @Override
      public Iterator> iterator() {
         return new EntrySetIterator(entrySet().iterator());
      }

      @Override
      public Object[] toArray() {
         return entrySet().toArray();
      }

      @Override
      public  T[] toArray(T[] a) {
         return entrySet().toArray(a);
      }

      @Override
      public boolean remove(Object o) {
         if (o instanceof Map.Entry) {
            Entry entry = (Entry) o;
            return AtomicMapProxyImpl.wait(rw.eval(key, new RemoveCond<>(entry.getKey(), entry.getValue())));
         } else return false;
      }

      @Override
      public boolean containsAll(Collection c) {
         return entrySet().containsAll(c);
      }

      @Override
      public void clear() {
         AtomicMapProxyImpl.this.clear();
      }
   }

   private class EntrySetIterator implements Iterator> {
      private final Iterator> iterator;
      private Entry last;

      public EntrySetIterator(Iterator> iterator) {
         this.iterator = iterator;
      }

      @Override
      public boolean hasNext() {
         return iterator.hasNext();
      }

      @Override
      public Entry next() {
         return last = iterator.next();
      }

      @Override
      public void remove() {
         if (last == null) {
            throw new IllegalStateException();
         }
         AtomicMapProxyImpl.wait(rw.eval(key, new RemoveCond(last.getKey(), last.getValue())));
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy