Java tutorial
/** * Copyright 2009 - 2011 Sergio Bossa (sergio.bossa@gmail.com) * * 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 terrastore.store.impl; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terracotta.collections.ClusteredMap; import org.terracotta.collections.ConcurrentDistributedServerMap; import terrastore.internal.tc.TCMaster; import terrastore.common.ErrorMessage; import terrastore.event.EventBus; import terrastore.event.impl.ValueChangedEvent; import terrastore.event.impl.ValueRemovedEvent; import terrastore.server.Keys; import terrastore.server.Values; import terrastore.store.comparators.LexicographicalComparator; import terrastore.store.Bucket; import terrastore.store.FlushCondition; import terrastore.store.FlushStrategy; import terrastore.store.Key; import terrastore.store.LockManager; import terrastore.store.SnapshotManager; import terrastore.store.SortedSnapshot; import terrastore.store.StoreOperationException; import terrastore.store.ValidationException; import terrastore.store.features.Predicate; import terrastore.store.features.Update; import terrastore.store.Value; import terrastore.store.features.Mapper; import terrastore.store.operators.Condition; import terrastore.store.operators.Function; import terrastore.store.features.Range; import terrastore.store.operators.Comparator; import terrastore.store.operators.OperatorException; import terrastore.util.collect.Sets; import terrastore.util.collect.Transformer; import terrastore.util.concurrent.GlobalExecutor; /** * @author Sergio Bossa */ public class TCBucket implements Bucket { private static final Logger LOG = LoggerFactory.getLogger(TCBucket.class); // private static final String BUCKET_LOCK_KEY_PREFIX = TCBucket.class.getName() + ".BUCKET_LOCK_KEY."; // private final String name; private final ClusteredMap<String, byte[]> bucket; private final Key bucketLockKey; private boolean compressedDocuments; private EventBus eventBus; private SnapshotManager snapshotManager; private LockManager lockManager; private Comparator defaultComparator = new LexicographicalComparator(true); private final Map<String, Comparator> comparators = new HashMap<String, Comparator>(); private final Map<String, Condition> conditions = new HashMap<String, Condition>(); private final Map<String, Function> updaters = new HashMap<String, Function>(); private final Map<String, Function> mappers = new HashMap<String, Function>(); public TCBucket(String name) { this.name = name; this.bucket = TCMaster.getInstance().getUnlockedMap(TCBucket.class.getName() + ".bucket." + name); this.bucketLockKey = new Key(BUCKET_LOCK_KEY_PREFIX + name); } public String getName() { return name; } public void put(Key key, Value value) { // Use explicit locking to put and publish on the same "transactional" boundary and keep ordering under concurrency. lockWrite(key); try { Value old = doGet(key); doPut(key, value); if (eventBus.isEnabled()) { eventBus.publish(new ValueChangedEvent(name, key.toString(), old, value)); } } finally { unlockWrite(key); } } public boolean conditionalPut(Key key, Value value, Predicate predicate) throws StoreOperationException { // Use explicit locking to put and publish on the same "transactional" boundary and keep ordering under concurrency. lockWrite(key); try { Condition condition = getCondition(predicate.getConditionType()); Value old = doGet(key); if (old == null || old.dispatch(key, predicate, condition)) { doPut(key, value); if (eventBus.isEnabled()) { eventBus.publish(new ValueChangedEvent(name, key.toString(), old, value)); } return true; } else { return false; } } catch (OperatorException ex) { throw new StoreOperationException(ex.getErrorMessage()); } finally { unlockWrite(key); } } public boolean conditionalRemove(Key key, Predicate predicate) throws StoreOperationException { // Use explicit locking to make sure we see a consistent state while examining, removing and publishing. lockWrite(key); try { Condition condition = getCondition(predicate.getConditionType()); Value value = doGet(key); if (value != null && value.dispatch(key, predicate, condition)) { doRemove(key); if (eventBus.isEnabled()) { eventBus.publish(new ValueRemovedEvent(name, key.toString(), value)); } return true; } else { return false; } } catch (OperatorException ex) { throw new StoreOperationException(ex.getErrorMessage()); } finally { unlockWrite(key); } } public Value get(Key key) throws StoreOperationException { Value value = doGet(key); if (value != null) { return value; } else { throw new StoreOperationException( new ErrorMessage(ErrorMessage.NOT_FOUND_ERROR_CODE, "Key not found: " + key)); } } @Override public Values get(Set<Key> keys) throws StoreOperationException { Map<Key, Value> result = new HashMap<Key, Value>(keys.size()); for (Key key : keys) { Value value = doGet(key); if (value != null) { result.put(key, value); } } return new Values(result); } @Override public Value conditionalGet(Key key, Predicate predicate) throws StoreOperationException { Value value = doGet(key); if (value != null) { try { Condition condition = getCondition(predicate.getConditionType()); if (value.dispatch(key, predicate, condition)) { return value; } else { return null; } } catch (OperatorException ex) { throw new StoreOperationException(ex.getErrorMessage()); } } else { throw new StoreOperationException( new ErrorMessage(ErrorMessage.NOT_FOUND_ERROR_CODE, "Key not found: " + key)); } } @Override public Values conditionalGet(Set<Key> keys, Predicate predicate) throws StoreOperationException { Map<Key, Value> result = new HashMap<Key, Value>(keys.size()); for (Key key : keys) { try { Value value = doGet(key); Condition condition = getCondition(predicate.getConditionType()); if (value.dispatch(key, predicate, condition)) { result.put(key, value); } } catch (OperatorException ex) { throw new StoreOperationException(ex.getErrorMessage()); } } return new Values(result); } public void remove(Key key) throws StoreOperationException { // Use explicit locking to remove and publish on the same "transactional" boundary and keep ordering under concurrency. lockWrite(key); try { Value removed = doGet(key); doRemove(key); if (removed != null) { if (eventBus.isEnabled()) { eventBus.publish(new ValueRemovedEvent(name, key.toString(), removed)); } } } finally { unlockWrite(key); } } @Override public Value update(final Key key, final Update update) throws StoreOperationException { long timeout = update.getTimeoutInMillis(); Future<Value> task = null; // Use explicit locking to update and block concurrent operations on the same key, // and also publish on the same "transactional" boundary and keep ordering under concurrency. lockWrite(key); try { final Value value = doGet(key); if (value != null) { final Function function = getFunction(updaters, update.getFunctionName()); task = GlobalExecutor.getUpdateExecutor().submit(new Callable<Value>() { @Override public Value call() { try { return value.dispatch(key, update, function); } catch (OperatorException ex) { throw new RuntimeException(ex); } } }); Value result = task.get(timeout, TimeUnit.MILLISECONDS); doPut(key, result); if (eventBus.isEnabled()) { eventBus.publish(new ValueChangedEvent(name, key.toString(), value, result)); } return result; } else { throw new StoreOperationException( new ErrorMessage(ErrorMessage.NOT_FOUND_ERROR_CODE, "Key not found: " + key)); } } catch (StoreOperationException ex) { throw ex; } catch (TimeoutException ex) { task.cancel(true); throw new StoreOperationException(new ErrorMessage(ErrorMessage.INTERNAL_SERVER_ERROR_CODE, "Update cancelled due to long execution time.")); } catch (ExecutionException ex) { if (ex.getCause() instanceof RuntimeException && ex.getCause().getCause() instanceof OperatorException) { throw new StoreOperationException(((OperatorException) ex.getCause().getCause()).getErrorMessage()); } else { throw new StoreOperationException( new ErrorMessage(ErrorMessage.INTERNAL_SERVER_ERROR_CODE, ex.getMessage())); } } catch (Exception ex) { throw new StoreOperationException( new ErrorMessage(ErrorMessage.INTERNAL_SERVER_ERROR_CODE, ex.getMessage())); } finally { unlockWrite(key); } } @Override public Value merge(Key key, Value value) throws StoreOperationException { // Use explicit locking to update and block concurrent operations on the same key, // and also publish on the same "transactional" boundary and keep ordering under concurrency. lockWrite(key); try { Value old = doGet(key); Value result = null; if (old != null) { result = old.merge(value); doPut(key, result); if (eventBus.isEnabled()) { eventBus.publish(new ValueChangedEvent(name, key.toString(), old, result)); } return result; } else { throw new StoreOperationException( new ErrorMessage(ErrorMessage.NOT_FOUND_ERROR_CODE, "Key not found: " + key)); } } catch (StoreOperationException ex) { throw ex; } catch (ValidationException ex) { throw new StoreOperationException( new ErrorMessage(ErrorMessage.BAD_REQUEST_ERROR_CODE, ex.getMessage())); } finally { unlockWrite(key); } } public Map<String, Object> map(final Key key, final Mapper mapper) throws StoreOperationException { Value value = doGet(key); if (value != null) { try { Function function = getFunction(mappers, mapper.getMapperName()); return value.dispatch(key, mapper, function); } catch (OperatorException ex) { throw new StoreOperationException(ex.getErrorMessage()); } } else { return null; } } @Override public void clear() { bucket.clear(); } @Override public long size() { return bucket.size(); } @Override public Keys keys() { return new Keys(Sets.transformed(bucket.keySet(), new KeyDeserializer())); } @Override public Keys keysInRange(Range keyRange) throws StoreOperationException { Comparator keyComparator = getComparator(keyRange.getKeyComparatorName()); SortedSnapshot snapshot = snapshotManager.getOrComputeSortedSnapshot(this, keyComparator, keyRange.getKeyComparatorName(), keyRange.getTimeToLive()); return new Keys(snapshot.keysInRange(keyRange.getStartKey(), keyRange.getEndKey(), keyRange.getLimit())); } @Override public void flush(FlushStrategy flushStrategy, FlushCondition flushCondition) { // TODO / WARN: ConcurrentDistributedServerMap doesn't allow to selectively flush keys anymore ... but let's keep this // signature in case flush gets implemented later. if (bucket.getClass().getName().equals(ConcurrentDistributedServerMap.class.getName())) { lockWrite(bucketLockKey); try { LOG.warn("Request to flush on bucket {}", name); bucket.getClass().getMethod("clearLocalCache").invoke(bucket); } catch (Exception ex) { throw new IllegalStateException(ex.getMessage(), ex); } finally { unlockWrite(bucketLockKey); } } else { LOG.warn("Running outside of cluster, no keys to flush!"); } } @Override public void setCompressDocuments(boolean compressed) { this.compressedDocuments = compressed; } @Override public void setEventBus(EventBus eventBus) { this.eventBus = eventBus; } @Override public void setSnapshotManager(SnapshotManager snapshotManager) { this.snapshotManager = snapshotManager; } @Override public void setLockManager(LockManager lockManager) { this.lockManager = lockManager; } @Override public void setDefaultComparator(Comparator defaultComparator) { this.defaultComparator = defaultComparator; } @Override public void setComparators(Map<String, Comparator> comparators) { this.comparators.clear(); this.comparators.putAll(comparators); } @Override public void setUpdaters(Map<String, Function> functions) { this.updaters.clear(); this.updaters.putAll(functions); } @Override public void setMappers(Map<String, Function> functions) { this.mappers.clear(); this.mappers.putAll(functions); } @Override public void setConditions(Map<String, Condition> conditions) { this.conditions.clear(); this.conditions.putAll(conditions); } private Comparator getComparator(String comparatorName) throws StoreOperationException { if (comparators.containsKey(comparatorName)) { return comparators.get(comparatorName); } else if (StringUtils.isBlank(comparatorName)) { return defaultComparator; } else { throw new StoreOperationException(new ErrorMessage(ErrorMessage.BAD_REQUEST_ERROR_CODE, "Wrong comparator name: " + comparatorName)); } } private Function getFunction(Map<String, Function> functions, String functionName) throws StoreOperationException { if (functions.containsKey(functionName)) { return functions.get(functionName); } else { throw new StoreOperationException( new ErrorMessage(ErrorMessage.BAD_REQUEST_ERROR_CODE, "Wrong function name: " + functionName)); } } private Condition getCondition(String conditionType) throws StoreOperationException { if (conditions.containsKey(conditionType)) { return conditions.get(conditionType); } else { throw new StoreOperationException(new ErrorMessage(ErrorMessage.BAD_REQUEST_ERROR_CODE, "Wrong condition type: " + conditionType)); } } private void lockRead(Key key) { lockManager.lockRead(name, key); } private void unlockRead(Key key) { lockManager.unlockRead(name, key); } private void lockWrite(Key key) { lockManager.lockWrite(name, key); } private void unlockWrite(Key key) { lockManager.unlockWrite(name, key); } private byte[] valueToBytes(Value value) { if (value != null) { return compressedDocuments ? value.getCompressedBytes() : value.getBytes(); } else { return null; } } private Value bytesToValue(byte[] bytes) { if (bytes != null) { return new Value(bytes); } else { return null; } } private Value doGet(Key key) { Value value = bytesToValue(bucket.unsafeGet(key.toString())); if (value == null) { lockRead(key); try { value = bytesToValue(bucket.unlockedGet(key.toString())); } finally { unlockRead(key); } } return value; } private void doRemove(Key key) { bucket.unlockedRemoveNoReturn(key.toString()); } private void doPut(Key key, Value value) { bucket.unlockedPutNoReturn(key.toString(), valueToBytes(value)); } private static class KeyDeserializer implements Transformer<String, Key> { @Override public Key transform(String input) { return new Key(input); } } }