All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.kv.fdb.FoundationKVDatabase Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.kv.fdb;

import com.apple.foundationdb.Database;
import com.apple.foundationdb.FDB;
import com.apple.foundationdb.FDBException;
import com.apple.foundationdb.NetworkOptions;
import com.google.common.base.Preconditions;

import io.permazen.kv.KVDatabase;
import io.permazen.kv.KVDatabaseException;
import io.permazen.util.ByteReader;
import io.permazen.util.ByteUtil;
import io.permazen.util.ByteWriter;

import java.util.Map;
import java.util.concurrent.Executor;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * FoundationDB {@link KVDatabase} implementation.
 *
 * 

* Allows specifying a {@linkplain #setKeyPrefix key prefix} for all keys, allowing multiple independent databases. * {@linkplain FoundationKVTransaction#watchKey Key watches} are supported. */ public class FoundationKVDatabase implements KVDatabase { /** * The API version used by this class. */ public static final int API_VERSION = 510; private final FDB fdb = FDB.selectAPIVersion(API_VERSION); private final NetworkOptions options = this.fdb.options(); private String clusterFilePath; private byte[] keyPrefix; private Executor executor; private Database database; private boolean started; // FDB can only be started up once /** * Constructor. * * @throws FDBException if {@link #API_VERSION} is not supported */ public FoundationKVDatabase() { } /** * Get the {@link NetworkOptions} associated with this instance. * Options must be configured prior to {@link #start}. * * @return network options */ public NetworkOptions getNetworkOptions() { return this.options; } /** * Configure the {@link Executor} used for the FoundationDB networking event loop. * *

* By default, the default thread pool is used to execute the FoundationDB network. * * @param executor executor for networking activity * @see FDB#startNetwork(Executor) FDB.startNetwork() */ public synchronized void setExecutor(Executor executor) { this.executor = executor; } /** * Configure the cluster file path. Default is null, which results in the default fdb.cluster file being used. * * @param clusterFilePath cluster file pathname */ public synchronized void setClusterFilePath(String clusterFilePath) { this.clusterFilePath = clusterFilePath; } /** * Get the key prefix for all keys. * * @return key prefix, or null if there is none configured */ public synchronized byte[] getKeyPrefix() { return this.keyPrefix.clone(); } /** * Configure a prefix for all keys. The prefix will be added/removed automatically with all access. * The default prefix is null, which is equivalent to an empty prefix. * *

* The key prefix may not be changed after this instance has {@linkplain #start started}. * * @param keyPrefix new prefix, or null for none * @throws IllegalArgumentException if {@code keyPrefix} starts with {@code 0xff} * @throws IllegalStateException if this instance has already been {@linkplain #start started} */ public synchronized void setKeyPrefix(byte[] keyPrefix) { Preconditions.checkState(this.database == null, "already started"); Preconditions.checkArgument(keyPrefix == null || keyPrefix.length == 0 || keyPrefix[0] != (byte)0xff, "prefix starts with 0xff"); this.keyPrefix = keyPrefix != null && keyPrefix.length > 0 ? keyPrefix.clone() : null; } /** * Get the underlying {@link Database} associated with this instance. * * @return the associated {@link Database} * @throws IllegalStateException if this instance has not yet been {@linkplain #start started} */ public synchronized Database getDatabase() { Preconditions.checkState(this.database != null, "not started"); return this.database; } // KVDatabase @Override @PostConstruct public synchronized void start() { if (this.database != null) return; if (this.started) throw new UnsupportedOperationException("restarts not supported"); this.database = this.fdb.open(this.clusterFilePath); if (this.executor != null) this.fdb.startNetwork(this.executor); else this.fdb.startNetwork(); this.started = true; } @Override @PreDestroy public synchronized void stop() { if (this.database == null) return; this.fdb.stopNetwork(); this.database = null; } @Override public FoundationKVTransaction createTransaction() { return this.createTransaction(null); } @Override public synchronized FoundationKVTransaction createTransaction(Map options) { Preconditions.checkState(this.database != null, "not started"); try { return new FoundationKVTransaction(this, this.database.createTransaction(), this.keyPrefix); } catch (FDBException e) { throw new KVDatabaseException(this, e); } } // Counters /** * Encode a 64 bit counter value. * * @param value counter value * @return encoded value */ public static byte[] encodeCounter(long value) { final ByteWriter writer = new ByteWriter(8); ByteUtil.writeLong(writer, value); final byte[] bytes = writer.getBytes(); FoundationKVDatabase.reverse(bytes); return bytes; } /** * Decode a 64 bit counter value. * * @param bytes encoded value * @return value counter value * @throws NullPointerException if {@code bytes} is null * @throws IllegalArgumentException if {@code bytes} is invalid */ public static long decodeCounter(byte[] bytes) { Preconditions.checkArgument(bytes.length == 8, "invalid encoded counter value length != 8"); bytes = bytes.clone(); FoundationKVDatabase.reverse(bytes); return ByteUtil.readLong(new ByteReader(bytes)); } private static void reverse(byte[] bytes) { int i = 0; int j = bytes.length - 1; while (i < j) { final byte temp = bytes[i]; bytes[i] = bytes[j]; bytes[j] = temp; i++; j--; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy