com.joyent.manta.client.multipart.AbstractMultipartManager.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 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.multipart;

import com.joyent.manta.client.MantaClient;
import com.joyent.manta.exception.MantaMultipartException;
import com.joyent.manta.http.entity.ExposedByteArrayEntity;
import com.joyent.manta.http.entity.ExposedStringEntity;
import com.joyent.manta.http.entity.MantaInputStreamEntity;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.protocol.HttpContext;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;

/**
 * <p>Base class providing generic methods useful for {@link MantaMultipartManager}
 * implementations.</p>
 *
 * <p>This class provides a coherent structure for multipart implementations
 * that communicate over the network using the Apache HTTP Client library by
 * providing default mappings for part upload methods to a {@link HttpEntity}
 * upload method.</p>
 *
 * <p>By allowing for UPLOAD and PART to be generic, we can easily create
 * wrapping implementations like {@link EncryptedMultipartManager} that
 * will wrap any base implementation.</p>
 *
 * @author <a href="https://github.com/dekobon">Elijah Zupancic</a>
 * @since 3.0.0
 *
 * @param <UPLOAD> Manta multipart upload object used to manage MPU state
 * @param <PART> Manta multipart upload part object used to manage MPU part state
 */
abstract class AbstractMultipartManager<UPLOAD extends MantaMultipartUpload, PART extends MantaMultipartUploadPart>
        implements MantaMultipartManager<UPLOAD, PART> {
    @SuppressWarnings("ReturnValueIgnored")
    @Override
    public void validateThatThereAreSequentialPartNumbers(final UPLOAD upload)
            throws IOException, MantaMultipartException {
        Validate.notNull(upload, "Multipart upload object must not be null");

        listParts(upload).sorted().map(MantaMultipartUploadPart::getPartNumber).reduce(1, (memo, value) -> {
            if (!memo.equals(value)) {
                MantaMultipartException e = new MantaMultipartException("Missing part of multipart upload");
                e.setContextValue("missing_part", memo);
                e.setContextValue("uploadId", upload.getId());
                e.setContextValue("path", upload.getPath());
                throw e;
            }

            return memo + 1;
        });
    }

    /**
     * Validates that the given part number is specified correctly.
     *
     * @param partNumber integer part number value
     * @throws IllegalArgumentException if partNumber is less than 1 or greater than MULTIPART_DIRECTORY
     */
    void validatePartNumber(final int partNumber) {
        if (partNumber <= 0) {
            throw new IllegalArgumentException("Negative or zero part numbers " + "are not valid");
        }

        if (partNumber > getMaxParts()) {
            final String msg = String.format("Part number of [%d] exceeds " + "maximum parts (%d)", partNumber,
                    getMaxParts());
            throw new IllegalArgumentException(msg);
        }
    }

    @Override
    public PART uploadPart(final UPLOAD upload, final int partNumber, final String contents) throws IOException {
        validatePartNumber(partNumber);
        Validate.notNull(contents, "String must not be null");

        HttpEntity entity = new ExposedStringEntity(contents, ContentType.APPLICATION_OCTET_STREAM);

        return uploadPart(upload, partNumber, entity, null);
    }

    @Override
    public PART uploadPart(final UPLOAD upload, final int partNumber, final byte[] bytes) throws IOException {
        validatePartNumber(partNumber);
        Validate.notNull(bytes, "Byte array must not be null");

        HttpEntity entity = new ExposedByteArrayEntity(bytes, ContentType.APPLICATION_OCTET_STREAM);
        return uploadPart(upload, partNumber, entity, null);
    }

    @Override
    public PART uploadPart(final UPLOAD upload, final int partNumber, final File file) throws IOException {
        validatePartNumber(partNumber);
        Validate.notNull(file, "File must not be null");

        if (!file.exists()) {
            String msg = String.format("File doesn't exist: %s", file.getPath());
            throw new FileNotFoundException(msg);
        }

        if (!file.canRead()) {
            String msg = String.format("Can't access file for read: %s", file.getPath());
            throw new IOException(msg);
        }

        HttpEntity entity = new FileEntity(file, ContentType.APPLICATION_OCTET_STREAM);
        return uploadPart(upload, partNumber, entity, null);
    }

    @Override
    public PART uploadPart(final UPLOAD upload, final int partNumber, final InputStream inputStream)
            throws IOException {
        Validate.notNull(inputStream, "InputStream must not be null");

        if (inputStream.getClass().equals(FileInputStream.class)) {
            final FileInputStream fin = (FileInputStream) inputStream;
            final long contentLength = fin.getChannel().size();
            return uploadPart(upload, partNumber, contentLength, inputStream);
        }

        HttpEntity entity = new MantaInputStreamEntity(inputStream, ContentType.APPLICATION_OCTET_STREAM);

        return uploadPart(upload, partNumber, entity, null);
    }

    @Override
    public PART uploadPart(final UPLOAD upload, final int partNumber, final long contentLength,
            final InputStream inputStream) throws IOException {
        Validate.notNull(inputStream, "InputStream must not be null");

        HttpEntity entity;

        if (contentLength > -1) {
            entity = new MantaInputStreamEntity(inputStream, contentLength, ContentType.APPLICATION_OCTET_STREAM);
        } else {
            entity = new MantaInputStreamEntity(inputStream, ContentType.APPLICATION_OCTET_STREAM);
        }

        return uploadPart(upload, partNumber, entity, null);
    }

    /**
     * <p>Uploads a single part of a multipart upload using an implementation
     * of the Apache HTTP Client's {@link HttpEntity} interface.</p>
     *
     * <p>Even though all implementations of this class take a reference to
     * {@link MantaClient} as a constructor parameter, the {@link MantaClient}
     * class isn't always the best interface to interact with to specify
     * how a multipart upload part is sent to Manta. The abstraction of
     * {@link MantaClient} is intended for users of the SDK and not necessarily
     * internal consumers, so additional customization of HTTP calls needs to
     * be done by a {@link MantaMultipartManager}.</p>
     *
     * @param upload multipart upload object
     * @param partNumber part number to identify relative location in final file
     * @param entity Apache HTTP Client entity instance
     * @param context additional request context, may be null
     * @return multipart single part object
     * @throws IOException thrown if there is a problem connecting to Manta
     */
    abstract PART uploadPart(UPLOAD upload, int partNumber, HttpEntity entity, HttpContext context)
            throws IOException;

    /**
     * <p>Uses reflection to read a private field from a {@link MantaClient}
     * instance.</p>
     *
     * <p>We use reflection to read private fields from {@link MantaClient} as
     * part of a compromise between package level separation and private/default
     * scoping. Essentially, it makes sense to put multipart related classes
     * in their own package because the package in which {@link MantaClient} is
     * contained is already crowded. However, by making that decision there is
     * no scoping mechanism available in Java to allow us to share
     * methods/fields between packages without giving other packages access.
     * Thus, we scope the methods/fields that shouldn't be available to a user
     * of the SDK as private/protected/default and use reflection
     * <em>sparingly</em> to access the values from another package. Particular
     * care has been paid to making this reflection-based reads outside of
     * performance sensitive code paths.</p>
     *
     * @param fieldName field name to read
     * @param mantaClient Manta client instance to read fields from
     * @param returnType type of field
     * @param <T> field type
     * @return value of the field
     */
    protected static <T> T readFieldFromMantaClient(final String fieldName, final MantaClient mantaClient,
            final Class<T> returnType) {
        final Field field = FieldUtils.getField(MantaClient.class, fieldName, true);

        try {
            Object object = FieldUtils.readField(field, mantaClient, true);
            return returnType.cast(object);
        } catch (IllegalAccessException e) {
            throw new MantaMultipartException("Unable to access httpHelper " + "field on MantaClient");
        }
    }
}