com.ethlo.geodata.util.ResourceUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.ethlo.geodata.util.ResourceUtil.java

Source

package com.ethlo.geodata.util;

/*-
 * #%L
 * geodata
 * %%
 * Copyright (C) 2017 Morten Haraldsen (ethlo)
 * %%
 * This program 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.
 * 
 * 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 General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.AbstractMap;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.zeroturnaround.zip.ZipUtil;

import com.ethlo.geodata.DataLoadedEvent;
import com.ethlo.geodata.importer.DataType;
import com.ethlo.geodata.importer.Operation;
import com.vividsolutions.jts.util.Assert;

public class ResourceUtil {
    private static final Logger logger = LoggerFactory.getLogger(ResourceUtil.class);
    private final File tmpDir;
    private ApplicationEventPublisher publisher;

    public ResourceUtil(ApplicationEventPublisher publisher, File tmpDir) {
        this.publisher = publisher;
        this.tmpDir = tmpDir;
    }

    public Date getLastModified(String urlStr) throws IOException {
        final Resource connection = openConnection(urlStr);
        final long lastModified = connection.lastModified();
        if (lastModified == 0) {
            throw new IOException("No value for Last-Modified for URL " + urlStr);
        }
        return new Date(lastModified);
    }

    private Resource openConnection(String urlStr) throws IOException {
        final String[] urlParts = StringUtils.split(urlStr, "|");

        if (urlStr.startsWith("file:")) {
            String path = urlParts[0].substring(7);
            if (path.startsWith("~" + File.separator)) {
                path = System.getProperty("user.home") + path.substring(1);
            }
            final File file = new File(path);
            if (!file.exists()) {
                throw new FileNotFoundException(file.getAbsolutePath());
            }
            return new FileSystemResource(file);
        } else if (urlStr.startsWith("classpath:")) {
            return new ClassPathResource(urlParts[0].substring(10));
        }

        return new UrlResource(urlParts[0]);
    }

    public Map.Entry<Date, File> fetchResource(DataType dataType, String urlStr) throws IOException {
        final String[] urlParts = StringUtils.split(urlStr, "|");
        if (urlParts[0].endsWith(".zip")) {
            return fetchZip(dataType, urlParts[0], urlParts[1]);
        } else {
            return fetch(dataType, urlStr);
        }
    }

    private Map.Entry<Date, File> fetchZip(DataType dataType, String url, String zipEntry) throws IOException {
        final Resource resource = openConnection(url);
        return downloadIfNewer(dataType, resource, f -> {
            final File unzipDir = new File(tmpDir, dataType.name().toLowerCase() + "_unzip");
            ZipUtil.unpack(f.toFile(), unzipDir, name -> name.endsWith(zipEntry) ? zipEntry : null,
                    StandardCharsets.UTF_8);
            final File file = new File(unzipDir, zipEntry);
            Assert.isTrue(file.exists(), "File " + file + " does not exist");
            return file.toPath();
        });
    }

    private Entry<Date, File> downloadIfNewer(DataType dataType, Resource resource, CheckedFunction<Path, Path> fun)
            throws IOException {
        publisher.publishEvent(new DataLoadedEvent(this, dataType, Operation.DOWNLOAD, 0, 1));
        final String alias = dataType.name().toLowerCase();
        final File tmpDownloadedFile = new File(tmpDir, alias);
        final Date remoteLastModified = new Date(resource.lastModified());
        final long localLastModified = tmpDownloadedFile.exists() ? tmpDownloadedFile.lastModified() : -2;
        logger.info(
                "Local file for alias {}" + "\nPath: {}" + "\nExists: {}" + "\nLocal last-modified: {} "
                        + "\nRemote last modified: {}",
                alias, tmpDownloadedFile.getAbsolutePath(), tmpDownloadedFile.exists(),
                formatDate(localLastModified), formatDate(remoteLastModified.getTime()));

        if (!tmpDownloadedFile.exists() || remoteLastModified.getTime() > localLastModified) {
            logger.info("Downloading {}", resource.getURL());
            Files.copy(resource.getInputStream(), tmpDownloadedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            logger.info("Download complete");
        }

        final Path preppedFile = fun.apply(tmpDownloadedFile.toPath());
        Files.setLastModifiedTime(tmpDownloadedFile.toPath(), FileTime.fromMillis(remoteLastModified.getTime()));
        Files.setLastModifiedTime(preppedFile, FileTime.fromMillis(remoteLastModified.getTime()));
        publisher.publishEvent(new DataLoadedEvent(this, dataType, Operation.DOWNLOAD, 1, 1));
        return new AbstractMap.SimpleEntry<>(new Date(remoteLastModified.getTime()), preppedFile.toFile());
    }

    private static LocalDateTime formatDate(long timestamp) {
        return LocalDateTime.ofEpochSecond(timestamp / 1_000, 0, ZoneOffset.UTC);
    }

    private Entry<Date, File> fetch(DataType dataType, String url) throws IOException {
        final Resource resource = openConnection(url);
        return downloadIfNewer(dataType, resource, in -> in);
    }

    @FunctionalInterface
    public interface CheckedFunction<T, R> {
        R apply(T t) throws IOException;
    }
}