/* * Copyright 2008-2009 LinkedIn, Inc * Copyright 2013 Big Switch Networks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ package org.sdnplatform.sync.internal.store; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.sdnplatform.sync.IClosableIterator; import org.sdnplatform.sync.IVersion; import org.sdnplatform.sync.Versioned; import org.sdnplatform.sync.IVersion.Occurred; import org.sdnplatform.sync.error.ObsoleteVersionException; import org.sdnplatform.sync.error.SyncException; import org.sdnplatform.sync.internal.util.Pair; /** * A simple non-persistent, in-memory store. */ public class InMemoryStorageEngine<K, V> implements IStorageEngine<K, V> { private final ConcurrentMap<K, List<Versioned<V>>> map; private final String name; /** * Interval in milliseconds before tombstones will be cleared. */ protected int tombstoneDeletion = 24 * 60 * 60 * 1000; public InMemoryStorageEngine(String name) { this.name = name; this.map = new ConcurrentHashMap<K, List<Versioned<V>>>(); } public InMemoryStorageEngine(String name, ConcurrentMap<K, List<Versioned<V>>> map) { this.name = name; this.map = map; } // ****************** // StorageEngine<K,V> // ****************** @Override public void close() {} @Override public List<IVersion> getVersions(K key) throws SyncException { return StoreUtils.getVersions(get(key)); } @Override public List<Versioned<V>> get(K key) throws SyncException { StoreUtils.assertValidKey(key); List<Versioned<V>> results = map.get(key); if(results == null) { return new ArrayList<Versioned<V>>(0); } synchronized(results) { return new ArrayList<Versioned<V>>(results); } } @Override public void put(K key, Versioned<V> value) throws SyncException { if (!doput(key, value)) throw new ObsoleteVersionException(); } public boolean doput(K key, Versioned<V> value) throws SyncException { StoreUtils.assertValidKey(key); IVersion version = value.getVersion(); while(true) { List<Versioned<V>> items = map.get(key); // If we have no value, optimistically try to add one if(items == null) { items = new ArrayList<Versioned<V>>(); items.add(new Versioned<V>(value.getValue(), version)); if (map.putIfAbsent(key, items) != null) continue; return true; } else { synchronized(items) { // if this check fails, items has been removed from the map // by delete, so we try again. if(map.get(key) != items) continue; // Check for existing versions - remember which items to // remove in case of success List<Versioned<V>> itemsToRemove = new ArrayList<Versioned<V>>(items.size()); for(Versioned<V> versioned: items) { Occurred occurred = value.getVersion().compare(versioned.getVersion()); if(occurred == Occurred.BEFORE) { return false; } else if(occurred == Occurred.AFTER) { itemsToRemove.add(versioned); } } items.removeAll(itemsToRemove); items.add(value); } return true; } } } @Override public IClosableIterator<Entry<K,List<Versioned<V>>>> entries() { return new InMemoryIterator<K, V>(map); } @Override public IClosableIterator<K> keys() { // TODO Implement more efficient version. return StoreUtils.keys(entries()); } @Override public void truncate() { map.clear(); } @Override public String getName() { return name; } @Override public boolean writeSyncValue(K key, Iterable<Versioned<V>> values) { boolean success = false; for (Versioned<V> value : values) { try { put (key, value); success = true; } catch (SyncException e) { // ignore } } return success; } @Override public void cleanupTask() { // Remove tombstones that are older than the tombstone deletion // threshold. If a value is deleted and the tombstone has been // cleaned up before the cluster is fully synchronized, then there // is a chance that deleted values could be resurrected Iterator<Entry<K, List<Versioned<V>>>> iter = map.entrySet().iterator(); while (iter.hasNext()) { Entry<K, List<Versioned<V>>> e = iter.next(); List<Versioned<V>> items = e.getValue(); synchronized (items) { if (StoreUtils.canDelete(items, tombstoneDeletion)) iter.remove(); } } } @Override public boolean isPersistent() { return false; } @Override public void setTombstoneInterval(int interval) { this.tombstoneDeletion = interval; } // ********************* // InMemoryStorageEngine // ********************* /** * Get the number of keys currently in the store * @return */ public int size() { return map.size(); } /** * Atomically remove the key and return the value that was mapped to it, * if any * @param key the key to remove * @return the mapped values */ public List<Versioned<V>> remove(K key) { while (true) { List<Versioned<V>> items = map.get(key); synchronized (items) { if (map.remove(key, items)) return items; } } } /** * Check whether the given key is present in the store * @param key the key * @return <code>true</code> if the key is present */ public boolean containsKey(K key) { return map.containsKey(key); } // ****** // Object // ****** @Override public String toString() { return toString(15); } // ************* // Local methods // ************* protected String toString(int size) { StringBuilder builder = new StringBuilder(); builder.append("{"); int count = 0; for(Entry<K, List<Versioned<V>>> entry: map.entrySet()) { if(count > size) { builder.append("..."); break; } builder.append(entry.getKey()); builder.append(':'); builder.append(entry.getValue()); builder.append(','); } builder.append('}'); return builder.toString(); } private static class InMemoryIterator<K, V> implements IClosableIterator<Entry<K, List<Versioned<V>>>> { private final Iterator<Entry<K, List<Versioned<V>>>> iterator; public InMemoryIterator(ConcurrentMap<K, List<Versioned<V>>> map) { this.iterator = map.entrySet().iterator(); } public boolean hasNext() { return iterator.hasNext(); } public Pair<K, List<Versioned<V>>> next() { Entry<K, List<Versioned<V>>> entry = iterator.next(); return new Pair<K, List<Versioned<V>>>(entry.getKey(), entry.getValue()); } public void remove() { throw new UnsupportedOperationException("No removal y'all."); } @Override public void close() { // nothing to do } } }