package net.onrc.onos.core.newintent; import com.hazelcast.core.EntryEvent; import com.hazelcast.core.EntryListener; import com.hazelcast.core.IMap; import net.onrc.onos.api.newintent.IntentId; import net.onrc.onos.core.datagrid.ISharedCollectionsService; import net.onrc.onos.core.util.serializers.KryoFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; import static com.google.common.base.Preconditions.checkNotNull; /** * A class representing map storing an intent related value associated with * intent ID as key. * <p> * Implementation-Specific: The backend of this data structure is Hazelcast IMap. * </p> * FIXME: refactor this class to aggregate logic for distributed listenable map. * Intent Service, Flow Manager, and Match-Action Service would want to have similar * logic to store and load the data from a distributed data structure, but these logic * is scattered in each package now. * * @param <V> the type of value */ class IntentMap<V> { private static final Logger log = LoggerFactory.getLogger(IntentMap.class); private final Class<V> valueType; private final IMap<String, byte[]> map; /** * Constructs a map which stores intent related information with the specified arguments. * * @param name name of the map * @param valueType type of value * @param collectionsService service for creating Hazelcast IMap */ public IntentMap(String name, Class<V> valueType, ISharedCollectionsService collectionsService) { this.valueType = checkNotNull(valueType); this.map = checkNotNull(collectionsService.getConcurrentMap(name, String.class, byte[].class)); } /** * Stores the specified value associated with the intent ID. * * @param id intent ID * @param value value */ public void put(IntentId id, V value) { checkNotNull(id); checkNotNull(value); map.set(id.toString(), KryoFactory.serialize(value)); } /** * Returns the value associated with the specified intent ID. * * @param id intent ID * @return the value associated with the key */ public V get(IntentId id) { checkNotNull(id); byte[] bytes = map.get(id.toString()); if (bytes == null) { return null; } return KryoFactory.deserialize(bytes); } /** * Removes the value associated with the specified intent ID. * * @param id intent ID */ public void remove(IntentId id) { checkNotNull(id); map.remove(id.toString()); } /** * Returns all values stored in the instance. * * @return all values stored in the sintance. */ public Collection<V> values() { Collection<V> values = new ArrayList<>(); for (byte[] bytes : map.values()) { V value = KryoFactory.deserialize(bytes); if (value == null) { continue; } values.add(value); } return values; } /** * Adds an entry listener for this map. Listener will get notified for all events. * * @param listener entry listener */ public void addListener(final EntryListener<IntentId, V> listener) { checkNotNull(listener); EntryListener<String, byte[]> internalListener = new EntryListener<String, byte[]>() { @Override public void entryAdded(EntryEvent<String, byte[]> event) { listener.entryAdded(convertEntryEvent(event)); } @Override public void entryRemoved(EntryEvent<String, byte[]> event) { listener.entryRemoved(convertEntryEvent(event)); } @Override public void entryUpdated(EntryEvent<String, byte[]> event) { listener.entryUpdated(convertEntryEvent(event)); } @Override public void entryEvicted(EntryEvent<String, byte[]> event) { listener.entryEvicted(convertEntryEvent(event)); } /** * Converts an entry event used internally to another entry event exposed externally. * * @param internalEvent entry event used internally used * @return entry event exposed externally */ private EntryEvent<IntentId, V> convertEntryEvent(EntryEvent<String, byte[]> internalEvent) { EntryEvent<IntentId, V> converted = new EntryEvent<>( internalEvent.getSource(), internalEvent.getMember(), internalEvent.getEventType().getType(), IntentId.valueOf(internalEvent.getKey()), KryoFactory.<V>deserialize(internalEvent.getValue()) ); return converted; } }; map.addEntryListener(internalListener, true); } /** * Destroys the backend Hazelcast IMap. This method is only for testing purpose. */ void destroy() { map.destroy(); } }