package com.rubiconproject.oss.kv.backends; import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.rubiconproject.oss.kv.KeyValueStore; import com.rubiconproject.oss.kv.KeyValueStoreException; import com.rubiconproject.oss.kv.transcoder.Transcoder; import com.rubiconproject.oss.kv.util.DaemonThreadFactory; import com.rubiconproject.oss.kv.util.ExecutorUtils; public class ReplicatingKeyValueStore extends ReadLoadBalancingKeyValueStore { public static final String IDENTIFIER = "replicating"; private Log log = LogFactory.getLog(getClass()); private ExecutorService executor; private int threadPoolSize = 1; private boolean iOwnThreadPool = true; public ReplicatingKeyValueStore() { super(); } public ReplicatingKeyValueStore(KeyValueStore master) { super(master); } public ReplicatingKeyValueStore(KeyValueStore master, List<KeyValueStore> replicas) { super(master, replicas); this.threadPoolSize = replicas.size(); } public ReplicatingKeyValueStore(KeyValueStore master, List<KeyValueStore> replicas, int threadPoolSize) { super(master, replicas); this.threadPoolSize = threadPoolSize; } public ReplicatingKeyValueStore(KeyValueStore master, List<KeyValueStore> replicas, ExecutorService executor) { super(master, replicas); this.executor = executor; } public void setExecutorService(ExecutorService executor) { this.executor = executor; } public String getIdentifier() { return IDENTIFIER; } public void addReplica(KeyValueStore store) { super.addReader(store); } public void removeReplica(KeyValueStore store) { super.removeReader(store); } public void start() throws IOException { if (executor == null) { executor = ExecutorUtils.newFixedSizeDaemonThreadPool(threadPoolSize); iOwnThreadPool = true; } else iOwnThreadPool = false; super.start(); } public void stop() { if (iOwnThreadPool) { ExecutorUtils.shutdown(executor, TimeUnit.SECONDS, 2l, TimeUnit.SECONDS, 2); executor = null; } super.stop(); } public void set(String key, Object value) throws KeyValueStoreException, IOException { super.set(key, value); replicateWrite(key, value, null); } public void set(String key, Object value, Transcoder transcoder) throws KeyValueStoreException, IOException { super.set(key, value, transcoder); replicateWrite(key, value, transcoder); } public void delete(String key) throws KeyValueStoreException, IOException { super.delete(key); replicateDelete(key); } private void replicateWrite(String key, Object value, Transcoder transcoder) { for (KeyValueStore replica : readers) { WriteReplicaRunnable runner = new WriteReplicaRunnable(replica, key, value, transcoder); executor.execute(runner); } } private void replicateDelete(String key) { for (KeyValueStore replica : readers) { DeleteReplicaRunnable runner = new DeleteReplicaRunnable(replica, key); executor.execute(runner); } } private static class WriteReplicaRunnable implements Runnable { protected static Log log = LogFactory .getLog(WriteReplicaRunnable.class); protected KeyValueStore store; protected String key; protected Object value; protected Transcoder transcoder; public WriteReplicaRunnable(KeyValueStore store, String key, Object value, Transcoder transcoder) { this.store = store; this.key = key; this.value = value; this.transcoder = transcoder; } public void run() { try { if (transcoder == null) store.set(key, value); else store.set(key, value, transcoder); } catch (KeyValueStoreException e) { log.error("Cannot write to replica thread", e); } catch (IOException e) { log.error("Cannot write to replica thread", e); } finally { } } } private static class DeleteReplicaRunnable extends WriteReplicaRunnable { public DeleteReplicaRunnable(KeyValueStore store, String key) { super(store, key, null, null); } public void run() { try { store.delete(key); } catch (KeyValueStoreException e) { log.error("Cannot write to replica thread", e); } catch (IOException e) { log.error("Cannot write to replica thread", e); } finally { } } } }