/** * Copyright 2016 LinkedIn Corp. All rights reserved. * * 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. */ package com.github.ambry.store; import com.github.ambry.utils.Pair; import java.io.Closeable; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A read option class that maintains the offset and size */ class BlobReadOptions implements Comparable<BlobReadOptions>, Closeable { private final LogSegment segment; private final Pair<File, FileChannel> segmentView; private final Offset offset; private final Long size; private final boolean isDeleted; private final Long expiresAtMs; private final Long crc; private final StoreKey storeKey; private final AtomicBoolean open = new AtomicBoolean(true); private final Logger logger = LoggerFactory.getLogger(getClass()); static final short VERSION_0 = 0; static final short VERSION_1 = 1; private static final short VERSION_LENGTH = 2; private static final short SIZE_LENGTH = 8; private static final short EXPIRES_AT_MS_LENGTH = 8; BlobReadOptions(Log log, Offset offset, long size, long expiresAtMs, StoreKey storeKey) { this(log, offset, size, expiresAtMs, storeKey, false, null); } BlobReadOptions(Log log, Offset offset, long size, long expiresAtMs, StoreKey storeKey, boolean isDeleted, Long crc) { segment = log.getSegment(offset.getName()); if (offset.getOffset() + size > segment.getEndOffset()) { throw new IllegalArgumentException( "Invalid offset [" + offset + "] and size [" + size + "]. Segment end offset: " + "[" + segment.getEndOffset() + "]"); } segmentView = segment.getView(); this.offset = offset; this.size = size; this.expiresAtMs = expiresAtMs; this.storeKey = storeKey; this.isDeleted = isDeleted; this.crc = crc; logger.trace("BlobReadOption offset {} size {} expiresAtMs {} storeKey {}", offset, size, expiresAtMs, storeKey, isDeleted, crc); } String getLogSegmentName() { return offset.getName(); } long getOffset() { return offset.getOffset(); } long getSize() { return size; } long getExpiresAtMs() { return expiresAtMs; } StoreKey getStoreKey() { return storeKey; } MessageInfo getMessageInfo() { return new MessageInfo(storeKey, size, isDeleted, expiresAtMs, crc); } File getFile() { return segmentView.getFirst(); } FileChannel getChannel() { return segmentView.getSecond(); } @Override public int compareTo(BlobReadOptions o) { return offset.compareTo(o.offset); } byte[] toBytes() { byte[] offsetBytes = offset.toBytes(); byte[] buf = new byte[VERSION_LENGTH + offsetBytes.length + SIZE_LENGTH + EXPIRES_AT_MS_LENGTH + storeKey.sizeInBytes()]; ByteBuffer bufWrap = ByteBuffer.wrap(buf); bufWrap.putShort(VERSION_1); bufWrap.put(offsetBytes); bufWrap.putLong(size); bufWrap.putLong(expiresAtMs); bufWrap.put(storeKey.toBytes()); return buf; } static BlobReadOptions fromBytes(DataInputStream stream, StoreKeyFactory factory, Log log) throws IOException { short version = stream.readShort(); switch (version) { case VERSION_0: // backwards compatibility Offset offset = new Offset(log.getFirstSegment().getName(), stream.readLong()); long size = stream.readLong(); long expiresAtMs = stream.readLong(); StoreKey key = factory.getStoreKey(stream); return new BlobReadOptions(log, offset, size, expiresAtMs, key); case VERSION_1: offset = Offset.fromBytes(stream); size = stream.readLong(); expiresAtMs = stream.readLong(); key = factory.getStoreKey(stream); return new BlobReadOptions(log, offset, size, expiresAtMs, key); default: throw new IOException("Unknown version encountered for BlobReadOptions"); } } @Override public void close() { if (open.compareAndSet(true, false)) { segment.closeView(); } } } /** * An implementation of MessageReadSet that maintains a list of * offsets from the underlying file channel */ class StoreMessageReadSet implements MessageReadSet { private final List<BlobReadOptions> readOptions; private final Logger logger = LoggerFactory.getLogger(getClass()); StoreMessageReadSet(List<BlobReadOptions> readOptions) { Collections.sort(readOptions); this.readOptions = readOptions; } @Override public long writeTo(int index, WritableByteChannel channel, long relativeOffset, long maxSize) throws IOException { if (index >= readOptions.size()) { throw new IndexOutOfBoundsException("index " + index + " out of the messageset size " + readOptions.size()); } BlobReadOptions options = readOptions.get(index); long startOffset = options.getOffset() + relativeOffset; long sizeToRead = Math.min(maxSize, options.getSize() - relativeOffset); logger.trace("Blob Message Read Set position {} count {}", startOffset, sizeToRead); long written = options.getChannel().transferTo(startOffset, sizeToRead, channel); logger.trace("Written {} bytes to the write channel from the file channel : {}", written, options.getFile()); return written; } @Override public int count() { return readOptions.size(); } @Override public long sizeInBytes(int index) { if (index >= readOptions.size()) { throw new IndexOutOfBoundsException("index [" + index + "] out of the messageset"); } return readOptions.get(index).getSize(); } @Override public StoreKey getKeyAt(int index) { if (index >= readOptions.size()) { throw new IndexOutOfBoundsException("index [" + index + "] out of the messageset"); } return readOptions.get(index).getStoreKey(); } }