package io.eguan.ibs;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import java.nio.ByteBuffer;
import java.util.Objects;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import com.google.protobuf.ByteString;
/**
* Implementation of {@link Ibs} based on LevelDB.
*
* @author oodrive
* @author llambert
* @author jmcaba
*
*/
final class IbsLevelDB extends IbsDBAbstract {
// Load native code
static {
NarSystem.loadLibrary();
}
/** Id of the IBS. For native access to the object */
private final int ibsId;
/**
* Creates a new instance. The IBS have been opened.
*
* @param ibsPath
* path of the IBS store
* @param ibsId
* IBS native index.
*/
IbsLevelDB(final String ibsPath, final int ibsId) {
super(ibsPath);
this.ibsId = ibsId;
}
@Override
protected final int doStart() {
return ibsStart(ibsId);
}
@Override
protected int doStop() {
return ibsStop(ibsId);
}
@Override
protected int doClose() {
// Release native instance
return ibsDelete(ibsId);
}
@Override
protected final int doDestroy() {
// Remove storage
int retval = ibsDestroy(ibsId);
if (retval == 0) {
retval = doClose();
}
return retval;
}
@Override
public final boolean isHotDataEnabled() throws IbsException {
final int retval = ibsIsHotDataEnabled(ibsId);
if (retval == 1) {
return true;
}
else if (retval == 0) {
return false;
}
else if (retval < 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
throw new IbsException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
else {
throw new AssertionError();
}
}
@Override
public final int get(@Nonnull final byte[] key, @Nonnull final ByteBuffer data, @Nonnegative final int offset,
@Nonnegative final int length) throws IbsException, IbsIOException, IbsBufferTooSmallException,
IllegalArgumentException, IndexOutOfBoundsException, NullPointerException {
// TODO shared access to the IBS state during the whole get?
if (!started || closed) {
throw new IbsException(toString());
}
checkArgs(key, data, offset, length);
final int retval;
// Call native get. Direct access for direct buffers only
if (data.isDirect()) {
retval = ibsGetDirect(ibsId, key, data, offset, length);
}
else {
retval = ibsGet(ibsId, key, data.array(), offset, length);
}
if (retval < 0) {
// Extract IBS error code
final int code = retval >> 24;
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(code);
if (ibsErrorCode == IbsErrorCode.BUFFER_TOO_SMALL) {
// Get record size
throw new IbsBufferTooSmallException(toString(), retval & 0x00FFFFFF);
}
else {
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
}
return retval;
}
@Override
public final void del(@Nonnull final byte[] key) throws IbsException, IbsIOException, NullPointerException {
// TODO shared access to the IBS state during the whole put?
if (!started || closed) {
throw new IbsException(toString());
}
// Check reference
Objects.requireNonNull(key);
final int retval = ibsDel(ibsId, key);
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
}
@Override
public final boolean put(@Nonnull final byte[] key, final ByteBuffer data) throws IbsException, IbsIOException,
NullPointerException {
// New put() of known data?
if (data == null) {
final int retval = ibsPutDirect(ibsId, 0, key, data, 0, 1);
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
if (ibsErrorCode == IbsErrorCode.KEY_ALREADY_ADDED) {
// No new addition in the database
return false;
}
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
// When data is not set, ibs is supposed to return KEY_ALREADY_ADDED or NOT_FOUND
throw new AssertionError();
}
// Real put()
final int writtenLength = data.remaining();
final boolean result = put(key, data, data.position(), writtenLength);
data.position(data.position() + writtenLength);
return result;
}
@Override
public final boolean put(@Nonnull final byte[] key, @Nonnull final ByteString data) throws IbsException,
IbsIOException, NullPointerException {
if (!started || closed) {
throw new IbsException(toString());
}
// Check references
Objects.requireNonNull(key);
Objects.requireNonNull(data);
// Call native put: read internal field from native code
final int retval = ibsPutByteStr(ibsId, key, data);
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
if (ibsErrorCode == IbsErrorCode.KEY_ALREADY_ADDED) {
// No new addition in the database
return false;
}
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
// New key/pair added
return true;
}
@Override
public final boolean replace(@Nonnull final byte[] oldKey, @Nonnull final byte[] newKey, final ByteBuffer data)
throws IbsException, IbsIOException {
// New replace() of known data?
if (data == null) {
final int retval = ibsReplaceDirect(ibsId, 0, oldKey, newKey, data, 0, 1);
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
if (ibsErrorCode == IbsErrorCode.KEY_ALREADY_ADDED) {
// No new addition in the database
return false;
}
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
// When data is not set, ibs is supposed
// to return KEY_ALREADY_ADDED or NOT_FOUND
throw new AssertionError();
}
// Real replace()
final int writtenLength = data.remaining();
final boolean result = replace(oldKey, newKey, data, data.position(), writtenLength);
data.position(data.position() + writtenLength);
return result;
}
@Override
public final int createTransaction() throws IbsException, IllegalArgumentException, IbsIOException {
if (!started || closed) {
throw new IbsException(toString());
}
final int retval = ibsCreateTransaction(ibsId);
if (retval < 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
return retval;
}
@Override
public final void commit(@Nonnegative final int txId) throws IbsException, IllegalArgumentException, IbsIOException {
if (!started || closed) {
throw new IbsException(toString());
}
if (txId <= 0) {
throw new IllegalArgumentException("txId=" + txId);
}
final int retval = ibsCommitTransaction(ibsId, txId);
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
}
@Override
public final void rollback(@Nonnegative final int txId) throws IbsException, IllegalArgumentException,
IbsIOException {
if (!started || closed) {
throw new IbsException(toString());
}
if (txId <= 0) {
throw new IllegalArgumentException("txId=" + txId);
}
final int retval = ibsRollbackTransaction(ibsId, txId);
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public final String toString() {
return "IBS[" + ibsPath + ", id=" + ibsId + ", started=" + started + ", closed=" + closed + "]";
}
@Override
protected final boolean doPut(final int txId, @Nonnull final byte[] key, @Nonnull final ByteBuffer data,
@Nonnegative final int offset, @Nonnegative final int length) throws IbsException, IbsIOException,
IllegalArgumentException, IndexOutOfBoundsException, NullPointerException {
// TODO shared access to the IBS state during the whole put?
if (!started || closed) {
throw new IbsException(toString());
}
checkArgs(key, data, offset, length);
final int retval;
// Call native put. Direct access for direct buffers only
if (data.isDirect()) {
retval = ibsPutDirect(ibsId, txId, key, data, offset, length);
}
else {
retval = ibsPut(ibsId, txId, key, data.array(), offset, length);
}
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
if (ibsErrorCode == IbsErrorCode.KEY_ALREADY_ADDED) {
// No new addition in the database
return false;
}
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
// New key/pair added
return true;
}
@Override
protected final boolean doReplace(final int txId, @Nonnull final byte[] oldKey, @Nonnull final byte[] newKey,
@Nonnull final ByteBuffer data, @Nonnegative final int offset, @Nonnegative final int length)
throws IbsException, IllegalArgumentException, IbsIOException, IndexOutOfBoundsException,
NullPointerException {
// TODO shared access to the IBS state during the whole replace?
if (!started || closed) {
throw new IbsException(toString());
}
// Check references
Objects.requireNonNull(oldKey);
checkArgs(newKey, data, offset, length);
final int retval;
// Fake impl
// Call native replace. Direct access for direct buffers only
if (data.isDirect()) {
retval = ibsReplaceDirect(ibsId, txId, oldKey, newKey, data, offset, length);
}
else {
retval = ibsReplace(ibsId, txId, oldKey, newKey, data.array(), offset, length);
}
if (retval != 0) {
final IbsErrorCode ibsErrorCode = IbsErrorCode.valueOf(retval);
if (ibsErrorCode == IbsErrorCode.KEY_ALREADY_ADDED) {
// No new addition in the database
return false;
}
throw new IbsIOException(toString() + ": " + ibsErrorCode, ibsErrorCode);
}
// New key/pair added
return true;
}
//
// Native Calls
//
/**
* Create a new IBS.
*
* @param path
* The configuration filename.
* @return The IBS instance id (>0) if successful else an ibsErrorCode
*/
static native int ibsCreate(final String path);
/**
* Init an existing IBS instance.
*
* @param path
* The configuration filename.
* @return The IBS instance id (>0) if successful else an ibsErrorCode
*/
static native int ibsInit(final String path);
/**
* Start an IBS instance.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @return 0 if success else an ibsErrorCode
*/
private static native int ibsStart(final int id);
/**
* Stop an IBS instance.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @return 0 if success else an ibsErrorCode
*/
private static native int ibsStop(final int id);
/**
* Delete an IBS instance (free memory, leave on-disk data).
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @return 0 if success else an ibsErrorCode
*/
private static native int ibsDelete(int id);
/**
* Destroy an IBS instance. Wipe all on-disk data, does not release native instance memory.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @return 0 if success else an ibsErrorCode
*/
private static native int ibsDestroy(final int id);
/**
* Getter for the hot data status.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @return <code>1</code> if hot data are activated, <code>0</code> for not enabled or an ibsErrorCode
*/
private static native int ibsIsHotDataEnabled(final int id);
/**
* Fetch a record an IBS instance. Loads data from a direct {@link ByteBuffer}.
*
* @see #ibsGet(int, byte[], byte[], int, int)
*/
private static native int ibsGetDirect(final int id, final byte[] key, final ByteBuffer data, final int offset,
final int lengthMax);
/**
* Fetch a record an IBS instance. Blocking and thread safe.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @param key
* The key associated to the record
* @param data
* Destination array. The record will be stored in <code>data</code>
* @param offset
* offset in data were the data will be written
* @param lengthMax
* data maximum length
* @return If positive, the length of data written inside <code>data</code>; if negative, the composition of the IBS
* error code (8 higher bits) and the length of the record (24 lower bits)
*/
private static native int ibsGet(final int id, final byte[] key, final byte[] data, final int offset,
final int lengthMax);
/**
* Delete a record an IBS instance. Blocking & thread safe.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @param key
* The key associated to the record
* @return 0 if success, an ibsErrorCode if something went wrong.
*/
private static native int ibsDel(final int id, final byte[] key);
/**
* Save a record associated to a key and store in a direct {@link ByteBuffer}.
*
* @see #ibsPut(int, int, byte[], byte[], int, int)
*/
private static native int ibsPutDirect(int id, int txId, byte[] key, ByteBuffer data, final int offset,
final int length);
/**
* Save a record associated to a key. Blocking and thread safe.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @param txId
* id of a transaction or <code>0</code>
* @param key
* The key associated to the record
* @param data
* The record that will be stored
* @param offset
* offset in data
* @param length
* data length
* @return 0 if success else an ibsErrorCode.
*/
private static native int ibsPut(int id, int txId, byte[] key, byte[] data, final int offset, final int length);
/**
* Save a record associated to a key. Blocking and thread safe.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @param key
* The key associated to the record
* @param data
* The record that will be stored
* @return 0 if success else an ibsErrorCode.
*/
private static native int ibsPutByteStr(int id, byte[] key, ByteString data);
/**
* Issue a replace request on a direct {@link ByteBuffer}.
*
* @see #ibsReplace(int, int, byte[], byte[], byte[], int, int)
*/
private static native int ibsReplaceDirect(int id, int txId, byte[] oldKey, byte[] newKey,
final ByteBuffer newData, final int offset, final int length);
/**
* Issue a replace request.
*
* A replace request is an enriched put request that warns the underlying storage that a record has been replaced by
* a new one. The main purpose of this request is to properly handle short-lived records.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @param txId
* id of a transaction or <code>0</code>
* @param oldKey
* The oldKey is the key associated to the the record that is being (on client side) overwritten by the
* new one. HighId and lowId must be use to ensure safe replacement. Raw buffer.
* @param newKey
* The new key associated to the new record. Raw buffer.
* @param data
* The record that will be store. Raw buffer.
* @return 0 if success else an ibsErrorCode.
*/
private static native int ibsReplace(int id, int txId, byte[] oldKey, byte[] newKey, byte[] newData,
final int offset, final int length);
/**
* Create a new {@link Ibs} transaction.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @return id of the transaction or an negative error code
*/
private static native int ibsCreateTransaction(int id);
/**
* Commits a transaction.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @param txId
* The transaction id returned by {@link #ibsCreateTransaction(int)}.
* @return 0 or an negative error code
*/
private static native int ibsCommitTransaction(int id, int txId);
/**
* Rolls back a transaction.
*
* @param id
* The IBS id returned by {@link #ibsInit(String)}.
* @param txId
* The transaction id returned by {@link #ibsCreateTransaction(int)}.
* @return 0 or an negative error code
*/
private static native int ibsRollbackTransaction(int id, int txId);
}