/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.layers.data;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.avlist.AVListImpl;
import gov.nasa.worldwind.cache.FileStore;
import gov.nasa.worldwind.retrieve.AbstractRetrievalPostProcessor;
import gov.nasa.worldwind.retrieve.RetrievalPostProcessor;
import gov.nasa.worldwind.retrieve.Retriever;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.WWIO;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import au.gov.ga.earthsci.worldwind.common.layers.delegate.retriever.PassThroughZipRetriever;
/**
* Basic implementation of the {@link DataProvider} interface. Handles
* retrieving the data from the layer's url, and once downloaded, calls an
* abstract method which loads the data. Also handles caching the data.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public abstract class AbstractDataProvider<L extends DataLayer> implements DataProvider<L>
{
private final Object readLock = new Object();
private boolean reading = false;
private boolean loading = false;
private boolean loaded = false;
private FileStore dataFileStore = WorldWind.getDataFileStore();
private final Object fileLock = new Object();
private final LoadingListenerList loadingListeners = new LoadingListenerList();
@Override
public void requestData(L layer)
{
synchronized (readLock)
{
if (!loaded && !reading)
{
RequestTask task = new RequestTask(this, layer);
if (!WorldWind.getTaskService().isFull())
{
loading = true;
loadingListeners.notifyListeners(this, isLoading());
WorldWind.getTaskService().addTask(task);
}
}
}
}
/**
* Is the cached file expired (download time is earlier than layer's last
* update time)?
*
* @param layer
* @param fileURL
* @param fileStore
* @return True if the file has expired
*/
protected boolean isFileExpired(L layer, URL fileURL, FileStore fileStore)
{
if (!WWIO.isFileOutOfDate(fileURL, layer.getExpiryTime()))
return false;
// The file has expired. Delete it.
fileStore.removeFile(fileURL);
String message = Logging.getMessage("generic.DataFileExpired", fileURL);
Logging.logger().fine(message);
return true;
}
public FileStore getDataFileStore()
{
return this.dataFileStore;
}
public Object getFileLock()
{
return fileLock;
}
public void setDataFileStore(FileStore fileStore)
{
if (fileStore == null)
{
String message = Logging.getMessage("nullValue.FileStoreIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
this.dataFileStore = fileStore;
}
/**
* Downloads the data file for the layer.
*
* @param layer
* @param postProcessor
*/
protected void downloadFile(L layer, RetrievalPostProcessor postProcessor)
{
if (!layer.isNetworkRetrievalEnabled())
return;
if (!WorldWind.getRetrievalService().isAvailable())
return;
URL url;
try
{
url = layer.getUrl();
if (url == null)
return;
if (WorldWind.getNetworkStatus().isHostUnavailable(url))
return;
}
catch (MalformedURLException e)
{
String message = "Exception creating data URL";
Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
return;
}
Retriever retriever;
if ("http".equalsIgnoreCase(url.getProtocol()) || "https".equalsIgnoreCase(url.getProtocol()))
{
if (postProcessor == null)
postProcessor = new DownloadPostProcessor(this, layer);
retriever = new PassThroughZipRetriever(url, postProcessor);
}
else
{
Logging.logger().severe(Logging.getMessage("layers.TextureLayer.UnknownRetrievalProtocol", url.toString()));
return;
}
// Apply any overridden timeouts.
Integer cto = AVListImpl.getIntegerValue(layer, AVKey.URL_CONNECT_TIMEOUT);
if (cto != null && cto > 0)
retriever.setConnectTimeout(cto);
Integer cro = AVListImpl.getIntegerValue(layer, AVKey.URL_READ_TIMEOUT);
if (cro != null && cro > 0)
retriever.setReadTimeout(cro);
Integer srl = AVListImpl.getIntegerValue(layer, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT);
if (srl != null && srl > 0)
retriever.setStaleRequestLimit(srl);
WorldWind.getRetrievalService().runRetriever(retriever);
}
/**
* Load the data from the file pointed to by the url. Delegates the loading
* to the subclass.
*
* @param url
* @param layer
* @return True if the data was loaded successfully
*/
protected boolean loadData(URL url, L layer)
{
synchronized (readLock)
{
reading = true;
}
//this is potentially a long operation
synchronized (getFileLock())
{
if (!loaded)
{
loaded = doLoadData(url, layer);
}
}
synchronized (readLock)
{
loading = false;
loadingListeners.notifyListeners(this, isLoading());
reading = false;
}
return loaded;
}
@Override
public boolean isLoading()
{
return loading;
}
@Override
public void addLoadingListener(LoadingListener listener)
{
loadingListeners.add(listener);
}
@Override
public void removeLoadingListener(LoadingListener listener)
{
loadingListeners.remove(listener);
}
/**
* Load the data from the file pointed to by the url.
*
* @param url
* @param layer
* @return True if the data was loaded successfully
*/
protected abstract boolean doLoadData(URL url, L layer);
/**
* {@link RetrievalPostProcessor} used when downloading the data.
*/
protected class DownloadPostProcessor extends AbstractRetrievalPostProcessor
{
protected final AbstractDataProvider<L> provider;
protected final L layer;
public DownloadPostProcessor(AbstractDataProvider<L> provider, L layer)
{
this.provider = provider;
this.layer = layer;
}
@Override
protected Object getFileLock()
{
return provider.getFileLock();
}
@Override
protected File doGetOutputFile()
{
return provider.getDataFileStore().newFile(layer.getDataCacheName());
}
}
/**
* Task which downloads and/or loads the data.
*/
private class RequestTask implements Runnable
{
private final AbstractDataProvider<L> provider;
private final L layer;
private RequestTask(AbstractDataProvider<L> provider, L layer)
{
this.provider = provider;
this.layer = layer;
}
@Override
public void run()
{
String dataCacheName = layer.getDataCacheName();
//first check if the layer URL is pointing to a local file (has file:// protocol)
URL fileUrl = null;
try
{
URL url = layer.getUrl();
if ("file".equalsIgnoreCase(url.getProtocol()))
{
fileUrl = url;
}
}
catch (MalformedURLException e)
{
}
if (fileUrl != null)
{
//if the layer url is a local file, load the data straight away
if (provider.loadData(fileUrl, layer))
{
layer.firePropertyChange(AVKey.LAYER, null, this);
return;
}
}
else
{
//otherwise, check the cache for the downloaded data, and load or download
URL url = provider.getDataFileStore().findFile(dataCacheName, false);
if (url != null && !this.provider.isFileExpired(layer, url, provider.getDataFileStore()))
{
if (provider.loadData(url, layer))
{
layer.firePropertyChange(AVKey.LAYER, null, this);
return;
}
else
{
// Assume that something's wrong with the file and delete it.
provider.getDataFileStore().removeFile(url);
String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
Logging.logger().info(message);
}
}
provider.downloadFile(layer, null);
}
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
@SuppressWarnings("unchecked")
final RequestTask that = (RequestTask) o;
//assumes each layer only has a single file to request
return !(layer != null ? !layer.equals(that.layer) : that.layer != null);
}
@Override
public int hashCode()
{
return (layer != null ? layer.hashCode() : 0);
}
}
}