/*
 * Copyright (c) 2010-2016 the original author or authors
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package org.jmxtrans.agent.util.io;

import org.jmxtrans.agent.util.Preconditions2;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;

/**
 * {@link Resource} for {@code http://...}, {@code https://...} or {@code file://...}.
 *
 * @author <a href="mailto:cleclerc@cloudbees.com">Cyrille Le Clerc</a>
 */
public class UrlResource extends AbstractResource implements Resource {
    private final URL url;
    private final URI uri;

    public UrlResource(@Nonnull String url) {
        Preconditions2.checkNotNull(url, "Given url cannot be null");
        try {
            this.url = new URL(url);
        } catch (MalformedURLException e) {
            throw new IoRuntimeException("Exception parsing URL '" + url + "'", e);
        }
        URI uri;
        try {
            uri = this.url.toURI();
        } catch (URISyntaxException e) {
            throw new IoRuntimeException("Exception parsing URL '" + url + "'", e);
        }
        this.uri = uri;
    }


    @Nonnull
    @Override
    public File getFile() throws IoRuntimeException {
        if (IoUtils.isFileUrl(url)) {
            return new File(uri);
        } else {
            return super.getFile();
        }
    }

    @Override
    public boolean exists() {
        if (IoUtils.isFileUrl(url)) {
            return super.exists();
        } else {
            URLConnection conn;
            try {
                conn = url.openConnection();
                if (conn instanceof HttpURLConnection) {
                    HttpURLConnection httpConn = (HttpURLConnection) conn;
                    configureUrlConnection(httpConn);
                    try {
                        int responseCode = httpConn.getResponseCode();
                        if (responseCode == HttpURLConnection.HTTP_OK) {
                            return true;
                        } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
                            return false;
                        } else if (httpConn.getContentLength() > 0) {
                            return true;
                        } else {
                            return false;
                        }
                    } finally {
                        httpConn.disconnect();
                    }
                } else {
                    return super.exists();
                }
            } catch (IOException e) {
                return false;
            }
        }
    }

    @Override
    public long lastModified() throws IoRuntimeException {
        if (IoUtils.isFileUrl(url)) {
            return super.lastModified();
        } else {
            URLConnection conn = null;
            try {
                conn = url.openConnection();
                configureUrlConnection(conn);
                if (conn instanceof HttpURLConnection) {
                    HttpURLConnection httpConn = (HttpURLConnection) conn;
                    int responseCode = httpConn.getResponseCode();
                    if (responseCode == HttpURLConnection.HTTP_OK) {
                        return httpConn.getLastModified();
                    } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
                        throw new FileNotFoundRuntimeException(getDescription() + " not found: " +
                                "responseCode=" + responseCode + ", usingProxy=" + httpConn.usingProxy());
                    } else if (responseCode / 100 == 4) {
                        throw new IoRuntimeException(getDescription() + " is not accessible: " +
                                "responseCode=" + responseCode + ", usingProxy=" + httpConn.usingProxy());
                    } else if (responseCode / 100 == 5) {
                        throw new IoRuntimeException(getDescription() + " is not available: " +
                                "responseCode=" + responseCode + ", usingProxy=" + httpConn.usingProxy());
                    } else {
                        return 0; // TODO should we fail?
                    }
                } else {
                    return super.lastModified();
                }
            } catch (IOException e) {
                throw IoRuntimeException.propagate(e);
            } finally {
                IoUtils.closeQuietly(conn);
            }
        }
    }

    protected void configureUrlConnection(URLConnection conn) {
        if (conn instanceof HttpURLConnection) {
            HttpURLConnection httpConn = (HttpURLConnection) conn;

            int connectTimeoutInMillis = (int) TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS);
            httpConn.setConnectTimeout(connectTimeoutInMillis);

            int readTimeoutInMillis = (int) TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS);
            httpConn.setReadTimeout(readTimeoutInMillis);
        } else {

        }
    }

    @Nonnull
    @Override
    public InputStream getInputStream() throws IoRuntimeException {
        URLConnection cnn = null;
        try {
            cnn = url.openConnection();
            configureUrlConnection(cnn);
            return cnn.getInputStream();
        } catch (IOException e) {
            IoUtils.closeQuietly(cnn);
            throw IoRuntimeException.propagate(e);
        }
    }

    @Override
    public String getDescription() {
        return "Http resource: " + url;
    }
}

@@ -1220,16 +1220,63 @@
 itions2;
+%0Aimport org.jmxtrans.agent.util.logging.Logger;
 %0A%0Aimport
@@ -1604,16 +1604,48 @@
 imeUnit;
+%0Aimport java.util.logging.Level;
 %0A%0Aimport
@@ -1842,16 +1842,16 @@
 /a%3E%0A */%0A
-
 public c
@@ -1910,24 +1910,101 @@
  Resource %7B%0A
+    protected final Logger logger = Logger.getLogger(getClass().getName());%0A%0A
     private 
@@ -4986,32 +4986,505 @@
 .usingProxy());%0A
+                    %7D else if ((responseCode / 100 == 2) %7C%7C (responseCode / 100 == 3)) %7B%0A                        long lastModified = httpConn.getLastModified();%0A                        if (logger.isLoggable(Level.FINER)) %7B%0A                            logger.finer(getDescription() + %22 returned an unexpected '%22 + responseCode + %22 %22 + httpConn.getResponseMessage() + %22'. lastModified=%22 + lastModified);%0A                        %7D%0A                        return lastModified;%0A
                 
@@ -5521,24 +5521,24 @@
 100 == 4) %7B%0A
-
             
@@ -6047,41 +6047,136 @@
     
-return 0; // TODO should we fail?
+throw new IoRuntimeException(getDescription() + %22returned an unexpected %22 + responseCode + %22 %22 + httpConn.getResponseMessage());
 %0A   

Throw an exception in lastModified() for unexpected HTTP Response codes. Note: for unexpected 20x and 30x, we try to get lastModified() instead of throwing an exception as they are not HTTP error codes. Based on @kerlandsson's suggestion.