/*******************************************************************************
* 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.core.retrieve;
import java.io.Closeable;
import java.net.URL;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import au.gov.ga.earthsci.common.collection.HashSetAndArray;
import au.gov.ga.earthsci.common.collection.SetAndArray;
import au.gov.ga.earthsci.common.util.AbstractPropertyChangeBean;
/**
* Basic {@link IRetrieval} implementation.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class Retrieval extends AbstractPropertyChangeBean implements IRetrieval, IRetrieverMonitor
{
private final SetAndArray<Object> callers = new HashSetAndArray<Object>();
private final URL url;
private final IRetrievalProperties retrievalProperties;
private final IRetriever retriever;
private RetrievalStatus status = RetrievalStatus.NOT_STARTED;
private long position = 0;
private long length = UNKNOWN_LENGTH;
private final CompoundRetrievalListener listeners = new CompoundRetrievalListener();
private final Object listenersMutex = new Object();
private final Object jobSemaphore = new Object();
private RetrievalJob job;
private boolean canceled = false;
private boolean paused = false;
private final Object pausedSemaphore = new Object();
private IRetrievalData cachedData;
private IRetrievalResult result;
public Retrieval(Object caller, URL url, IRetrievalProperties retrievalProperties, IRetriever retriever)
{
addCaller(caller);
this.url = url;
this.retrievalProperties = retrievalProperties;
this.retriever = retriever;
}
void addCaller(Object caller)
{
synchronized (callers)
{
callers.add(caller);
listeners.callersChanged(this);
}
}
RetrieverResult retrieve(IRetrieverMonitor monitor) throws Exception
{
if (retrievalProperties.isUseCache())
{
IRetrievalData cachedData = retriever.checkCache(url);
synchronized (listenersMutex)
{
this.cachedData = cachedData;
if (cachedData != null)
{
listeners.cached(this);
}
}
}
return retriever.retrieve(url, monitor, retrievalProperties, cachedData);
}
@Override
public Object[] getCallers()
{
synchronized (callers)
{
return callers.getArray(Object.class);
}
}
@Override
public URL getURL()
{
return url;
}
@Override
public RetrievalStatus getStatus()
{
return status;
}
@Override
public long getPosition()
{
return position;
}
@Override
public long getLength()
{
return length;
}
@Override
public float getPercentage()
{
return length == 0 ? 1 : length < 0 ? -1 : Math.max(0, Math.min(1, (float) (position / (double) length)));
}
@Override
public void addListener(IRetrievalListener listener)
{
synchronized (listenersMutex)
{
listeners.addListener(listener);
if (result != null)
{
listener.complete(this);
}
else if (cachedData != null)
{
listener.cached(this);
}
if (isPaused())
{
listener.paused(this);
}
}
}
@Override
public void removeListener(IRetrievalListener listener)
{
listeners.removeListener(listener);
}
@Override
public void start()
{
synchronized (jobSemaphore)
{
if (job == null)
{
job = new RetrievalJob(this);
setPaused(false);
setCanceled(false);
job.addJobChangeListener(new JobChangeAdapter()
{
@Override
public void done(IJobChangeEvent event)
{
RetrieverResult rr = job.getRetrievalResult();
//ensure the retriever's paused/canceled state matches the result:
boolean wasPaused = rr == null ? false : rr.status == RetrieverResultStatus.PAUSED;
boolean wasCanceled = rr == null ? true : rr.status == RetrieverResultStatus.CANCELED;
setPaused(wasPaused);
setCanceled(wasCanceled);
synchronized (jobSemaphore)
{
job.removeJobChangeListener(this);
job = null;
}
synchronized (listenersMutex)
{
result = rr == null ? null : rr.result;
if (wasPaused)
{
listeners.paused(Retrieval.this);
}
else
{
listeners.complete(Retrieval.this);
}
}
}
});
try
{
job.schedule();
}
catch (IllegalStateException e)
{
//job manager shutdown, ignore
}
}
}
}
@Override
public void pause()
{
synchronized (jobSemaphore)
{
//can only pause a currently running job
if (job != null)
{
setPaused(true);
}
}
}
@Override
public boolean isPaused()
{
return paused;
}
void setPaused(boolean paused)
{
synchronized (pausedSemaphore)
{
this.paused = paused;
if (!paused)
{
pausedSemaphore.notifyAll();
}
}
}
@Override
public void cancel()
{
synchronized (jobSemaphore)
{
//can only cancel a currently running job
if (job != null)
{
setCanceled(true);
job.cancel();
}
}
}
@Override
public boolean isCanceled()
{
return canceled;
}
void setCanceled(boolean canceled)
{
this.canceled = canceled;
}
@Override
public IRetrievalData getCachedData()
{
return cachedData;
}
@Override
public IRetrievalResult getResult()
{
return result;
}
@Override
public boolean hasResult()
{
return getResult() != null;
}
@Override
public IRetrievalData getData()
{
synchronized (jobSemaphore)
{
if (result != null && result.getData() != null)
{
return result.getData();
}
return cachedData;
}
}
@Override
public IRetrievalResult waitAndGetResult() throws InterruptedException
{
while (checkAndWaitIfPaused() || joinJob())
{
}
return getResult();
}
private boolean joinJob() throws InterruptedException
{
RetrievalJob job;
synchronized (jobSemaphore)
{
job = this.job;
}
if (job != null)
{
job.join();
return true;
}
return false;
}
private boolean checkAndWaitIfPaused() throws InterruptedException
{
synchronized (pausedSemaphore)
{
if (isPaused())
{
pausedSemaphore.wait();
return true;
}
}
return false;
}
@Override
public void updateStatus(RetrievalStatus status)
{
firePropertyChange("status", getStatus(), this.status = status); //$NON-NLS-1$
listeners.statusChanged(this);
}
@Override
public void progress(long amount)
{
setPosition(getPosition() + amount);
}
@Override
public void setPosition(long position)
{
firePropertyChange("position", getPosition(), this.position = position); //$NON-NLS-1$
listeners.progress(this);
}
@Override
public void setLength(long length)
{
firePropertyChange("length", getLength(), this.length = length); //$NON-NLS-1$
}
@Override
public void setCloseable(Closeable closeable)
{
}
}