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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2015-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 org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.entity.ContentType;
import org.apache.http.message.BasicHeader;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

/**
 * Implementation of {@link HttpEntity} that allows for the real-time streaming
 * of data from an iterator or a Java 8 stream to an {@link OutputStream} that
 * is connected to HTTP content.
 *
 * @author <a href="https://github.com/dekobon">Elijah Zupancic</a>
 */
public class StringIteratorHttpContent implements HttpEntity {
    /**
     * Iterator containing lines to stream into content.
     */
    private final Iterator<String> iterator;

    /**
     * Java 8 stream containing lines to stream into content.
     */
    private final Stream<String> stream;

    /**
     * Content (mime) type associated with content.
     */
    private final ContentType contentType;

    /**
     * Total bytes of content. Defaults to -1 before content is written.
     */
    private AtomicLong length = new AtomicLong(-1L);

    /**
     * Create a new instance based on a {@link Iterator} of strings.
     *
     * @param iterator iterator of strings for each line
     * @param contentType content (mime) type associated with content
     */
    public StringIteratorHttpContent(final Iterator<String> iterator, final ContentType contentType) {
        this.iterator = iterator;
        this.stream = null;
        this.contentType = contentType;
    }

    /**
     * Create a new instance based on a {@link Stream} of strings.
     * Stream will be closed after all elements are read.
     *
     * @param stream stream of strings for each line
     * @param contentType content (mime) type associated with content
     */
    public StringIteratorHttpContent(final Stream<String> stream, final ContentType contentType) {
        this.stream = stream;
        this.iterator = null;
        this.contentType = contentType;
    }

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

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

    @Override
    public long getContentLength() {
        return length.get();
    }

    @Override
    public Header getContentType() {
        return new BasicHeader(HttpHeaders.CONTENT_TYPE, contentType.toString());
    }

    @Override
    public void writeTo(final OutputStream out) throws IOException {
        try {
            if (iterator != null) {
                writeIterator(out);
            } else if (stream != null) {
                writeStream(out);
            }
        } finally {
            out.close();

            if (stream != null) {
                stream.close();
            }
        }
    }

    @Override
    public InputStream getContent() throws IOException, UnsupportedOperationException {
        throw new UnsupportedOperationException("getContent isn't supported");
    }

    @Override
    @Deprecated
    public void consumeContent() throws IOException {
        throw new UnsupportedOperationException("getContent isn't supported");
    }

    @Override
    public Header getContentEncoding() {
        // the content encoding is unknown, so we return null
        return null;
    }

    @Override
    public boolean isStreaming() {
        return true;
    }

    /**
     * Write all of the strings in the stored iterator to the passed
     * {@link OutputStream}.
     *
     * @param out output to write to
     * @throws IOException thrown when we can't write
     */
    protected void writeIterator(final OutputStream out) throws IOException {
        Validate.notNull(iterator, "Iterator must not be null");

        // Start length at zero because it is set to -1 by default
        length.set(0L);

        while (iterator.hasNext()) {
            String next = iterator.next();
            if (next == null) {
                continue;
            }

            // We use Unix new lines intentionally for the Manta service
            final String formatted = String.format("%s%s", next, StringUtils.LF);
            byte[] bytes = formatted.getBytes(StandardCharsets.UTF_8);
            length.addAndGet(bytes.length);
            out.write(bytes);
        }
    }

    /**
     * Write all of the strings in the stored stream to the passed
     * {@link OutputStream}.
     *
     * @param out output to write to
     * @throws IOException thrown when we can't write
     */
    protected void writeStream(final OutputStream out) throws IOException {
        Validate.notNull(stream, "Stream must not be null");

        // Start length at zero because it is set to -1 by default
        length.set(0L);

        /* This horribly contorted exception handling is because Java 8
         * streams do not support checked exception handling. */
        try {
            stream.forEach(item -> {
                if (item != null) {
                    try {
                        String formatted = String.format("%s\n", item);
                        byte[] bytes = formatted.getBytes(StandardCharsets.UTF_8);
                        length.addAndGet(bytes.length);
                        out.write(bytes);
                    } catch (IOException e) {
                        throw new StreamIOException(e);
                    }
                }
            });
        } catch (StreamIOException e) {
            throw e.getIOCause();
        }
    }

    /**
     * Inner exception class for handling the wrapping of {@link IOException}
     * when processing Java 8 streams.
     */
    protected static class StreamIOException extends RuntimeException {
        private static final long serialVersionUID = 1317022643615834337L;

        /**
         * Create an instance that wraps the passed cause.
         *
         * @param cause {@link IOException} to wrap
         */
        public StreamIOException(final Throwable cause) {
            super(cause);
        }

        /**
         * @return the wrapped exception as a {@link IOException}
         */
        @SuppressWarnings("unchecked")
        IOException getIOCause() {
            return (IOException) getCause();
        }
    }
}