All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.nio.spi.s3.S3DirectReadableByteChannel Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package software.amazon.nio.spi.s3;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Objects;

/**
 * A delegate to provide byte reading from S3 to the delegating {@code S3SeekableByteChannel}. As reads are made
 * on behalf of the {@code delegator} this class will update the delegators {@code position} appropriately.
 */
public class S3DirectReadableByteChannel implements ReadableByteChannel {

    private final S3Client client;
    private final S3Path path;
    private final S3SeekableByteChannel delegator;


    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public S3DirectReadableByteChannel(S3Path path, S3Client client, S3SeekableByteChannel delegator) {
        this.path = path;
        this.client = client;
        this.delegator = delegator;
    }

    @Override
    public int read(ByteBuffer dst) throws IOException {
        String key = path.getKey();
        Objects.requireNonNull(dst, "The destination byte buffer of a read operation may not be null");
        synchronized (this) {
            logger.debug("channel position: {} of {}", delegator.position(), key);

            long size = delegator.size();
            logger.debug("{} size: {}", key, size);

            if (delegator.position() >= size) {
                logger.debug("completed reading from {}", key);
                return -1;
            }

            /*
             * Suppose that a byte sequence of length n is read, where
             * 0 <= n <= r.
             * This byte sequence will be transferred into the buffer of limit r so that the first
             * byte in the sequence is at index p and the last byte is at index
             * p + n - 1, where p is the buffer's position at the moment this method is
             * invoked.  Upon return the buffer's position will be equal to
             * p + n. Its limit will not have changed.
             */

            logger.debug("buffer position = {}, buffer limit = {}, buffer remaining: {}", dst.position(), dst.limit(), dst.remaining());

            long readTo = Math.min(dst.limit() - dst.position(), size - delegator.position()) - 1;

            String range = "bytes=" + delegator.position() + "-" + (delegator.position() + readTo);
            logger.debug("byte range for {} is '{}'", key, range);

            final ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder
                    .bucket(path.bucketName())
                    .key(path.getKey())
                    .range(range));

            byte[] s3Bytes = responseBytes.asByteArray();
            logger.info("read {} bytes from {}", s3Bytes.length, key);

            delegator.position(delegator.position() + s3Bytes.length);
            logger.debug("new position of {} is: {}", key, delegator.position());

            int amountToWrite = Math.min(s3Bytes.length, dst.remaining());
            logger.debug("amount to write to buffer: {}, buffer remaining: {}", amountToWrite, dst.remaining());

            dst.put(s3Bytes, 0, amountToWrite);
            logger.debug("new buffer position is {}", dst.position());

            return s3Bytes.length;
        }
    }

    /**
     * This channel is always open
     */
    @Override
    public boolean isOpen() {
        return true;
    }

    /**
     * No op, this channel doesn't close as there are no resources to release.
     */
    @Override
    public void close() {}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy