Java tutorial
/* * The MIT License (MIT) * * Copyright (c) 2013-2014 Jeff Nelson, Cinchapi Software Collective * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.cinchapi.concourse.server.storage; import java.util.Map; import java.util.Set; import org.cinchapi.concourse.server.concurrent.LockService; import org.cinchapi.concourse.server.concurrent.RangeLockService; import org.cinchapi.concourse.server.storage.temp.Limbo; import org.cinchapi.concourse.server.storage.temp.Write; import org.cinchapi.concourse.thrift.Operator; import org.cinchapi.concourse.thrift.TObject; import org.cinchapi.concourse.time.Time; import com.google.common.collect.Sets; /** * A {@link BufferedStore} holds data in a {@link ProxyStore} buffer before * making batch commits to some other {@link PermanentStore}. * <p> * Data is written to the buffer until the buffer is full, at which point the * BufferingStore will flush the data to the destination store. Reads are * handled by taking the XOR (see {@link Sets#symmetricDifference(Set, Set)} or * XOR truth (see <a * href="http://en.wikipedia.org/wiki/Exclusive_or#Truth_table" * >http://en.wikipedia.org/wiki/Exclusive_or#Truth_table</a>) of the values * read from the buffer and the destination. * </p> * * @author jnelson */ public abstract class BufferedStore extends BaseStore { // NOTE ON LOCKING: // ================ // We can't do any global locking to coordinate the #buffer and #destination // in this class because we cannot make assumptions about how the // implementing class actually wants to handle concurrency (i.e. an // AtomicOperation does not grab any coordinating locks until it goes to // commit so that it doesn't do any unnecessary blocking). // NOTE ON HISTORICAL READS // ======================== // Buffered historical reads do not merely delegate to their timestamp // accepting counterparts because we expect the #destination to use // different code paths for present vs historical reads unlike Limbo. /** * The {@code buffer} is the place where data is initially stored. The * contained data is eventually moved to the {@link #destination} when the * {@link Limbo#transport(PermanentStore)} method is called. */ protected final Limbo buffer; /** * The {@code destination} is the place where data is stored when it is * transferred from the {@link #buffer}. The {@code destination} defines its * protocol for accepting data in the {@link PermanentStore#accept(Write)} * method. */ protected final PermanentStore destination; /** * The {@link LockService} that is used to coordinate concurrent operations. */ protected LockService lockService; /** * The {@link RangeLockService} that is used to coordinate concurrent * operations. */ protected RangeLockService rangeLockService; /** * Construct a new instance. * * @param transportable * @param destination * @param lockService * @param rangeLockService */ protected BufferedStore(Limbo transportable, PermanentStore destination, LockService lockService, RangeLockService rangeLockService) { this.buffer = transportable; this.destination = destination; this.lockService = lockService; this.rangeLockService = rangeLockService; } /** * Add {@code key} as {@code value} to {@code record}. * <p> * This method maps {@code key} to {@code value} in {@code record}, if and * only if that mapping does not <em>currently</em> exist (i.e. * {@link #verify(String, Object, long)} is {@code false}). Adding * {@code value} to {@code key} does not replace any existing mappings from * {@code key} in {@code record} because a field may contain multiple * distinct values. * </p> * * @param key * @param value * @param record * @return {@code true} if the mapping is added */ public boolean add(String key, TObject value, long record) { Write write = Write.add(key, value, record); if (!verify(write)) { return buffer.insert(write); /* Authorized */ } return false; } @Override public Map<Long, String> audit(long record) { Map<Long, String> result = destination.audit(record); result.putAll(buffer.audit(record)); return result; } @Override public Map<Long, String> audit(String key, long record) { Map<Long, String> result = destination.audit(key, record); result.putAll(buffer.audit(key, record)); return result; } @Override public Map<TObject, Set<Long>> browse(String key) { Map<TObject, Set<Long>> context = destination.browse(key); return buffer.browse(key, Time.now(), context); } @Override public Map<TObject, Set<Long>> browse(String key, long timestamp) { Map<TObject, Set<Long>> context = destination.browse(key, timestamp); return buffer.browse(key, timestamp, context); } @Override public Map<String, Set<TObject>> browse(long record) { Map<String, Set<TObject>> context = destination.browse(record); return buffer.browse(record, Time.now(), context); } @Override public Map<String, Set<TObject>> browse(long record, long timestamp) { Map<String, Set<TObject>> context = destination.browse(record, timestamp); return buffer.browse(record, timestamp, context); } @Override public Set<TObject> fetch(String key, long record) { Set<TObject> context = destination.fetch(key, record); return buffer.fetch(key, record, Time.now(), context); } @Override public Set<TObject> fetch(String key, long record, long timestamp) { Set<TObject> context = destination.fetch(key, record, timestamp); return buffer.fetch(key, record, timestamp, context); } /** * Remove {@code key} as {@code value} from {@code record}. * <p> * This method deletes the mapping from {@code key} to {@code value} in * {@code record}, if that mapping <em>currently</em> exists (i.e. * {@link #verify(String, Object, long)} is {@code true}. No other mappings * from {@code key} in {@code record} are affected. * </p> * * @param key * @param value * @param record * @return {@code true} if the mapping is removed */ public boolean remove(String key, TObject value, long record) { Write write = Write.remove(key, value, record); if (verify(write)) { return buffer.insert(write); /* Authorized */ } return false; } @Override public Set<Long> search(String key, String query) { // FIXME: should this be implemented using a context instead? return Sets.symmetricDifference(buffer.search(key, query), destination.search(key, query)); } @Override public boolean verify(String key, TObject value, long record) { return buffer.verify(Write.notStorable(key, value, record), destination.verify(key, value, record)); } @Override public boolean verify(String key, TObject value, long record, long timestamp) { return buffer.verify(Write.notStorable(key, value, record), timestamp, destination.verify(key, value, record, timestamp)); } @Override protected Map<Long, Set<TObject>> doExplore(long timestamp, String key, Operator operator, TObject... values) { Map<Long, Set<TObject>> context = destination.explore(timestamp, key, operator, values); return buffer.explore(context, timestamp, key, operator, values); } protected Map<Long, Set<TObject>> doExplore(String key, Operator operator, TObject... values) { Map<Long, Set<TObject>> context = destination.explore(key, operator, values); return buffer.explore(context, Time.now(), key, operator, values); } /** * Shortcut method to verify {@code write}. This method is called from * {@link #add(String, TObject, long)} and * {@link #remove(String, TObject, long)} so that we can avoid creating a * duplicate Write. * * @param write * @return {@code true} if {@code write} currently exists */ private boolean verify(Write write) { return buffer.verify(write, destination.verify(write.getKey().toString(), write.getValue().getTObject(), write.getRecord().longValue())); } }