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

org.jgroups.blocks.ReplicatedHashMap Maven / Gradle / Ivy

There is a newer version: 5.3.13.Final
Show newest version
package org.jgroups.blocks;

import org.jgroups.*;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.Util;

import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors;

/**
 * Implementation of a {@link java.util.concurrent.ConcurrentMap} with replication of the contents across a cluster.
 * Any change to the hashmap (clear(), put(), remove() etc) will transparently be propagated to all replicas in the group.
 * All read-only methods will always access the local replica.
 * 

* Keys and values added to the hashmap must be serializable, the reason being that they will be sent * across the network to all replicas of the group.

* A {@code ReplicatedHashMap} allows one to implement a distributed naming service in just a couple of lines. *

* An instance of this class will contact an existing member of the group to fetch its initial state. * * @author Bela Ban */ public class ReplicatedHashMap extends AbstractMap implements ConcurrentMap, Receiver, ReplicatedMap, Closeable { public interface Notification { void entrySet(K key, V value); void entryRemoved(K key); void viewChange(View view, java.util.List

mbrs_joined, java.util.List
mbrs_left); void contentsSet(Map new_entries); void contentsCleared(); } private static final short PUT = 1; private static final short PUT_IF_ABSENT = 2; private static final short PUT_ALL = 3; private static final short REMOVE = 4; private static final short REMOVE_IF_EQUALS = 5; private static final short REPLACE_IF_EXISTS = 6; private static final short REPLACE_IF_EQUALS = 7; private static final short CLEAR = 8; protected static Map methods; static { try { methods = new HashMap<>(8); methods.put(PUT, ReplicatedHashMap.class.getMethod("_put", Object.class, Object.class)); methods.put(PUT_IF_ABSENT, ReplicatedHashMap.class.getMethod("_putIfAbsent", Object.class, Object.class)); methods.put(PUT_ALL, ReplicatedHashMap.class.getMethod("_putAll", Map.class)); methods.put(REMOVE, ReplicatedHashMap.class.getMethod("_remove", Object.class)); methods.put(REMOVE_IF_EQUALS, ReplicatedHashMap.class.getMethod("_remove", Object.class, Object.class)); methods.put(REPLACE_IF_EXISTS, ReplicatedHashMap.class.getMethod("_replace", Object.class, Object.class)); methods.put(REPLACE_IF_EQUALS, ReplicatedHashMap.class.getMethod("_replace", Object.class, Object.class, Object.class)); methods.put(CLEAR, ReplicatedHashMap.class.getMethod("_clear")); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private final JChannel channel; protected RpcDispatcher disp = null; private String cluster_name = null; // to be notified when mbrship changes private final Set notifs = new CopyOnWriteArraySet<>(); private final List
members = new ArrayList<>(); // keeps track of all DHTs protected final RequestOptions call_options = new RequestOptions(ResponseMode.GET_NONE, 5000); protected final Log log = LogFactory.getLog(this.getClass()); /** * wrapped map instance */ protected ConcurrentMap map = null; /** * Constructs a new ReplicatedHashMap with channel. Call {@link #start(long)} to start this map. */ public ReplicatedHashMap(JChannel channel) { this.channel = channel; this.map = new ConcurrentHashMap<>(); init(); } /** * Constructs a new ReplicatedHashMap using provided map instance. */ public ReplicatedHashMap(ConcurrentMap map, JChannel channel) { if (channel == null) { throw new IllegalArgumentException("Cannot create ReplicatedHashMap with null channel"); } if (map == null) { throw new IllegalArgumentException("Cannot create ReplicatedHashMap with null map"); } this.map = map; this.cluster_name = channel.getClusterName(); this.channel = channel; init(); } protected final void init() { disp=new RpcDispatcher(channel, this).setMethodLookup(id -> methods.get(id)); disp.setReceiver(this); } public boolean isBlockingUpdates() { return call_options.mode() == ResponseMode.GET_ALL; } /** * Whether updates across the cluster should be asynchronous (default) or synchronous) * * @param blocking_updates */ public void setBlockingUpdates(boolean blocking_updates) { call_options.mode(blocking_updates ? ResponseMode.GET_ALL : ResponseMode.GET_NONE); } /** * The timeout (in milliseconds) for blocking updates */ public long getTimeout() { return call_options.timeout(); } /** * Sets the cluster call timeout (until all acks have been received) * * @param timeout The timeout (in milliseconds) for blocking updates */ public void setTimeout(long timeout) { call_options.timeout(timeout); } /** * Fetches the state * * @param state_timeout */ public final void start(long state_timeout) throws Exception { channel.getState(null, state_timeout); } public Address getLocalAddress() { return channel != null ? channel.getAddress() : null; } public String getClusterName() { return cluster_name; } public JChannel getChannel() { return channel; } public void addNotifier(Notification n) { if (n != null) { notifs.add(n); } } public void removeNotifier(Notification n) { if (n != null) { notifs.remove(n); } } public void stop() { if (disp != null) { disp.stop(); disp = null; } Util.close(channel); } @Override public void close() throws IOException { stop(); } /** * Maps the specified key to the specified value in this table. Neither the key nor the value can be null.

*

* The value can be retrieved by calling the get method with a key that is equal to the original key. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with key, or * null if there was no mapping for key * @throws NullPointerException if the specified key or value is null */ public V put(K key, V value) { V prev_val = get(key); try { MethodCall call = new MethodCall(PUT, key, value); disp.callRemoteMethods(null, call, call_options); } catch (Exception e) { throw new RuntimeException("put(" + key + ", " + value + ") failed", e); } return prev_val; } /** * @return the previous value associated with the specified key, or null if there was no mapping for the key * @throws NullPointerException if the specified key or value is null */ public V putIfAbsent(K key, V value) { V prev_val = get(key); try { MethodCall call = new MethodCall(PUT_IF_ABSENT, key, value); disp.callRemoteMethods(null, call, call_options); } catch (Exception e) { throw new RuntimeException("putIfAbsent(" + key + ", " + value + ") failed", e); } return prev_val; } /** * Copies all of the mappings from the specified map to this one. These * mappings replace any mappings that this map had for any of the keys * currently in the specified map. * * @param m mappings to be stored in this map */ public void putAll(Map m) { try { MethodCall call = new MethodCall(PUT_ALL, m); disp.callRemoteMethods(null, call, call_options); } catch (Throwable t) { throw new RuntimeException("putAll() failed", t); } } /** * Removes all of the mappings from this map. */ public void clear() { try { MethodCall call = new MethodCall(CLEAR); disp.callRemoteMethods(null, call, call_options); } catch (Exception e) { throw new RuntimeException("clear() failed", e); } } /** * Removes the key (and its corresponding value) from this map. This method * does nothing if the key is not in the map. * * @param key the key that needs to be removed * @return the previous value associated with key, or * null if there was no mapping for key * @throws NullPointerException if the specified key is null */ public V remove(Object key) { V retval = get(key); try { MethodCall call = new MethodCall(REMOVE, key); disp.callRemoteMethods(null, call, call_options); } catch (Exception e) { throw new RuntimeException("remove(" + key + ") failed", e); } return retval; } /** * @throws NullPointerException if the specified key is null */ public boolean remove(Object key, Object value) { Object val = get(key); boolean removed =Objects.equals(val, value); try { MethodCall call = new MethodCall(REMOVE_IF_EQUALS, key, value); disp.callRemoteMethods(null, call, call_options); } catch (Exception e) { throw new RuntimeException("remove(" + key + ", " + value + ") failed", e); } return removed; } /** * @throws NullPointerException if any of the arguments are null */ public boolean replace(K key, V oldValue, V newValue) { Object val = get(key); boolean replaced=Objects.equals(val, oldValue); try { MethodCall call = new MethodCall(REPLACE_IF_EQUALS, key, oldValue, newValue); disp.callRemoteMethods(null, call, call_options); } catch (Exception e) { throw new RuntimeException("replace(" + key + ", " + oldValue + ", " + newValue + ") failed", e); } return replaced; } /** * @return the previous value associated with the specified key, or * null if there was no mapping for the key * @throws NullPointerException if the specified key or value is null */ public V replace(K key, V value) { V retval = get(key); try { MethodCall call = new MethodCall(REPLACE_IF_EXISTS, key, value); disp.callRemoteMethods(null, call, call_options); } catch (Exception e) { throw new RuntimeException("replace(" + key + ", " + value + ") failed", e); } return retval; } /*------------------------ Callbacks -----------------------*/ public V _put(K key, V value) { V retval = map.put(key, value); for (Notification notif : notifs) notif.entrySet(key, value); return retval; } public V _putIfAbsent(K key, V value) { V retval = map.putIfAbsent(key, value); for (Notification notif : notifs) notif.entrySet(key, value); return retval; } /** * @see java.util.Map#putAll(java.util.Map) */ public void _putAll(Map map) { if (map == null) { return; } // Calling the method below seems okay, but would result in ... deadlock ! // The reason is that Map.putAll() calls put(), which we override, which results in // lock contention for the map. // ---> super.putAll(m); <--- CULPRIT !!!@#$%$ // That said let's do it the stupid way: for (Entry entry : map.entrySet()) { this.map.put(entry.getKey(), entry.getValue()); } if (!map.isEmpty()) { for (Notification notif : notifs) notif.contentsSet(map); } } public void _clear() { map.clear(); notifs.forEach(Notification::contentsCleared); } public V _remove(K key) { V retval = map.remove(key); if (retval != null) { for (Notification notif : notifs) notif.entryRemoved(key); } return retval; } public boolean _remove(K key, V value) { boolean removed = map.remove(key, value); if (removed) { for (Notification notif : notifs) notif.entryRemoved(key); } return removed; } public boolean _replace(K key, V oldValue, V newValue) { boolean replaced = map.replace(key, oldValue, newValue); if (replaced) { for (Notification notif : notifs) notif.entrySet(key, newValue); } return replaced; } public V _replace(K key, V value) { V retval = map.replace(key, value); for (Notification notif : notifs) notif.entrySet(key, value); return retval; } /*----------------------------------------------------------*/ /*-------------------- State Exchange ----------------------*/ public void getState(OutputStream ostream) throws Exception { HashMap copy = new HashMap<>(); for (Entry entry : entrySet()) { K key = entry.getKey(); V val = entry.getValue(); copy.put(key, val); } try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(ostream, 1024))) { oos.writeObject(copy); } } public void setState(InputStream istream) throws Exception { HashMap new_copy = null; try (ObjectInputStream ois = new ObjectInputStream(istream)) { new_copy = (HashMap) ois.readObject(); } if (new_copy != null) { _putAll(new_copy); } log.debug("state received successfully"); } /*------------------- Membership Changes ----------------------*/ public void viewAccepted(View new_view) { List

new_mbrs = new_view.getMembers(); if (new_mbrs != null) { sendViewChangeNotifications(new_view, new_mbrs, new ArrayList<>(members)); // notifies observers (joined, left) members.clear(); members.addAll(new_mbrs); } } void sendViewChangeNotifications(View view, List
new_mbrs, List
old_mbrs) { if ((notifs.isEmpty()) || (old_mbrs == null) || (new_mbrs == null)) { return; } // 1. Compute set of members that joined: all that are in new_mbrs, but not in old_mbrs List
joined = new_mbrs.stream().filter(mbr -> !old_mbrs.contains(mbr)).collect(Collectors.toList()); // 2. Compute set of members that left: all that were in old_mbrs, but not in new_mbrs List
left = old_mbrs.stream().filter(mbr -> !new_mbrs.contains(mbr)).collect(Collectors.toList()); notifs.forEach(notif -> notif.viewChange(view, joined, left)); } /** * Creates a synchronized facade for a ReplicatedMap. All methods which * change state are invoked through a monitor. This is similar to * {@link java.util.Collections#synchronizedMap(Map)}, but also includes the replication * methods (starting with an underscore). * * @param map * @return */ public static ReplicatedMap synchronizedMap(ReplicatedMap map) { return new SynchronizedReplicatedMap<>(map); } private static final class SynchronizedReplicatedMap implements ReplicatedMap { private final ReplicatedMap map; private final Object mutex; private SynchronizedReplicatedMap(ReplicatedMap map) { this.map = map; this.mutex = this; } public int size() { synchronized (mutex) { return map.size(); } } public boolean isEmpty() { synchronized (mutex) { return map.isEmpty(); } } public boolean containsKey(Object key) { synchronized (mutex) { return map.containsKey(key); } } public boolean containsValue(Object value) { synchronized (mutex) { return map.containsValue(value); } } public V get(Object key) { synchronized (mutex) { return map.get(key); } } public V put(K key, V value) { synchronized (mutex) { return map.put(key, value); } } public void putAll(Map m) { synchronized (mutex) { map.putAll(m); } } public void clear() { synchronized (mutex) { map.clear(); } } public V putIfAbsent(K key, V value) { synchronized (mutex) { return map.putIfAbsent(key, value); } } public boolean remove(Object key, Object value) { synchronized (mutex) { return map.remove(key, value); } } public boolean replace(K key, V oldValue, V newValue) { synchronized (mutex) { return map.replace(key, oldValue, newValue); } } public V replace(K key, V value) { synchronized (mutex) { return map.replace(key, value); } } private Set keySet = null; private Set> entrySet = null; private Collection values = null; public Set keySet() { synchronized (mutex) { if (keySet == null) { keySet = Collections.synchronizedSet(map.keySet()); } return keySet; } } public Collection values() { synchronized (mutex) { if (values == null) { values = Collections.synchronizedCollection(map.values()); } return values; } } public Set> entrySet() { synchronized (mutex) { if (entrySet == null) { entrySet = Collections.synchronizedSet(map.entrySet()); } return entrySet; } } public V remove(Object key) { synchronized (mutex) { return map.remove(key); } } public V _put(K key, V value) { synchronized (mutex) { return map._put(key, value); } } public void _putAll(Map map) { synchronized (mutex) { this.map._putAll(map); } } public void _clear() { synchronized (mutex) { map._clear(); } } public V _remove(K key) { synchronized (mutex) { return map._remove(key); } } public V _putIfAbsent(K key, V value) { synchronized (mutex) { return map._putIfAbsent(key, value); } } public boolean _remove(K key, V value) { synchronized (mutex) { return map._remove(key, value); } } public boolean _replace(K key, V oldValue, V newValue) { synchronized (mutex) { return map._replace(key, oldValue, newValue); } } public V _replace(K key, V value) { synchronized (mutex) { return map._replace(key, value); } } public String toString() { synchronized (mutex) { return map.toString(); } } public int hashCode() { synchronized (mutex) { return map.hashCode(); } } public boolean equals(Object obj) { synchronized (mutex) { return map.equals(obj); } } } // Delegate Methods @Override public boolean containsKey(Object key) { return map.containsKey(key); } @Override public boolean containsValue(Object value) { return map.containsValue(value); } @Override public Set> entrySet() { return new AbstractSet<>() { @Override public void clear() { ReplicatedHashMap.this.clear(); } @Override public Iterator> iterator() { final Iterator> it=map.entrySet().iterator(); return new Iterator<>() { Entry cur=null; public boolean hasNext() { return it.hasNext(); } public Entry next() { cur=it.next(); return cur; } public void remove() { if(cur == null) { throw new IllegalStateException(); } ReplicatedHashMap.this.remove(cur.getKey()); cur=null; } }; } public int size() { return map.size(); } }; } @Override public V get(Object key) { return map.get(key); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy