com.frochr123.helper.CachedFileDownloader.java Source code

Java tutorial

Introduction

Here is the source code for com.frochr123.helper.CachedFileDownloader.java

Source

/**
 * This file is part of VisiCut.
 * Copyright (C) 2011 - 2013 Thomas Oster <thomas.oster@rwth-aachen.de>
 * RWTH Aachen University - 52062 Aachen, Germany
 *
 *     VisiCut is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     VisiCut 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 Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with VisiCut.  If not, see <http://www.gnu.org/licenses/>.
 **/
package com.frochr123.helper;

import com.frochr123.fabqr.FabQRFunctions;
import com.t_oster.visicut.VisicutModel;
import com.t_oster.visicut.misc.FileUtils;
import com.t_oster.visicut.misc.Helper;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.AbstractMap.SimpleEntry;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map.Entry;
import org.apache.commons.io.FilenameUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.RedirectLocations;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

/**
 * CachedFileDownloader.java: Download files specified by their URL and cache them in memory
 * @author Christian
 */
public class CachedFileDownloader {
    // Constants
    public static final int CACHE_DOWNLOADER_MAX_ENTRIES = 25;
    public static final int CACHE_DOWNLOADER_DEFAULT_TIMEOUT = 5000;
    public static final int CACHE_DOWNLOADER_MAX_FILESIZE_BYTES = 10000000;
    public static final String CACHE_DOWNLOADER_DEFAULT_FILETYPES = "plf,svg";

    // Variables
    // Store: Key: Requested URL, Value: <Key: Final redirected URL (filename), Value: OutputStream>
    // LinkedHashMap stores items in their insertion order, which is important for removing old entries later on
    private static LinkedHashMap<String, SimpleEntry<String, File>> cacheMap = new LinkedHashMap<String, SimpleEntry<String, File>>();

    // Function to download a file to cache and serve the data
    // commaSeperatedAllowedFileTypes, e.g. : plf,svg
    public synchronized static SimpleEntry<String, SimpleEntry<String, File>> downloadFile(String url,
            String commaSeperatedAllowedFileTypes) throws IOException {
        // On invalid URL return null
        if (url == null || url.isEmpty()) {
            return null;
        }

        // Prepare check file extensions
        String allowedFileTypesString = commaSeperatedAllowedFileTypes;

        // Fallback to default allowed file types
        if (allowedFileTypesString == null || allowedFileTypesString.isEmpty()) {
            allowedFileTypesString = CACHE_DOWNLOADER_DEFAULT_FILETYPES;
        }

        // Get seperate file types from string and normalize to lowercase
        String[] allowedFileTypesArray = allowedFileTypesString.split(",");

        if (allowedFileTypesArray.length > 0) {
            // Normalize file extensions to lower case
            for (int i = 0; i < allowedFileTypesArray.length; ++i) {
                if (allowedFileTypesArray[i] != null && !allowedFileTypesArray[i].isEmpty()) {
                    allowedFileTypesArray[i] = allowedFileTypesArray[i].toLowerCase();
                }
            }
        }

        File file = null;
        String finalUrl = null;
        String fileExtension = null;
        String fileBaseName = null;
        SimpleEntry<String, File> innerResult = null;
        SimpleEntry<String, SimpleEntry<String, File>> finalResult = null;

        // Check if URL is already stored in cache map
        if (cacheMap.containsKey(url)) {
            if (cacheMap.get(url) != null) {
                innerResult = cacheMap.get(url);
                file = (File) (cacheMap.get(url).getValue());
            }
        }
        // URL is not stored in cache, download and store it in cache
        else {
            // Resize cache if needed, LinkedHashMap keeps insertion order, oldest entries are first entries
            // Temporary store keys in list and remove afterwards to avoid read / write issues
            // Get one free space for new download
            LinkedList<String> deleteKeys = new LinkedList<String>();
            for (Entry<String, SimpleEntry<String, File>> cacheEntry : cacheMap.entrySet()) {
                if ((cacheMap.size() - deleteKeys.size()) >= CACHE_DOWNLOADER_MAX_ENTRIES) {
                    deleteKeys.add(cacheEntry.getKey());
                } else {
                    break;
                }
            }

            // Remove keys
            if (!deleteKeys.isEmpty()) {
                for (String key : deleteKeys) {
                    // Delete old files
                    if (cacheMap.get(key) != null && cacheMap.get(key).getValue() != null) {
                        cacheMap.get(key).getValue().delete();
                    }

                    // Remove entry in cache map
                    cacheMap.remove(key);
                }
            }

            // Download file
            SimpleEntry<String, ByteArrayOutputStream> download = getResultFromURL(url);

            if (download == null || download.getKey() == null || download.getKey().isEmpty()
                    || download.getValue() == null) {
                return null;
            }

            // Check for file type
            if (allowedFileTypesArray.length > 0) {
                finalUrl = download.getKey();
                fileExtension = FilenameUtils.getExtension(finalUrl);

                // Check for valid fileExtension
                if (fileExtension == null || fileExtension.isEmpty()) {
                    return null;
                }

                // Check if fileExtension is contained in allowed file extensions
                // Normalize file extensions to lower case
                boolean foundAllowedFileExtension = false;

                for (int i = 0; i < allowedFileTypesArray.length; ++i) {
                    if (allowedFileTypesArray[i].equals(fileExtension)) {
                        foundAllowedFileExtension = true;
                        break;
                    }
                }

                // File extension was not found, abort
                if (!foundAllowedFileExtension) {
                    return null;
                }
            }

            // Write result to file, it is allowed file type
            fileBaseName = FilenameUtils.getBaseName(finalUrl);
            file = FileUtils.getNonexistingWritableFile(fileBaseName + "." + fileExtension);
            file.deleteOnExit();
            FileOutputStream filestream = new FileOutputStream(file);
            download.getValue().writeTo(filestream);

            // Insert into cache and result variable
            innerResult = new SimpleEntry<String, File>(finalUrl, file);
            cacheMap.put(url, innerResult);
        }

        if (innerResult != null) {
            finalResult = new SimpleEntry<String, SimpleEntry<String, File>>(url, innerResult);
        }

        return finalResult;
    }

    // Get the result of a request for a URL
    // Also returns the final URL for checking correct file types after redirects
    public static SimpleEntry<String, ByteArrayOutputStream> getResultFromURL(String url) throws IOException {
        SimpleEntry<String, ByteArrayOutputStream> result = null;
        String finalUrl = null;
        ByteArrayOutputStream outputStream = null;

        if (url == null || url.isEmpty()) {
            return null;
        }

        // Create HTTP client and cusomized config for timeouts
        CloseableHttpClient httpClient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(CACHE_DOWNLOADER_DEFAULT_TIMEOUT)
                .setConnectTimeout(CACHE_DOWNLOADER_DEFAULT_TIMEOUT)
                .setConnectionRequestTimeout(CACHE_DOWNLOADER_DEFAULT_TIMEOUT).build();

        // Create HTTP Get request
        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);

        // Create context
        HttpContext context = new BasicHttpContext();

        // Check for temporary FabQR download of own configured private FabQR instance
        // In that case Authorization needs to be added
        if (FabQRFunctions.getFabqrPrivateURL() != null && !FabQRFunctions.getFabqrPrivateURL().isEmpty()
                && url.startsWith(FabQRFunctions.getFabqrPrivateURL())
                && url.contains("/" + FabQRFunctions.FABQR_TEMPORARY_MARKER + "/")) {
            // Set authentication information
            String encodedCredentials = Helper.getEncodedCredentials(FabQRFunctions.getFabqrPrivateUser(),
                    FabQRFunctions.getFabqrPrivatePassword());
            if (!encodedCredentials.isEmpty()) {
                httpGet.addHeader("Authorization", "Basic " + encodedCredentials);
            }
        }

        // Send request
        CloseableHttpResponse response = httpClient.execute(httpGet, context);

        // Get all redirected locations from context, if there are any
        RedirectLocations redirectLocations = (RedirectLocations) (context
                .getAttribute(HttpClientContext.REDIRECT_LOCATIONS));
        if (redirectLocations != null) {
            finalUrl = redirectLocations.getAll().get(redirectLocations.getAll().size() - 1).toString();
        } else {
            finalUrl = url;
        }

        // Check response valid and max file size
        if (response.getEntity() == null
                || response.getEntity().getContentLength() > CACHE_DOWNLOADER_MAX_FILESIZE_BYTES) {
            return null;
        }

        // Get data
        outputStream = new ByteArrayOutputStream();
        response.getEntity().writeTo(outputStream);

        // Return result
        result = new SimpleEntry<String, ByteArrayOutputStream>(finalUrl, outputStream);
        return result;
    }
}