Package com.urbanairship.datacube.dbharnesses

Source Code of com.urbanairship.datacube.dbharnesses.MapDbHarness$NullFuture

/*
Copyright 2012 Urban Airship and Contributors
*/

package com.urbanairship.datacube.dbharnesses;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.urbanairship.datacube.Address;
import com.urbanairship.datacube.Batch;
import com.urbanairship.datacube.BoxedByteArray;
import com.urbanairship.datacube.DbHarness;
import com.urbanairship.datacube.Deserializer;
import com.urbanairship.datacube.IdService;
import com.urbanairship.datacube.Op;

/**
* For testing, this is is a backing store for a cube that lives in memory. It saves us from
* calling a DB just to test the cube logic.
*/
public class MapDbHarness<T extends Op> implements DbHarness<T> {
    private final static Logger log = LoggerFactory.getLogger(MapDbHarness.class);

    private static final int casRetries = 10;
    private static final Future<?> nullFuture = new NullFuture();
   
    private final ConcurrentMap<BoxedByteArray,byte[]> map;
    private final Deserializer<T> deserializer;
    private final CommitType commitType;
    private final IdService idService;
   
    public MapDbHarness(ConcurrentMap<BoxedByteArray,byte[]> map, Deserializer<T> deserializer,
            CommitType commitType, IdService idService) {
        this.map = map;
        this.deserializer = deserializer;
        this.commitType = commitType;
        this.idService = idService;
        if(commitType != CommitType.OVERWRITE && commitType != CommitType.READ_COMBINE_CAS) {
            throw new IllegalArgumentException("MapDbHarness doesn't support commit type " +
                    commitType);
        }
    }
   
    /**
     * Actually synchronous and not asyncronous, which is allowed.
     */
    @SuppressWarnings("unchecked")
    @Override
    public Future<?> runBatchAsync(Batch<T> batch, AfterExecute<T> afterExecute) {
       
        for(Map.Entry<Address,T> entry: batch.getMap().entrySet()) {
            Address address = entry.getKey();
            T opFromBatch = entry.getValue();

            BoxedByteArray mapKey;
            try {
                mapKey = new BoxedByteArray(address.toKey(idService));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
           
            if(commitType == CommitType.READ_COMBINE_CAS) {
                int casRetriesRemaining = casRetries;
                do {
                    Optional<byte[]> oldBytes;
                    try {
                        oldBytes = getRaw(address);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                   
                    T newOp;
                    if(oldBytes.isPresent()) {
                        T oldOp = deserializer.fromBytes(oldBytes.get());
                        newOp = (T)opFromBatch.add(oldOp);
                        if(log.isDebugEnabled()) {
                            log.debug("Combined " + oldOp + " and " +  opFromBatch + " into " +
                                    newOp);
                        }
                    } else {
                        newOp = opFromBatch;
                    }
                   
                    if(oldBytes.isPresent()) {
                        // Compare and swap, if the key is still mapped to the same byte array.
                        if(map.replace(mapKey, oldBytes.get(), newOp.serialize())) {
                            if(log.isDebugEnabled()) {
                                log.debug("Successful CAS overwrite at key " +
                                        Hex.encodeHexString(mapKey.bytes+ " with " +
                                        Hex.encodeHexString(newOp.serialize()));
                            }
                            break;
                        }
                    } else {
                        // Compare and swap, if the key is still absent from the map.
                        if(map.putIfAbsent(mapKey, newOp.serialize()) == null) {
                            // null is returned when there was no existing mapping for the
                            // given key, which is the success case.
                            if(log.isDebugEnabled()) {
                                log.debug("Successful CAS insert without existing key for key " +
                                        Hex.encodeHexString(mapKey.bytes) + " with " +
                                        Hex.encodeHexString(newOp.serialize()));
                            }
                            break;
                        }
                    }
                } while (casRetriesRemaining-- > 0);
                if(casRetriesRemaining == -1) {
                    RuntimeException e = new RuntimeException("CAS retries exhausted");
                    afterExecute.afterExecute(e);
                    throw e;
                }
            } else if(commitType == CommitType.OVERWRITE) {
                map.put(mapKey, opFromBatch.serialize());
                if(log.isDebugEnabled()) {
                    log.debug("Write of key " + Hex.encodeHexString(mapKey.bytes));
                }
            } else {
                throw new AssertionError("Unsupported commit type: " + commitType);
            }
        }
        batch.reset();
        afterExecute.afterExecute(null); // null throwable => success
        return nullFuture;
    }

    @Override
    public Optional<T> get(Address address) throws IOException, InterruptedException {
        Optional<byte[]> bytes = getRaw(address);
        if(bytes.isPresent()) {
            return Optional.of(deserializer.fromBytes(bytes.get()));
        } else {
            return Optional.absent();
        }
    }
   
    @Override
    public void flush() throws InterruptedException {
        return; // all ops are synchronously applied, nothing to do
    }

    private Optional<byte[]> getRaw(Address address) throws InterruptedException {
        byte[] mapKey;
        try {
            mapKey = address.toKey(idService);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        byte[] bytes = map.get(new BoxedByteArray(mapKey));
        if(log.isDebugEnabled()) {
            log.debug("getRaw for key " + Hex.encodeHexString(mapKey) + " returned " +
                    Arrays.toString(bytes));
        }
        if(bytes == null) {
            return Optional.absent();
        } else {
            return Optional.of(bytes);
        }
    }

    @Override
    public List<Optional<T>> multiGet(List<Address> addresses) throws IOException {
        throw new NotImplementedException();
    }
   
    private static class NullFuture implements Future<Object> {
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return false;
        }

        @Override
        public Object get() throws InterruptedException, ExecutionException {
            return null;
        }

        @Override
        public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
                TimeoutException {
            return null;
        }

        @Override
        public boolean isCancelled() {
            return false;
        }

        @Override
        public boolean isDone() {
            return true;
        }
       
    }
}
TOP

Related Classes of com.urbanairship.datacube.dbharnesses.MapDbHarness$NullFuture

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.