/*
* Copyright (C) 2001, 2006 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration. All
* Rights Reserved.
*/
package gov.nasa.worldwind;
import java.util.concurrent.atomic.*;
import java.util.zip.*;
import java.nio.*;
import java.nio.channels.*;
import java.net.*;
import java.io.*;
/**
* @author Tom Gaskins
* @version $Id: URLRetriever.java 2228 2007-07-06 22:18:04Z tgaskins $
*/
public abstract class URLRetriever extends WWObjectImpl implements Retriever
{
private volatile String state = RETRIEVER_STATE_NOT_STARTED;
private volatile int contentLength = 0;
private AtomicInteger contentLengthRead = new AtomicInteger(0);
private volatile String contentType;
private volatile ByteBuffer byteBuffer;
private volatile URLConnection connection;
private final URL url;
private final RetrievalPostProcessor postProcessor;
private int connectTimeout = Configuration.getIntegerValue(
AVKey.URL_CONNECT_TIMEOUT,
8000);
private int readTimeout = Configuration.getIntegerValue(
AVKey.URL_READ_TIMEOUT,
5000);
private String URLProxyHost = Configuration.getStringValue(AVKey.URL_PROXY_HOST);
private String URLProxyType = Configuration.getStringValue(AVKey.URL_PROXY_TYPE);
private int URLProxyPort = Configuration.getIntegerValue(AVKey.URL_PROXY_PORT);
private int staleRequestLimit = -1;
private long submitTime;
private long beginTime;
private long endTime;
/**
* @param url
* @param postProcessor
* @throws IllegalArgumentException if url
or
* postProcessor
is null
*/
public URLRetriever(URL url, RetrievalPostProcessor postProcessor)
{
if (url == null)
{
String message = WorldWind.retrieveErrMsg("nullValue.URLIsNull");
WorldWind.logger().log(java.util.logging.Level.FINE, message);
throw new IllegalArgumentException(message);
}
if (postProcessor == null)
{
String message = WorldWind
.retrieveErrMsg("nullValue.PostProcessorIsNull");
WorldWind.logger().log(java.util.logging.Level.FINE, message);
throw new IllegalArgumentException(message);
}
this.url = url;
this.postProcessor = postProcessor;
}
public final URL getUrl()
{
return url;
}
public final int getContentLength()
{
return this.contentLength;
}
protected void setContentLengthRead(int length)
{
this.contentLengthRead.set(length);
}
public final int getContentLengthRead()
{
return this.contentLengthRead.get();
}
public final String getContentType()
{
return this.contentType;
}
public final ByteBuffer getBuffer()
{
return this.byteBuffer;
}
public final String getName()
{
return this.url.toString();
}
public final String getState()
{
return this.state;
}
protected final URLConnection getConnection()
{
return this.connection;
}
public final RetrievalPostProcessor getPostProcessor()
{
return postProcessor;
}
public final int getConnectTimeout()
{
return connectTimeout;
}
public int getReadTimeout()
{
return readTimeout;
}
public void setReadTimeout(int readTimeout)
{
this.readTimeout = readTimeout;
}
public int getStaleRequestLimit()
{
return staleRequestLimit;
}
public void setStaleRequestLimit(int staleRequestLimit)
{
this.staleRequestLimit = staleRequestLimit;
}
public final void setConnectTimeout(int connectTimeout)
{
this.connectTimeout = connectTimeout;
}
public long getSubmitTime()
{
return submitTime;
}
public void setSubmitTime(long submitTime)
{
this.submitTime = submitTime;
}
public long getBeginTime()
{
return beginTime;
}
public void setBeginTime(long beginTime)
{
this.beginTime = beginTime;
}
public long getEndTime()
{
return endTime;
}
public void setEndTime(long endTime)
{
this.endTime = endTime;
}
public final Retriever call() throws Exception
{
if (this.interrupted())
return this;
try
{
this.setState(RETRIEVER_STATE_STARTED);
if (!this.interrupted())
{
this.setState(RETRIEVER_STATE_CONNECTING);
this.connection = this.openConnection();
}
if (!this.interrupted())
{
this.setState(RETRIEVER_STATE_READING);
this.byteBuffer = this.read();
}
if (!this.interrupted())
this.setState(RETRIEVER_STATE_SUCCESSFUL);
}
catch (Exception e)
{
this.setState(RETRIEVER_STATE_ERROR);
if (!(e instanceof java.net.SocketTimeoutException))
{
String message = WorldWind
.retrieveErrMsg("URLRetriever.ErrorAttemptingToRetrieve")
+ this.url.toString();
WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
}
throw e;
}
finally
{
this.end();
}
return this;
}
private void setState(String state)
{
String oldState = this.state;
this.state = state;
this.firePropertyChange(AVKey.RETRIEVER_STATE, oldState, this.state);
}
private boolean interrupted()
{
if (Thread.currentThread().isInterrupted())
{
this.setState(RETRIEVER_STATE_INTERRUPTED);
String message = WorldWind
.retrieveErrMsg("URLRetriever.RetrievalInterruptedFor")
+ this.url.toString();
WorldWind.logger().log(java.util.logging.Level.FINER, message);
return true;
}
return false;
}
private URLConnection openConnection() throws IOException
{
try
{
if (URLProxyHost != null)
{
SocketAddress addr = new InetSocketAddress(URLProxyHost, URLProxyPort);
Proxy proxy = null;
if (URLProxyType.equals("Proxy.Type.Http"))
{
proxy = new Proxy(Proxy.Type.HTTP, addr);
}
else if (URLProxyType.equals("Proxy.Type.SOCKS"))
{
proxy = new Proxy(Proxy.Type.SOCKS, addr);
}
this.connection = this.url.openConnection(proxy);
}
else
{
this.connection = this.url.openConnection();
}
}
catch (java.io.IOException e)
{
String message = WorldWind
.retrieveErrMsg("URLRetriever.ErrorOpeningConnection")
+ this.url.toString() + " " + e.getLocalizedMessage();
WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
throw e;
}
if (this.connection == null) // java.net.URL docs imply that this
// won't happen. We check anyway.
{
String message = WorldWind
.retrieveErrMsg("URLRetriever.NullReturnedFromOpenConnection")
+ this.url;
WorldWind.logger().log(java.util.logging.Level.FINE, message);
throw new IllegalStateException(message);
}
this.connection.setConnectTimeout(this.connectTimeout);
this.connection.setReadTimeout(this.readTimeout);
return connection;
}
private void end() throws Exception
{
try
{
if (this.postProcessor != null)
{
this.byteBuffer = this.postProcessor.run(this);
}
}
catch (Exception e)
{
this.setState(RETRIEVER_STATE_ERROR);
String message = WorldWind
.retrieveErrMsg("URLRetriever.ErrorPostProcessing")
+ this.url.toString();
WorldWind.logger().log(
java.util.logging.Level.FINE,
message + " " + e.getLocalizedMessage());
throw e;
}
}
private ByteBuffer read() throws Exception
{
try
{
ByteBuffer buffer = this.doRead(this.connection);
if (buffer == null)
this.contentLength = 0;
return buffer;
}
catch (Exception e)
{
if (!(e instanceof java.net.SocketTimeoutException))
{
String message = WorldWind
.retrieveErrMsg("URLRetriever.ErrorReadingFromConnection")
+ this.url.toString() + e.getLocalizedMessage();
WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
}
throw e;
}
}
/**
* @param connection
* @return
* @throws Exception
* @throws IllegalArgumentException if connection
is null
*/
protected ByteBuffer doRead(URLConnection connection) throws Exception
{
if (connection == null)
{
String msg = WorldWind.retrieveErrMsg("nullValue.ConnectionIsNull");
WorldWind.logger().log(java.util.logging.Level.FINE, msg);
throw new IllegalArgumentException(msg);
}
this.contentLength = this.connection.getContentLength();
ByteBuffer buffer;
InputStream inputStream = null;
try
{
inputStream = this.connection.getInputStream();
if (inputStream == null)
{
WorldWind
.logger()
.log(
java.util.logging.Level.FINE,
WorldWind
.retrieveErrMsg("URLRetriever.InputStreamFromConnectionNull")
+ connection.getURL());
return null;
}
// TODO: Make decompression of zip file configurable
// TODO: Make this more generally configurable based on content type
// todo: make a zip reader that handles streams of unknown length
// TODO: add a gzip reader
// TODO: this code is checking content type for compression when it
// should be checking content encoding,
// but the WW servers are sending application/zip as the content type,
// and null for the content encoding.
this.contentType = connection.getContentType();
if (this.contentType != null
&& this.contentType.equalsIgnoreCase("application/zip"))
buffer = this.readZipStream(inputStream, connection); // assume
// single file
// in zip and
// decompress
// it
else
buffer = this.readNonSpecificStream(inputStream, connection);
this.contentType = connection.getContentType();
}
finally
{
if (inputStream != null)
try
{
inputStream.close();
}
catch (IOException e)
{
WorldWind
.logger()
.log(
java.util.logging.Level.FINE,
WorldWind
.retrieveErrMsg("URLRetriever.ExceptionClosingInputStreamToConnection")
+ connection.getURL());
}
}
return buffer;
}
private ByteBuffer readNonSpecificStream(InputStream inputStream,
URLConnection connection)
throws IOException
{
if (inputStream == null)
{
String message = WorldWind
.retrieveErrMsg("URLRetriever.InputStreamNullFor")
+ connection.getURL();
WorldWind.logger().log(java.util.logging.Level.FINE, message);
throw new IllegalArgumentException(message);
}
if (this.contentLength < 1)
{
return readNonSpecificStreamUnknownLength(inputStream);
}
ReadableByteChannel channel = Channels.newChannel(inputStream);
ByteBuffer buffer = ByteBuffer.allocate(this.contentLength);
int numBytesRead = 0;
while (!this.interrupted() && numBytesRead >= 0
&& numBytesRead < buffer.limit())
{
int count = channel.read(buffer);
if (count > 0)
this.contentLengthRead.getAndAdd(numBytesRead += count);
}
if (buffer != null)
buffer.flip();
return buffer;
}
private ByteBuffer readNonSpecificStreamUnknownLength(InputStream inputStream)
throws IOException
{
final int PAGE_SIZE = 4096;
ReadableByteChannel channel = Channels.newChannel(inputStream);
ByteBuffer buffer = ByteBuffer.allocate(PAGE_SIZE);
int count = 0;
int numBytesRead = 0;
while (!this.interrupted() && count >= 0)
{
count = channel.read(buffer);
if (count > 0)
this.contentLengthRead.getAndAdd(numBytesRead += count);
if (count > 0 && !buffer.hasRemaining())
{
ByteBuffer biggerBuffer = ByteBuffer.allocate(buffer.limit()
+ PAGE_SIZE);
biggerBuffer.put((ByteBuffer)buffer.rewind());
buffer = biggerBuffer;
}
}
if (buffer != null)
buffer.flip();
return buffer;
}
/**
* @param inputStream
* @param connection
* @return
* @throws java.io.IOException
* @throws IllegalArgumentException if inputStream
is null
*/
private ByteBuffer readZipStream(InputStream inputStream,
URLConnection connection)
throws IOException
{
ZipInputStream zis = new ZipInputStream(inputStream);
ZipEntry ze = zis.getNextEntry();
if (ze == null)
{
WorldWind.logger().log(
java.util.logging.Level.FINE,
WorldWind.retrieveErrMsg("URLRetriever.NoZipEntryFor")
+ connection.getURL());
return null;
}
ByteBuffer buffer = null;
if (ze.getSize() > 0)
{
buffer = ByteBuffer.allocate((int)ze.getSize());
byte[] inputBuffer = new byte[8192];
while (buffer.hasRemaining())
{
int count = zis.read(inputBuffer);
if (count > 0)
{
buffer.put(inputBuffer, 0, count);
this.contentLengthRead.getAndAdd(buffer.position() + 1);
}
}
}
if (buffer != null)
buffer.flip();
return buffer;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final URLRetriever that = (URLRetriever)o;
// Retrievers are considered identical if they are for the same URL. This
// convention is used by the
// retrieval service to filter out duplicate retreival requests.
return !(url != null
? !url.toString().contentEquals(that.url.toString())
: that.url != null);
}
@Override
public int hashCode()
{
int result;
result = (url != null ? url.hashCode() : 0);
return result;
}
@Override
public String toString()
{
return this.getName() != null ? this.getName() : super.toString();
}
}