/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package org.jdesktop.swingx.mapviewer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ResponseCache;
import java.net.URI;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author joshy
*/
public class LocalResponseCache extends ResponseCache
{
private final static Logger mLog =
LoggerFactory.getLogger( LocalResponseCache.class );
private final File cacheDir;
private boolean checkForUpdates;
private String baseURL;
/**
* Private constructor to prevent instantiation.
* @param baseURL the URI that should be cached or <code>null</code> (for all URLs)
* @param cacheDir the cache directory
* @param checkForUpdates true if the URL is queried for newer versions of a file first
*/
private LocalResponseCache(String baseURL, File cacheDir, boolean checkForUpdates)
{
this.baseURL = baseURL;
this.cacheDir = cacheDir;
this.checkForUpdates = checkForUpdates;
if (!cacheDir.exists())
{
cacheDir.mkdirs();
}
}
/**
* Sets this cache as default response cache
* @param baseURL the URL, the caching should be restricted to or <code>null</code> for none
* @param cacheDir the cache directory
* @param checkForUpdates true if the URL is queried for newer versions of a file first
*/
public static void installResponseCache(String baseURL, File cacheDir, boolean checkForUpdates)
{
ResponseCache.setDefault(new LocalResponseCache(baseURL, cacheDir, checkForUpdates));
}
/**
* Returns the local File corresponding to the given remote URI.
* @param remoteUri the remote URI
* @return the corresponding local file
*/
public File getLocalFile(URI remoteUri)
{
if (baseURL != null)
{
String remote = remoteUri.toString();
if (!remote.startsWith(baseURL))
{
return null;
}
}
StringBuilder sb = new StringBuilder();
String host = remoteUri.getHost();
String query = remoteUri.getQuery();
String path = remoteUri.getPath();
String fragment = remoteUri.getFragment();
if (host != null)
{
sb.append(host);
}
if (path != null)
{
sb.append(path);
}
if (query != null)
{
sb.append('?');
sb.append(query);
}
if (fragment != null)
{
sb.append('#');
sb.append(fragment);
}
String name;
final int maxLen = 250;
if (sb.length() < maxLen)
{
name = sb.toString();
}
else
{
name = sb.substring(0, maxLen);
}
name = name.replace('?', '$');
name = name.replace('*', '$');
name = name.replace(':', '$');
name = name.replace('<', '$');
name = name.replace('>', '$');
name = name.replace('"', '$');
File f = new File(cacheDir, name);
return f;
}
/**
* @param remoteUri the remote URI
* @param localFile the corresponding local file
* @return true if the resource at the given remote URI is newer than the resource cached locally.
*/
private static boolean isUpdateAvailable(URI remoteUri, File localFile)
{
URLConnection conn;
try
{
conn = remoteUri.toURL().openConnection();
}
catch (MalformedURLException ex)
{
mLog.error("An exception occurred - ", ex );
return false;
}
catch (IOException ex)
{
mLog.error("An exception occurred - ", ex );
return false;
}
if (!(conn instanceof HttpURLConnection))
{
// don't bother with non-http connections
return false;
}
long localLastMod = localFile.lastModified();
long remoteLastMod = 0L;
HttpURLConnection httpconn = (HttpURLConnection) conn;
// disable caching so we don't get in feedback loop with ResponseCache
httpconn.setUseCaches(false);
try
{
httpconn.connect();
remoteLastMod = httpconn.getLastModified();
}
catch (IOException ex)
{
// log.error("An exception occurred", ex);();
return false;
}
finally
{
httpconn.disconnect();
}
return (remoteLastMod > localLastMod);
}
@Override
public CacheResponse get(URI uri, String rqstMethod, Map<String, List<String>> rqstHeaders) throws IOException
{
File localFile = getLocalFile(uri);
if (localFile == null)
{
// we don't want to cache this URL
return null;
}
if (!localFile.exists())
{
// the file isn't already in our cache, return null
return null;
}
if (checkForUpdates)
{
if (isUpdateAvailable(uri, localFile))
{
// there is an update available, so don't return cached version
return null;
}
}
return new LocalCacheResponse(localFile, rqstHeaders);
}
@Override
public CacheRequest put(URI uri, URLConnection conn) throws IOException
{
// only cache http(s) GET requests
if (!(conn instanceof HttpURLConnection) || !(((HttpURLConnection) conn).getRequestMethod().equals("GET")))
{
return null;
}
File localFile = getLocalFile(uri);
if (localFile == null)
{
// we don't want to cache this URL
return null;
}
new File(localFile.getParent()).mkdirs();
return new LocalCacheRequest(localFile);
}
private class LocalCacheResponse extends CacheResponse
{
private FileInputStream fis;
private final Map<String, List<String>> headers;
private LocalCacheResponse(File localFile, Map<String, List<String>> rqstHeaders)
{
try
{
this.fis = new FileInputStream(localFile);
}
catch (FileNotFoundException ex)
{
// should not happen, since we already checked for existence
mLog.error("An exception occurred - ", ex );
}
this.headers = rqstHeaders;
}
@Override
public Map<String, List<String>> getHeaders() throws IOException
{
return headers;
}
@Override
public InputStream getBody() throws IOException
{
return fis;
}
}
private class LocalCacheRequest extends CacheRequest
{
private final File localFile;
private FileOutputStream fos;
private LocalCacheRequest(File localFile)
{
this.localFile = localFile;
try
{
this.fos = new FileOutputStream(localFile);
}
catch (FileNotFoundException ex)
{
// should not happen if cache dir is valid
mLog.error("An exception occurred", ex );
}
}
@Override
public OutputStream getBody() throws IOException
{
return fos;
}
@Override
public void abort()
{
// abandon the cache attempt by closing the stream and deleting
// the local file
try
{
fos.close();
localFile.delete();
}
catch (IOException e)
{
// ignore
}
}
}
}