com.joyent.manta.client.MantaSeekableByteChannel.java Source code

Java tutorial

Introduction

Here is the source code for com.joyent.manta.client.MantaSeekableByteChannel.java

Source

/*
 * Copyright (c) 2016-2017, Joyent, Inc. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.joyent.manta.client;

import com.joyent.manta.exception.MantaClientException;
import com.joyent.manta.http.HttpHelper;
import com.joyent.manta.http.MantaConnectionFactory;
import com.joyent.manta.http.MantaHttpHeaders;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A read-only {@link SeekableByteChannel} implementation that utilizes
 * the HTTP Range header to allow you to seek any position in an object on
 * Manta. Connection opening to the remote server happens lazily upon the
 * first read() or size() method invoked.
 *
 * @author Elijah Zupancic
 */
public class MantaSeekableByteChannel extends InputStream implements SeekableByteChannel {
    /**
     * Constant representing the value returned when we have reached the
     * end of a stream.
     */
    private static final int EOF = -1;

    /**
     * Constant representing an unknown HTTP content length value.
     */
    private static final long UNKNOWN_CONTENT_LENGTH = -1L;

    /**
     * Flag indicating if the channel is open. Marked as volatile so
     * that different threads can flip its state.
     */
    private volatile boolean open = true;

    /**
     * Path of the object on the Manta API.
     */
    private final String path;

    /**
     * Current position in bytes from the start of the file.
     */
    private AtomicLong position = new AtomicLong(0L);

    /**
     * Helper class providing useful HTTP functions.
     */
    private final HttpHelper httpHelper;

    /**
     * Threadsafe reference to the Manta API HTTP request object.
     */
    private final AtomicReference<HttpUriRequest> requestRef;

    /**
     * Threadsafe reference to the Manta API HTTP response object.
     */
    private final AtomicReference<MantaObjectInputStream> responseStream;

    /**
     * Creates a new instance of a read-only seekable byte channel.
     *
     * @param path path of the object on the Manta API
     * @param position starting position in bytes from the start of the file
     * @param connectionFactory connection factory instance used for building requests to Manta
     * @param httpHelper helper class providing useful HTTP functions
     */
    @Deprecated
    public MantaSeekableByteChannel(final String path, final long position,
            final MantaConnectionFactory connectionFactory, final HttpHelper httpHelper) {
        this.path = path;
        this.position = new AtomicLong(position);
        this.httpHelper = httpHelper;
        this.requestRef = new AtomicReference<>();
        this.responseStream = new AtomicReference<>();
    }

    /**
     * Creates a new instance of a read-only seekable byte channel.
     *
     * @param path path of the object on the Manta API
     * @param connectionFactory connection factory instance used for building requests to Manta
     * @param httpHelper helper class providing useful HTTP functions
     */
    @Deprecated
    public MantaSeekableByteChannel(final String path, final MantaConnectionFactory connectionFactory,
            final HttpHelper httpHelper) {
        this(path, 0L, connectionFactory, httpHelper);
    }

    /**
     * Constructor used for creating a new instance of a read-only seekable byte
     * channel from within this class. This is used when position() is called.
     *
     * @param requestRef reference to existing HTTP request
     * @param responseStream reference to existing HTTP response
     * @param path path of the object on the Manta API
     * @param position starting position in bytes from the start of the file
     * @param connectionFactory connection factory instance used for building requests to Manta
     * @param httpHelper helper class providing useful HTTP functions
     */
    @Deprecated
    protected MantaSeekableByteChannel(final AtomicReference<HttpUriRequest> requestRef,
            final AtomicReference<MantaObjectInputStream> responseStream, final String path,
            final AtomicLong position, final MantaConnectionFactory connectionFactory,
            final HttpHelper httpHelper) {
        this.requestRef = requestRef;
        this.responseStream = responseStream;
        this.path = path;
        this.position = position;
        this.httpHelper = httpHelper;
    }

    /**
     * Creates a new instance of a read-only seekable byte channel.
     *
     * @param path path of the object on the Manta API
     * @param position starting position in bytes from the start of the file
     * @param httpHelper helper class providing useful HTTP functions
     */
    public MantaSeekableByteChannel(final String path, final long position, final HttpHelper httpHelper) {
        this.path = path;
        this.position = new AtomicLong(position);
        this.httpHelper = httpHelper;
        this.requestRef = new AtomicReference<>();
        this.responseStream = new AtomicReference<>();
    }

    /**
     * Creates a new instance of a read-only seekable byte channel.
     *
     * @param path path of the object on the Manta API
     * @param httpHelper helper class providing useful HTTP functions
     */
    public MantaSeekableByteChannel(final String path, final HttpHelper httpHelper) {
        this(path, 0L, httpHelper);
    }

    /**
     * Constructor used for creating a new instance of a read-only seekable byte
     * channel from within this class. This is used when position() is called.
     *
     * @param requestRef reference to existing HTTP request
     * @param responseStream reference to existing HTTP response
     * @param path path of the object on the Manta API
     * @param position starting position in bytes from the start of the file
     * @param httpHelper helper class providing useful HTTP functions
     */
    protected MantaSeekableByteChannel(final AtomicReference<HttpUriRequest> requestRef,
            final AtomicReference<MantaObjectInputStream> responseStream, final String path,
            final AtomicLong position, final HttpHelper httpHelper) {
        this.requestRef = requestRef;
        this.responseStream = responseStream;
        this.path = path;
        this.position = position;
        this.httpHelper = httpHelper;
    }

