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

cz.o2.proxima.direct.io.s3.S3BlobPath Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2024 O2 Czech Republic, a.s.
 *
 * 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 cz.o2.proxima.direct.io.s3;

import com.amazonaws.AmazonClientException;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.SSECustomerKey;
import cz.o2.proxima.core.annotations.Internal;
import cz.o2.proxima.direct.core.Context;
import cz.o2.proxima.direct.io.blob.BlobBase;
import cz.o2.proxima.direct.io.blob.BlobPath;
import cz.o2.proxima.direct.io.bulkfs.Path;
import cz.o2.proxima.internal.com.google.common.annotations.VisibleForTesting;
import cz.o2.proxima.internal.com.google.common.base.Preconditions;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.Objects;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/** A {@link Path} representation of a remote blob in S3. */
@Internal
@Slf4j
public class S3BlobPath extends BlobPath implements Path {

  private static final long serialVersionUID = 1L;

  public static class S3Blob implements BlobBase {

    @Getter private final String name;
    private final long size;
    @Nullable private final S3FileSystem fs;

    S3Blob(String name, long size) {
      // For existing files, where we already know the size.
      Preconditions.checkArgument(size >= 0, "Unknown size.");
      this.name = Objects.requireNonNull(name);
      this.size = size;
      this.fs = null;
    }

    @VisibleForTesting
    S3Blob(String name, S3FileSystem fs) {
      // For new files where we don't know the size upfront.
      this.name = Objects.requireNonNull(name);
      this.size = -1;
      this.fs = Objects.requireNonNull(fs);
    }

    @Override
    public long getSize() {
      if (size > -1) {
        return size;
      }
      Objects.requireNonNull(fs);
      try (final S3Object object = fs.getObject(name)) {
        return object.getObjectMetadata().getContentLength();
      } catch (Exception e) {
        log.warn("Unable to retrieve object size of [{}] from [{}].", name, fs.getUri(), e);
        return 0L;
      }
    }
  }

  public static S3BlobPath of(Context context, S3FileSystem fs, String name) {
    return new S3BlobPath(context, fs, new S3Blob(name, fs));
  }

  public static S3BlobPath of(Context context, S3FileSystem fs, String name, long size) {
    return new S3BlobPath(context, fs, new S3Blob(name, size));
  }

  private final Context context;

  @VisibleForTesting
  S3BlobPath(Context context, S3FileSystem fs, S3Blob blob) {
    super(fs, blob);
    this.context = Objects.requireNonNull(context);
  }

  @Override
  public ReadableByteChannel read() {
    final long contentLength = getBlob().getSize();
    return new SeekableByteChannel() {

      private boolean open = true;

      private S3Object object;
      private ReadableByteChannel contentChannel;
      private long position = 0;

      @Override
      public int read(ByteBuffer dst) throws IOException {
        ensureOpen();
        if (position >= contentLength) {
          // End of file.
          return -1;
        }
        if (object == null) {
          final S3FileSystem fs = (S3FileSystem) getFileSystem();
          final GetObjectRequest request = new GetObjectRequest(fs.getBucket(), getBlobName());
          @Nullable final SSECustomerKey sseCustomerKey = fs.getSseCustomerKey();
          if (sseCustomerKey != null) {
            request.setSSECustomerKey(sseCustomerKey);
          }
          if (position > 0) {
            request.setRange(position, contentLength);
          }
          object = fs.client().getObject(request);
          contentChannel =
              Channels.newChannel(new BufferedInputStream(object.getObjectContent(), 1024 * 1024));
        }

        int totalBytesRead = 0;
        int bytesRead = 0;

        do {
          totalBytesRead += bytesRead;
          try {
            bytesRead = contentChannel.read(dst);
          } catch (AmazonClientException e) {
            throw new IOException(e);
          }
        } while (bytesRead > 0);

        position += totalBytesRead;
        return totalBytesRead;
      }

      @Override
      public long position() throws IOException {
        ensureOpen();
        return position;
      }

      @Override
      public SeekableByteChannel position(long newPosition) throws IOException {
        ensureOpen();
        Preconditions.checkArgument(newPosition >= 0, "New position is too low.");
        Preconditions.checkArgument(newPosition < contentLength, "New position is too high.");

        if (newPosition == position) {
          return this;
        }

        // The position has changed, so close and destroy the object to induce a re-creation on the
        // next call to read()
        if (object != null) {
          object.close();
          object = null;
        }
        position = newPosition;
        return this;
      }

      @Override
      public long size() throws IOException {
        ensureOpen();
        return contentLength;
      }

      @Override
      public boolean isOpen() {
        return open;
      }

      @Override
      public void close() throws IOException {
        if (object != null) {
          object.close();
          object = null;
        }
        open = false;
      }

      @Override
      public int write(ByteBuffer src) {
        throw new NonWritableChannelException();
      }

      @Override
      public SeekableByteChannel truncate(long size) {
        throw new NonWritableChannelException();
      }

      private void ensureOpen() throws IOException {
        if (!open) {
          throw new ClosedChannelException();
        }
      }
    };
  }

  @Override
  public InputStream reader() {
    return Channels.newInputStream(read());
  }

  @Override
  public OutputStream writer() {
    return ((S3Client) getFileSystem()).putObject(getBlobName());
  }

  @Override
  public void delete() {
    ((S3Client) getFileSystem()).deleteObject(getBlob().getName());
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy