package io.eguan.vvr.repository.core.api; /* * #%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 io.eguan.hash.HashAlgorithm; import io.eguan.ibs.Ibs; import io.eguan.ibs.IbsErrorCode; import io.eguan.ibs.IbsException; import io.eguan.ibs.IbsIOException; import io.eguan.proto.vvr.VvrRemote; import io.eguan.proto.vvr.VvrRemote.RemoteOperation.Builder; import io.eguan.utils.ByteArrays; import io.eguan.vvr.repository.core.api.Device.ReadWriteHandle; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Objects; import javax.annotation.Nonnull; import com.google.protobuf.ByteString; /** * Implementation of a {@link ReadWriteHandle} writing directly blocks in the {@link Ibs}. * * @author oodrive * @author llambert * */ final class IbsDeviceReadWriteHandleImpl extends DeviceReadWriteHandleImpl { /** The {@link Ibs} on which to perform read/write operations. */ private final Ibs ibs; /** <code>true</code> if the hot data is enabled in {@link Ibs} */ private final boolean ibsHotDataEnabled; IbsDeviceReadWriteHandleImpl(final AbstractDeviceImplHelper deviceImplHelper, @Nonnull final Ibs ibs, final HashAlgorithm hashAlgorithm, final boolean readOnly, final int blockSize) { super(deviceImplHelper, hashAlgorithm, readOnly, blockSize); this.ibs = Objects.requireNonNull(ibs); this.ibsHotDataEnabled = ibs.isHotDataEnabled(); } @Override protected final int createBlockTransaction() throws IbsException, IllegalArgumentException, IbsIOException { return ibs.createTransaction(); } @Override protected final void commitBlockTransaction(final int txId) throws IbsException, IllegalArgumentException, IbsIOException { ibs.commit(txId); } @Override protected final void rollbackBlockTransaction(final int txId) throws IbsException, IllegalArgumentException, IbsIOException { ibs.rollback(txId); } @Override protected final boolean canReplaceOldKey() { return ibsHotDataEnabled; } @Override protected final boolean needsBlockOpBuilder() { return true; } @Override protected final void notifyBlockIO(@Nonnull final Builder blockOpBuilder) { if (blockOpBuilder.getIbsCount() > 0) { deviceImplHelper.notifyIO(blockOpBuilder); } } @Override protected final void storeNewBlock(final ByteBuffer block, final int offset, final long blockIndex, final byte[] newKey, final byte[] oldKey, final int ibsTxId, final VvrRemote.RemoteOperation.Builder opBuilder) throws IbsException, IllegalArgumentException, IndexOutOfBoundsException, NullPointerException, IOException { // Store in IBS: replace when possible final boolean newBlock; if (ibsTxId > 0) { if (oldKey == null) { newBlock = ibs.put(ibsTxId, newKey, block, offset, blockSize); } else { newBlock = ibs.replace(ibsTxId, oldKey, newKey, block, offset, blockSize); } } else { if (oldKey == null) { newBlock = ibs.put(newKey, block, offset, blockSize); } else { newBlock = ibs.replace(oldKey, newKey, block, offset, blockSize); } } // Fill IBS notification final VvrRemote.Ibs.Builder ibsBuilder = VvrRemote.Ibs.newBuilder(); ibsBuilder.setKey(ByteString.copyFrom(newKey)); if (newBlock) { // Need to set position and limit once again block.position(offset); block.limit(offset + blockSize); ibsBuilder.setValue(ByteString.copyFrom(block)); } if (oldKey != null) { ibsBuilder.setKeyOld(ByteString.copyFrom(oldKey)); } synchronized (opBuilder) { opBuilder.addIbs(ibsBuilder); } // Store key in persistence deviceImplHelper.writeBlockKey(blockIndex, newKey); } @Override protected final ByteBuffer getBlock(final long blockIndex, final byte[] key, final BlockKeyLookupEx blockKeyLookupEx, final boolean readOnly) throws IOException, InterruptedException { try { final ByteBuffer block = allocateBlock(false); try { ibs.get(key, block); } catch (IbsIOException | RuntimeException | Error e) { releaseBlock(block); throw e; } return block; } catch (final IbsIOException e) { if (e.getErrorCode() == IbsErrorCode.NOT_FOUND) { // Look for the block on remote nodes final ByteString byteString = deviceImplHelper.getRemoteBuffer(key, blockKeyLookupEx.getSourceNode()); if (byteString != null) { // Block found final ByteBuffer result; if (readOnly) { result = byteString.asReadOnlyByteBuffer(); assert result.capacity() == blockSize; } else { result = allocateBlock(false); byteString.copyTo(result); } return result; } } throw e; } } @Override protected final void fillBlock(final long blockIndex, final byte[] key, final ByteBuffer data, final int dataOffset, final BlockKeyLookupEx blockKeyLookupEx) throws IOException, InterruptedException { int readLen = 0; try { readLen = ibs.get(key, data, dataOffset, blockSize); } catch (final IbsIOException e) { if (e.getErrorCode() == IbsErrorCode.NOT_FOUND) { // Look for the block on remote nodes final ByteString byteString = deviceImplHelper.getRemoteBuffer(key, blockKeyLookupEx.getSourceNode()); if (byteString != null) { // Found: copy block to data data.rewind().position(dataOffset); byteString.copyTo(data); readLen = data.position() - dataOffset; } } // Block found? if (readLen == 0) { throw e; } } if (readLen != blockSize) { throw new IOException("Failed to read key=0x" + ByteArrays.toHex(key) + ", blockSize=" + blockSize + ", readlen=" + readLen); } } }