fr.gael.dhus.sync.smart.download.ProductDownloadTask.java Source code

Java tutorial

Introduction

Here is the source code for fr.gael.dhus.sync.smart.download.ProductDownloadTask.java

Source

/*
 * Data Hub Service (DHuS) - For Space data distribution.
 * Copyright (C) 2018 GAEL Systems
 *
 * This file is part of DHuS software sources.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package fr.gael.dhus.sync.smart.download;

import fr.gael.dhus.util.http.CountingIWC;
import fr.gael.dhus.util.http.InterruptibleHttpClient;

import java.io.IOException;
import java.nio.channels.Pipe;
import java.util.Formatter;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Performs a download from a remote DHuS, and forward transferred bytes in the
 * associated {@link java.nio.channels.Pipe}.
 */
class ProductDownloadTask implements Runnable {
    private static final Logger LOGGER = LogManager.getLogger();

    private static final String HTTP_HEADER_CAUSE_MESSAGE = "cause-message";

    private final InterruptibleHttpClient httpClient;
    private final Pipe pipe;
    private final String url;
    private final String eTag;
    private final long skip;
    private final long contentLength;
    private final boolean useRange;

    private int attempts;

    /**
     * Constructs a <em>ProductDownloadTask</em> with the specific arguments.
     *
     * @param client        HTTP client
     * @param url           resource location of data
     * @param pipe          pipe to buffered the download
     * @param eTag          HTTP ETag of data
     * @param skip          offset
     * @param contentLength total size of data
     * @param attempts      max attempts number
     * @param useRange      define if download can use HTTP Range
     */
    ProductDownloadTask(final InterruptibleHttpClient client, final String url, final Pipe pipe, final String eTag,
            final long skip, final long contentLength, final int attempts, boolean useRange) {
        this.httpClient = client;
        this.pipe = pipe;
        this.url = url;
        this.eTag = eTag;
        this.attempts = attempts;
        this.skip = skip;
        this.contentLength = contentLength;
        this.useRange = useRange;
    }

    /**
     * raise an IOException with the given StatusLine and cause Header (cause may be null).
     */
    private void raiseFailure(StatusLine stl, Header cause) throws IOException {
        Formatter ff = new Formatter();
        ff.format("Cannot download %s, Reason='%s' (HTTP%d)", this.url, stl.getReasonPhrase(), stl.getStatusCode());
        if (cause != null) {
            String cause_msg = cause.getValue();
            if (cause_msg != null && !cause_msg.isEmpty()) {
                ff.format(" Cause='%s'", cause_msg);
            }
        }

        IOException exception = new IOException(ff.out().toString());
        ff.close();
        throw exception;
    }

    /**
     * Performs a download range (with multiple attempts if necessary) and
     * transfers downloaded bytes in the given channel.
     *
     * @param output output channel
     * @return the number of bytes transferred
     * @throws IOException          if a I/O error occurred
     * @throws InterruptedException if the download is interrupted
     */
    private long downloadRange(Pipe.SinkChannel output) throws IOException, InterruptedException {
        long start = skip;
        CountingIWC counter = new CountingIWC<>(output);

        for (int time = 1; (skip + counter.currentCount()) < contentLength && time < attempts; time++) {
            try {
                httpClient.interruptibleGetRange(url, counter, eTag, start, contentLength);
            } catch (IOException e) {
                LOGGER.debug("Download of {} interrupted cause by {}:{}", url, e.getClass(), e.getMessage());
                start = skip + counter.currentCount();
            }
        }

        return counter.currentCount();
    }

    @Override
    public void run() {
        Pipe.SinkChannel output = pipe.sink();
        try {
            if (useRange) {
                long received = downloadRange(output);
                if (received != (contentLength - skip)) {
                    LOGGER.trace("Download range [{}-{}] of {} failure", skip, contentLength, url);
                    throw new IOException("Incomplete download from " + url);
                }
                LOGGER.trace("Download range [{}-{}] complete from {}", skip, contentLength, url);
            } else {
                HttpResponse response = httpClient.interruptibleGet(url, output);
                if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                    raiseFailure(response.getStatusLine(), response.getFirstHeader(HTTP_HEADER_CAUSE_MESSAGE));
                }
            }
        } catch (IOException | InterruptedException e) {
            try {
                pipe.source().close(); // Will generate an IOException on the reader side
            } catch (IOException suppressed) {
            }
            try {
                pipe.sink().close();
            } catch (IOException suppressed) {
            }
        } finally {
            try {
                output.close();
            } catch (IOException e) {
                LOGGER.warn("Cannot close pipe");
            }
        }
    }
}