    @Override
    public int read(final ByteBuffer dst) throws IOException {
        if (!open) {
            throw new ClosedChannelException();
        }

        final MantaObjectInputStream stream = connectOrGetResponse();
        final long size = size();

        if (position.get() >= size) {
            return EOF;
        }

        final byte[] buff = dst.array();
        final int bytesRead = stream.read(buff);

        position.addAndGet(bytesRead);

        return bytesRead;
    }

    /**
     * Reads the next byte of data from the backing input stream at the current
     * position. The value byte is returned as an <code>int</code> in the range
     * <code>0</code> to <code>255</code>. If no byte is available because the
     * end of the stream has been reached, the value <code>-1</code> is returned.
     * This method blocks until input data is available, the end of the stream
     * is detected, or an exception is thrown.
     *
     * @return     the next byte of data, or <code>-1</code> if the end of the
     *             stream is reached.
     * @exception  IOException  if an I/O error occurs.
     */
    @Override
    public int read() throws IOException {
        if (!open) {
            throw new ClosedChannelException();
        }

        final MantaObjectInputStream stream = connectOrGetResponse();
        final int read = stream.read();

        if (read > -1) {
            position.incrementAndGet();
        }

        return read;
    }

    @Override
    public int read(final byte[] buffer) throws IOException {
        if (!open) {
            throw new ClosedChannelException();
        }

        final MantaObjectInputStream stream = connectOrGetResponse();

        final int totalRead = stream.read(buffer);

        if (totalRead > -1) {
            position.addAndGet(totalRead);
        }

        return totalRead;
    }

    @Override
    public int read(final byte[] buffer, final int offset, final int length) throws IOException {
        if (!open) {
            throw new ClosedChannelException();
        }

        final MantaObjectInputStream stream = connectOrGetResponse();

        final int totalRead = stream.read(buffer, offset, length);

        if (totalRead > -1) {
            position.addAndGet(totalRead);
        }

        return totalRead;
    }

    @Override
    public long skip(final long noOfBytesToSkip) throws IOException {
        if (!open) {
            return 0;
        }

        final MantaObjectInputStream stream = connectOrGetResponse();
        final long totalSkipped = stream.skip(noOfBytesToSkip);

        position.addAndGet(totalSkipped);

        return totalSkipped;
    }

    @Override
    public int available() throws IOException {
        if (!open) {
            throw new ClosedChannelException();
        }

        final MantaObjectInputStream stream = connectOrGetResponse();

        return stream.available();
    }

    @Override
    public int write(final ByteBuffer src) throws IOException {
        // This is a read-only channel
        throw new NonWritableChannelException();
    }

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

    @Override
    public SeekableByteChannel position(final long newPosition) throws IOException {
        return new MantaSeekableByteChannel(new AtomicReference<>(), new AtomicReference<>(), path,
                new AtomicLong(newPosition), httpHelper);
    }

    @Override
    public long size() throws IOException {
        if (!open) {
            throw new ClosedChannelException();
        }

        final MantaObjectInputStream stream = connectOrGetResponse();
        final long contentLength = stream.getContentLength();

        if (contentLength == UNKNOWN_CONTENT_LENGTH) {
            MantaClientException e = new MantaClientException(
                    "Can't get SeekableByteChannel for objects of unknown size");
            HttpUriRequest request = requestRef.get();
            if (request != null) {
                @SuppressWarnings("unchecked")
                HttpResponse response = (HttpResponse) stream.getHttpResponse();
                HttpHelper.annotateContextedException(e, request, response);
            }

            throw e;
        }

        return contentLength;
    }

    @Override
    public synchronized void mark(final int readlimit) {
    }

    @Override
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    @Override
    public SeekableByteChannel truncate(final long newSize) throws IOException {
        throw new NonWritableChannelException();
    }

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

    @Override
    public synchronized void close() throws IOException {
        if (!open) {
            return;
        }

        MantaObjectInputStream stream = responseStream.getAndSet(null);
        IOUtils.closeQuietly(stream);

        open = false;
    }

    /**
     * Connects to the Manta API, updates the atomic reference and returns a
     * response if the atomic reference hasn't been set. Otherwise, it just returns
     * the response embedded in the atomic reference.
     *
     * @return HTTP response object
     * @throws IOException thrown when there are network problems connecting to the remote API
     */
    protected synchronized MantaObjectInputStream connectOrGetResponse() throws IOException {
        if (responseStream.get() != null) {
            return responseStream.get();
        }

        final HttpUriRequest request = httpHelper.getRequestFactory().get(path);
        final MantaHttpHeaders headers = new MantaHttpHeaders();

        // Set byte range requested via HTTP range header
        headers.setRange(String.format("bytes=%d-", position.get()));

        // Store the request so that we can use it for adding information to exceptions
        this.requestRef.set(request);

        MantaObjectInputStream stream = httpHelper.httpRequestAsInputStream(request, headers);
        responseStream.compareAndSet(null, stream);

        final String contentType = stream.getContentType();

        if (MantaObjectResponse.DIRECTORY_RESPONSE_CONTENT_TYPE.equals(contentType)) {
            MantaClientException e = new MantaClientException(
                    "Can't get SeekableByteChannel for directory objects");

            @SuppressWarnings("unchecked")
            HttpResponse response = (HttpResponse) stream.getHttpResponse();
            HttpHelper.annotateContextedException(e, request, response);
            throw e;
        }

        return stream;
    }
}