/**
* Copyright 2016 Yahoo Inc.
*
* 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.
*/
package org.apache.bookkeeper.client;
import java.security.GeneralSecurityException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Queue;
import java.util.concurrent.RejectedExecutionException;
import org.apache.bookkeeper.client.AsyncCallback.AddCallback;
import org.apache.bookkeeper.client.AsyncCallback.CloseCallback;
import org.apache.bookkeeper.client.AsyncCallback.ReadCallback;
import org.apache.bookkeeper.client.BookKeeper.DigestType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class MockLedgerHandle extends LedgerHandle {
final ArrayList<LedgerEntry> entries = Lists.newArrayList();
final MockBookKeeper bk;
final long id;
final DigestType digest;
final byte[] passwd;
long lastEntry = -1;
boolean fenced = false;
MockLedgerHandle(MockBookKeeper bk, long id, DigestType digest, byte[] passwd) throws GeneralSecurityException {
super(bk, id, new LedgerMetadata(3, 3, 2, DigestType.MAC, "".getBytes()), DigestType.MAC, "".getBytes());
this.bk = bk;
this.id = id;
this.digest = digest;
this.passwd = Arrays.copyOf(passwd, passwd.length);
}
@Override
public void asyncClose(CloseCallback cb, Object ctx) {
if (bk.getProgrammedFailStatus()) {
cb.closeComplete(bk.failReturnCode, this, ctx);
return;
}
fenced = true;
try {
bk.executor.execute(() -> cb.closeComplete(0, this, ctx));
} catch (RejectedExecutionException e) {
cb.closeComplete(0, this, ctx);
}
}
@Override
public void asyncReadEntries(final long firstEntry, final long lastEntry, final ReadCallback cb, final Object ctx) {
if (bk.isStopped()) {
cb.readComplete(-1, MockLedgerHandle.this, null, ctx);
return;
}
bk.executor.execute(new Runnable() {
public void run() {
if (bk.getProgrammedFailStatus()) {
cb.readComplete(bk.failReturnCode, MockLedgerHandle.this, null, ctx);
return;
} else if (bk.isStopped()) {
log.debug("Bookkeeper is closed!");
cb.readComplete(-1, MockLedgerHandle.this, null, ctx);
return;
}
log.debug("readEntries: first={} last={} total={}", firstEntry, lastEntry, entries.size());
final Queue<LedgerEntry> seq = new ArrayDeque<LedgerEntry>();
long entryId = firstEntry;
while (entryId <= lastEntry && entryId < entries.size()) {
seq.add(entries.get((int) entryId++));
}
log.debug("Entries read: {}", seq);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
cb.readComplete(0, MockLedgerHandle.this, new Enumeration<LedgerEntry>() {
public boolean hasMoreElements() {
return !seq.isEmpty();
}
public LedgerEntry nextElement() {
return seq.remove();
}
}, ctx);
}
});
}
@Override
public void asyncReadLastEntry(final ReadCallback cb, final Object ctx) {
long lastEntryId = getLastAddConfirmed();
asyncReadEntries(lastEntryId, lastEntryId, cb, ctx);
}
@Override
public long addEntry(byte[] data) throws InterruptedException, BKException {
try {
bk.checkProgrammedFail();
} catch (BKException e) {
fenced = true;
throw e;
}
if (fenced) {
throw BKException.create(BKException.Code.LedgerFencedException);
}
if (bk.isStopped()) {
throw BKException.create(BKException.Code.NoBookieAvailableException);
}
lastEntry = entries.size();
entries.add(new MockLedgerEntry(ledgerId, lastEntry, data));
return lastEntry;
}
@Override
public void asyncAddEntry(final byte[] data, final AddCallback cb, final Object ctx) {
asyncAddEntry(data, 0, data.length, cb, ctx);
}
@Override
public void asyncAddEntry(final byte[] data, final int offset, final int length, final AddCallback cb,
final Object ctx) {
asyncAddEntry(Unpooled.wrappedBuffer(data, offset, length), cb, ctx);
}
@Override
public void asyncAddEntry(final ByteBuf data, final AddCallback cb, final Object ctx) {
if (bk.isStopped()) {
cb.addComplete(-1, MockLedgerHandle.this, INVALID_ENTRY_ID, ctx);
return;
}
data.retain();
bk.executor.execute(new Runnable() {
public void run() {
if (bk.getProgrammedFailStatus()) {
fenced = true;
data.release();
cb.addComplete(bk.failReturnCode, MockLedgerHandle.this, INVALID_ENTRY_ID, ctx);
return;
}
if (bk.isStopped()) {
data.release();
cb.addComplete(-1, MockLedgerHandle.this, INVALID_ENTRY_ID, ctx);
return;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
if (fenced) {
data.release();
cb.addComplete(BKException.Code.LedgerFencedException, MockLedgerHandle.this,
LedgerHandle.INVALID_ENTRY_ID, ctx);
} else {
lastEntry = entries.size();
byte[] storedData = new byte[data.readableBytes()];
data.readBytes(storedData);
LedgerEntry entry = new MockLedgerEntry(ledgerId, lastEntry, storedData);
entries.add(entry);
data.release();
cb.addComplete(0, MockLedgerHandle.this, lastEntry, ctx);
}
}
});
}
@Override
public long getId() {
return ledgerId;
}
@Override
public long getLastAddConfirmed() {
return lastEntry;
}
@Override
public long getLength() {
long length = 0;
for (LedgerEntry entry : entries) {
length += entry.getLength();
}
return length;
}
private static final Logger log = LoggerFactory.getLogger(MockLedgerHandle.class);
}