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.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import com.google.protobuf.ByteString;
/**
* Fake {@link Ibs}. In memory implementation, so limited to test. To be able to create/close/open/close a
* {@link IbsFake}, all the fake Ibs shares the same map of key/value.
*
* @author oodrive
* @author llambert
* @author jmcaba
*
*/
final class IbsFake extends IbsAbstract {
private static final class FakeItem {
private final AtomicInteger count;
private final ByteBuffer buffer;
FakeItem(final ByteBuffer buffer) {
super();
this.buffer = Objects.requireNonNull(buffer);
this.count = new AtomicInteger(1);
}
protected final int incrCount() {
return count.incrementAndGet();
}
protected final int decrCount() {
return count.decrementAndGet();
}
protected final ByteBuffer getBuffer() {
return buffer;
}
}
@GuardedBy(value = "fakeIbsList")
private static final Map<String, Map<byte[], FakeItem>> fakeIbsList = new HashMap<>();
@GuardedBy(value = "fakeIbsStore")
private final Map<byte[], FakeItem> fakeIbsStore;
private final IbsMemTransaction ibsMemTransaction;
// Test error management
private final AtomicInteger ioCountPut;
private final int ioCountLimitPut;
private final AtomicInteger ioCountGet;
private final int ioCountLimitGet;
private static Comparator<byte[]> fakeIbsComparator = new Comparator<byte[]>() {
@Override
public final int compare(final byte[] o1, final byte[] o2) {
final int l1 = o1.length;
final int l2 = o2.length;
if (l1 != l2) {
return l1 - l2;
}
for (int i = 0; i < l1; i++) {
final byte b1 = o1[i];
final byte b2 = o2[i];
if (b1 != b2) {
return b1 - b2;
}
}
return 0;
}
};
private IbsFake(final String ibsPath, @Nonnull final Map<byte[], FakeItem> fakeIbsStore) {
super(ibsPath);
this.fakeIbsStore = Objects.requireNonNull(fakeIbsStore);
if (ibsPath.startsWith(Ibs.UNIT_TEST_IBS_HEADER)) {
final int lengthHeader = Ibs.UNIT_TEST_IBS_HEADER.length();
final int indexSep = ibsPath.indexOf(':', lengthHeader);
ioCountPut = new AtomicInteger();
if (indexSep > 0) {
ioCountLimitPut = Integer.valueOf(ibsPath.substring(lengthHeader, indexSep));
ioCountGet = new AtomicInteger();
ioCountLimitGet = Integer.valueOf(ibsPath.substring(indexSep + 1));
}
else {
ioCountLimitPut = Integer.valueOf(ibsPath.substring(lengthHeader));
ioCountGet = null;
ioCountLimitGet = 0;
}
}
else {
ioCountPut = null;
ioCountLimitPut = 0;
ioCountGet = null;
ioCountLimitGet = 0;
}
ibsMemTransaction = new IbsMemTransaction(this);
}
static final Ibs createIbs(final String ibsPath) throws IbsException {
synchronized (fakeIbsList) {
if (fakeIbsList.containsKey(ibsPath)) {
throw new IbsException(ibsPath, IbsErrorCode.CREATE_IN_NON_EMPTY_DIR);
}
// Create new map
final Map<byte[], FakeItem> fakeIbsStore = new TreeMap<>(fakeIbsComparator);
fakeIbsList.put(ibsPath, fakeIbsStore);
return new IbsFake(ibsPath, fakeIbsStore);
}
}
// TODO lock fakeIbsStore when opened to avoid concurrent open
static final Ibs openIbs(final String ibsPath) throws IbsException {
synchronized (fakeIbsList) {
// Find map
final Map<byte[], FakeItem> fakeIbsStore = fakeIbsList.get(ibsPath);
if (fakeIbsStore == null) {
throw new IbsException(ibsPath, IbsErrorCode.INIT_FROM_EMPTY_DIR);
}
return new IbsFake(ibsPath, fakeIbsStore);
}
}
@Override
protected final int doStart() {
return 0;
}
@Override
protected final int doStop() {
ibsMemTransaction.clear();
return 0;
}
@Override
protected final int doClose() {
return 0;
}
@Override
protected final int doDestroy() {
synchronized (fakeIbsList) {
fakeIbsList.remove(ibsPath);
}
return 0;
}
@Override
public final boolean isHotDataEnabled() throws IbsException {
return true;
}
@Override
public final int get(final byte[] key, final ByteBuffer data, final int offset, final int length)
throws IbsException, IbsIOException, IbsBufferTooSmallException, IllegalArgumentException,
IndexOutOfBoundsException, NullPointerException {
if (!started || closed) {
throw new IbsException(toString());
}
checkArgs(key, data, offset, length);
final int prevPosition = data.position();
final int readLen = getFake(key, data, offset, length, true);
// Reset previous position
data.position(prevPosition);
return readLen;
}
@Override
public final void del(final byte[] key) throws IbsException, IbsIOException, NullPointerException {
if (!started || closed) {
throw new IbsException(toString());
}
// Check reference
Objects.requireNonNull(key);
synchronized (fakeIbsStore) {
fakeIbsStore.remove(key);
}
}
@Override
public final boolean put(final byte[] key, final ByteBuffer data) throws IbsException, IbsIOException,
NullPointerException {
if (data == null) {
return replaceFake(null, key, null, 0, 0, true);
}
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(final byte[] key, final ByteBuffer data, final int offset, final int length)
throws IbsException, IbsIOException, IllegalArgumentException, IndexOutOfBoundsException,
NullPointerException {
if (!started || closed) {
throw new IbsException(toString());
}
checkArgs(key, data, offset, length);
return replaceFake(null, key, data, offset, length, true);
}
@Override
public final boolean put(final byte[] key, final ByteString data) throws IbsException, IbsIOException,
NullPointerException {
final ByteBuffer dataBB = data.asReadOnlyByteBuffer();
return put(key, dataBB);
}
@Override
public final boolean replace(final byte[] oldKey, final byte[] newKey, final ByteBuffer data) throws IbsException,
IbsIOException {
if (data == null) {
return replaceFake(null, newKey, null, 0, 0, true);
}
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 boolean replace(final byte[] oldKey, final byte[] newKey, final ByteBuffer data, final int offset,
final int length) throws IbsException, IllegalArgumentException, IbsIOException, IndexOutOfBoundsException,
NullPointerException {
// TODO shared access to the IBS state during the whole put?
if (!started || closed) {
throw new IbsException(toString());
}
Objects.requireNonNull(oldKey);
checkArgs(newKey, data, offset, length);
return replaceFake(oldKey, newKey, data, offset, length, true);
}
@Override
public final int createTransaction() {
if (!started || closed) {
throw new IbsException(toString());
}
return ibsMemTransaction.createTransaction();
}
@Override
public final boolean put(final int txId, final byte[] key, final ByteBuffer data, final int offset, final int length)
throws IbsException, IbsIOException, IllegalArgumentException, IndexOutOfBoundsException,
NullPointerException {
if (txId <= 0) {
if (txId == Integer.MIN_VALUE) {
replaceFake(null, key, data, offset, length, false);
return false;
}
else {
throw new IllegalArgumentException("txId=" + txId);
}
}
checkArgs(key, data, offset, length);
final boolean newKeyToAdd;
synchronized (fakeIbsStore) {
newKeyToAdd = fakeIbsStore.get(key) == null;
}
// Count operations
if (ioCountPut != null) {
final int currentCount = ioCountPut.incrementAndGet();
if ((currentCount % ioCountLimitPut) == 0) {
throw new IbsIOException(IbsErrorCode.UNKNOW_ERROR);
}
}
// Add new operation
ibsMemTransaction.put(txId, key, data, offset, length);
return newKeyToAdd;
}
@Override
public final boolean replace(final int txId, final byte[] oldKey, final byte[] newKey, final ByteBuffer data,
final int offset, final int length) throws IbsException, IllegalArgumentException, IbsIOException,
IndexOutOfBoundsException, NullPointerException {
if (txId <= 0) {
if (txId == Integer.MIN_VALUE) {
replaceFake(oldKey, newKey, data, offset, length, false);
return false;
}
else {
throw new IllegalArgumentException("txId=" + txId);
}
}
Objects.requireNonNull(oldKey);
checkArgs(newKey, data, offset, length);
final boolean newKeyToAdd;
synchronized (fakeIbsStore) {
newKeyToAdd = fakeIbsStore.get(newKey) == null;
}
// Count operations
if (ioCountPut != null) {
final int currentCount = ioCountPut.incrementAndGet();
if ((currentCount % ioCountLimitPut) == 0) {
throw new IbsIOException(IbsErrorCode.UNKNOW_ERROR);
}
}
// Add new operation
ibsMemTransaction.replace(txId, oldKey, newKey, data, offset, length);
return newKeyToAdd;
}
@Override
public final void commit(final int txId) throws IbsException, IllegalArgumentException, IbsIOException {
if (!started || closed) {
throw new IbsException(toString());
}
if (txId <= 0) {
throw new IllegalArgumentException("txId=" + txId);
}
ibsMemTransaction.commit(txId);
}
@Override
public final void rollback(final int txId) throws IbsException, IllegalArgumentException, IbsIOException {
if (!started || closed) {
throw new IbsException(toString());
}
if (txId <= 0) {
throw new IllegalArgumentException("txId=" + txId);
}
ibsMemTransaction.rollback(txId);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public final String toString() {
return "IBS[" + ibsPath + ", started=" + started + ", closed=" + closed + "]";
}
/**
* Puts a value in the fake IBS.
*
* @param key
* @param data
* @param offset
* @param length
* @throws IbsIOException
*/
private final boolean replaceFake(final byte[] oldKey, final byte[] key, final ByteBuffer data, final int offset,
final int length, final boolean count) throws IbsIOException {
// Reached limit to send error
if (count && ioCountPut != null) {
final int currentCount = ioCountPut.incrementAndGet();
if ((currentCount % ioCountLimitPut) == 0) {
throw new IbsIOException(IbsErrorCode.UNKNOW_ERROR);
}
}
final ByteBuffer val;
if (data == null) {
val = null;
}
else {
if (data.isDirect())
val = ByteBuffer.allocateDirect(length);
else
val = ByteBuffer.allocate(length);
final ByteBuffer src = data.duplicate();
src.position(offset).limit(offset + length);
val.put(src);
}
synchronized (fakeIbsStore) {
// REPLACE: remove oldKey if not referenced
if (oldKey != null) {
final FakeItem fakeItem = fakeIbsStore.get(oldKey);
if (fakeItem != null) {
if (fakeItem.decrCount() <= 0) {
fakeIbsStore.remove(oldKey);
// logger.warn("REM " + oldKey[0] + oldKey[1] + oldKey[2] + oldKey[3] + oldKey[4] + oldKey[5]);
}
}
}
// PUT
final FakeItem fakeItem = fakeIbsStore.get(key);
if (fakeItem != null) {
// Keep current value
fakeItem.incrCount();
// logger.warn("PUT " + key[0] + key[1] + key[2] + key[3] + key[4] + key[5] + " incr");
return false;
}
else if (val == null) {
// Key not found
throw new IbsIOException(IbsErrorCode.NOT_FOUND);
}
if (fakeIbsStore.put(key, new FakeItem(val)) != null) {
throw new AssertionError();
}
// logger.warn("PUT " + key[0] + key[1] + key[2] + key[3] + key[4] + key[5] + " new");
return true;
}
}
/**
* Gets a value from the fake IBS.
*
* @param key
* @param data
* @param offset
* @param length
* @throws IbsIOException
*/
private final int getFake(final byte[] key, final ByteBuffer data, final int offset, final int length,
final boolean count) throws IbsIOException {
// Reached limit to send error
if (count && ioCountGet != null) {
final int currentCount = ioCountGet.incrementAndGet();
if ((currentCount % ioCountLimitGet) == 0) {
throw new IbsIOException(IbsErrorCode.UNKNOW_ERROR);
}
}
// logger.warn("GET " + key[0] + key[1] + key[2] + key[3] + key[4] + key[5]);
final FakeItem fakeItem;
synchronized (fakeIbsStore) {
fakeItem = fakeIbsStore.get(key);
}
if (fakeItem == null) {
throw new IbsIOException(toString(), IbsErrorCode.NOT_FOUND);
}
final ByteBuffer val = (ByteBuffer) fakeItem.getBuffer().rewind();
if (val.capacity() > (data.capacity() - offset)) {
throw new IbsBufferTooSmallException(val.capacity());
}
data.position(offset);
data.put(val);
return data.position() - offset;
}
}