Worldwind public release 0.2.1
[worldwind-tracker.git] / gov / nasa / worldwind / URLRetriever.java
blob2975e3e21da8a20ad4cfd47bd8b698671026e565
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
7 package gov.nasa.worldwind;
9 import java.util.concurrent.atomic.*;
10 import java.util.zip.*;
11 import java.nio.*;
12 import java.nio.channels.*;
13 import java.net.*;
14 import java.io.*;
16 /**
17 * @author Tom Gaskins
18 * @version $Id: URLRetriever.java 2228 2007-07-06 22:18:04Z tgaskins $
20 public abstract class URLRetriever extends WWObjectImpl implements Retriever
22 private volatile String state = RETRIEVER_STATE_NOT_STARTED;
23 private volatile int contentLength = 0;
24 private AtomicInteger contentLengthRead = new AtomicInteger(0);
25 private volatile String contentType;
26 private volatile ByteBuffer byteBuffer;
27 private volatile URLConnection connection;
28 private final URL url;
29 private final RetrievalPostProcessor postProcessor;
30 private int connectTimeout = Configuration.getIntegerValue(AVKey.URL_CONNECT_TIMEOUT, 8000);
31 private int readTimeout = Configuration.getIntegerValue(AVKey.URL_READ_TIMEOUT, 5000);
32 private int staleRequestLimit = -1;
33 private long submitTime;
34 private long beginTime;
35 private long endTime;
37 /**
38 * @param url
39 * @param postProcessor
40 * @throws IllegalArgumentException if <code>url</code> or <code>postProcessor</code> is null
42 public URLRetriever(URL url, RetrievalPostProcessor postProcessor)
44 if (url == null)
46 String message = WorldWind.retrieveErrMsg("nullValue.URLIsNull");
47 WorldWind.logger().log(java.util.logging.Level.FINE, message);
48 throw new IllegalArgumentException(message);
50 if (postProcessor == null)
52 String message = WorldWind.retrieveErrMsg("nullValue.PostProcessorIsNull");
53 WorldWind.logger().log(java.util.logging.Level.FINE, message);
54 throw new IllegalArgumentException(message);
57 this.url = url;
58 this.postProcessor = postProcessor;
61 public final URL getUrl()
63 return url;
66 public final int getContentLength()
68 return this.contentLength;
71 protected void setContentLengthRead(int length)
73 this.contentLengthRead.set(length);
76 public final int getContentLengthRead()
78 return this.contentLengthRead.get();
81 public final String getContentType()
83 return this.contentType;
86 public final ByteBuffer getBuffer()
88 return this.byteBuffer;
91 public final String getName()
93 return this.url.toString();
96 public final String getState()
98 return this.state;
101 protected final URLConnection getConnection()
103 return this.connection;
106 public final RetrievalPostProcessor getPostProcessor()
108 return postProcessor;
111 public final int getConnectTimeout()
113 return connectTimeout;
116 public int getReadTimeout()
118 return readTimeout;
121 public void setReadTimeout(int readTimeout)
123 this.readTimeout = readTimeout;
126 public int getStaleRequestLimit()
128 return staleRequestLimit;
131 public void setStaleRequestLimit(int staleRequestLimit)
133 this.staleRequestLimit = staleRequestLimit;
136 public final void setConnectTimeout(int connectTimeout)
138 this.connectTimeout = connectTimeout;
141 public long getSubmitTime()
143 return submitTime;
146 public void setSubmitTime(long submitTime)
148 this.submitTime = submitTime;
151 public long getBeginTime()
153 return beginTime;
156 public void setBeginTime(long beginTime)
158 this.beginTime = beginTime;
161 public long getEndTime()
163 return endTime;
166 public void setEndTime(long endTime)
168 this.endTime = endTime;
171 public final Retriever call() throws Exception
173 if (this.interrupted())
174 return this;
178 this.setState(RETRIEVER_STATE_STARTED);
180 if (!this.interrupted())
182 this.setState(RETRIEVER_STATE_CONNECTING);
183 this.connection = this.openConnection();
186 if (!this.interrupted())
188 this.setState(RETRIEVER_STATE_READING);
189 this.byteBuffer = this.read();
192 if (!this.interrupted())
193 this.setState(RETRIEVER_STATE_SUCCESSFUL);
195 catch (Exception e)
197 this.setState(RETRIEVER_STATE_ERROR);
198 if (!(e instanceof java.net.SocketTimeoutException))
200 String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorAttemptingToRetrieve")
201 + this.url.toString();
202 WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
204 throw e;
206 finally
208 this.end();
211 return this;
214 private void setState(String state)
216 String oldState = this.state;
217 this.state = state;
218 this.firePropertyChange(AVKey.RETRIEVER_STATE, oldState, this.state);
221 private boolean interrupted()
223 if (Thread.currentThread().isInterrupted())
225 this.setState(RETRIEVER_STATE_INTERRUPTED);
226 String message = WorldWind.retrieveErrMsg("URLRetriever.RetrievalInterruptedFor") + this.url.toString();
227 WorldWind.logger().log(java.util.logging.Level.FINER, message);
228 return true;
230 return false;
233 private URLConnection openConnection() throws IOException
237 this.connection = this.url.openConnection();
239 catch (java.io.IOException e)
241 String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorOpeningConnection") + this.url.toString()
242 + " " + e.getLocalizedMessage();
243 WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
244 throw e;
247 if (this.connection == null) // java.net.URL docs imply that this won't happen. We check anyway.
249 String message = WorldWind.retrieveErrMsg("URLRetriever.NullReturnedFromOpenConnection") + this.url;
250 WorldWind.logger().log(java.util.logging.Level.FINE, message);
251 throw new IllegalStateException(message);
254 this.connection.setConnectTimeout(this.connectTimeout);
255 this.connection.setReadTimeout(this.readTimeout);
257 return connection;
260 private void end() throws Exception
264 if (this.postProcessor != null)
266 this.byteBuffer = this.postProcessor.run(this);
269 catch (Exception e)
271 this.setState(RETRIEVER_STATE_ERROR);
272 String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorPostProcessing") + this.url.toString();
273 WorldWind.logger().log(java.util.logging.Level.FINE, message + " " + e.getLocalizedMessage());
274 throw e;
278 private ByteBuffer read() throws Exception
282 ByteBuffer buffer = this.doRead(this.connection);
283 if (buffer == null)
284 this.contentLength = 0;
285 return buffer;
287 catch (Exception e)
289 if (!(e instanceof java.net.SocketTimeoutException))
291 String message = WorldWind.retrieveErrMsg("URLRetriever.ErrorReadingFromConnection")
292 + this.url.toString() + e.getLocalizedMessage();
293 WorldWind.logger().log(java.util.logging.Level.FINE, message, e);
295 throw e;
300 * @param connection
301 * @return
302 * @throws Exception
303 * @throws IllegalArgumentException if <code>connection</code> is null
305 protected ByteBuffer doRead(URLConnection connection) throws Exception
307 if (connection == null)
309 String msg = WorldWind.retrieveErrMsg("nullValue.ConnectionIsNull");
310 WorldWind.logger().log(java.util.logging.Level.FINE, msg);
311 throw new IllegalArgumentException(msg);
314 this.contentLength = this.connection.getContentLength();
316 ByteBuffer buffer;
317 InputStream inputStream = null;
320 inputStream = this.connection.getInputStream();
321 if (inputStream == null)
323 WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg(
324 "URLRetriever.InputStreamFromConnectionNull") + connection.getURL());
325 return null;
328 // TODO: Make decompression of zip file configurable
329 // TODO: Make this more generally configurable based on content type
330 // todo: make a zip reader that handles streams of unknown length
331 // TODO: add a gzip reader
332 // TODO: this code is checking content type for compression when it should be checking content encoding,
333 // but the WW servers are sending application/zip as the content type, and null for the content encoding.
334 this.contentType = connection.getContentType();
335 if (this.contentType != null && this.contentType.equalsIgnoreCase("application/zip"))
336 buffer = this.readZipStream(inputStream, connection); // assume single file in zip and decompress it
337 else
338 buffer = this.readNonSpecificStream(inputStream, connection);
340 this.contentType = connection.getContentType();
342 finally
344 if (inputStream != null)
347 inputStream.close();
349 catch (IOException e)
351 WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg(
352 "URLRetriever.ExceptionClosingInputStreamToConnection") + connection.getURL());
356 return buffer;
359 private ByteBuffer readNonSpecificStream(InputStream inputStream, URLConnection connection) throws IOException
361 if (inputStream == null)
363 String message = WorldWind.retrieveErrMsg("URLRetriever.InputStreamNullFor") + connection.getURL();
364 WorldWind.logger().log(java.util.logging.Level.FINE, message);
365 throw new IllegalArgumentException(message);
368 if (this.contentLength < 1)
370 return readNonSpecificStreamUnknownLength(inputStream);
373 ReadableByteChannel channel = Channels.newChannel(inputStream);
374 ByteBuffer buffer = ByteBuffer.allocate(this.contentLength);
376 int numBytesRead = 0;
377 while (!this.interrupted() && numBytesRead >= 0 && numBytesRead < buffer.limit())
379 int count = channel.read(buffer);
380 if (count > 0)
381 this.contentLengthRead.getAndAdd(numBytesRead += count);
384 if (buffer != null)
385 buffer.flip();
387 return buffer;
390 private ByteBuffer readNonSpecificStreamUnknownLength(InputStream inputStream) throws IOException
392 final int PAGE_SIZE = 4096;
394 ReadableByteChannel channel = Channels.newChannel(inputStream);
395 ByteBuffer buffer = ByteBuffer.allocate(PAGE_SIZE);
397 int count = 0;
398 int numBytesRead = 0;
399 while (!this.interrupted() && count >= 0)
401 count = channel.read(buffer);
402 if (count > 0)
403 this.contentLengthRead.getAndAdd(numBytesRead += count);
405 if (count > 0 && !buffer.hasRemaining())
407 ByteBuffer biggerBuffer = ByteBuffer.allocate(buffer.limit() + PAGE_SIZE);
408 biggerBuffer.put((ByteBuffer) buffer.rewind());
409 buffer = biggerBuffer;
413 if (buffer != null)
414 buffer.flip();
416 return buffer;
420 * @param inputStream
421 * @param connection
422 * @return
423 * @throws java.io.IOException
424 * @throws IllegalArgumentException if <code>inputStream</code> is null
426 private ByteBuffer readZipStream(InputStream inputStream, URLConnection connection) throws IOException
428 ZipInputStream zis = new ZipInputStream(inputStream);
429 ZipEntry ze = zis.getNextEntry();
430 if (ze == null)
432 WorldWind.logger().log(java.util.logging.Level.FINE, WorldWind.retrieveErrMsg("URLRetriever.NoZipEntryFor")
433 + connection.getURL());
434 return null;
437 ByteBuffer buffer = null;
438 if (ze.getSize() > 0)
440 buffer = ByteBuffer.allocate((int) ze.getSize());
442 byte[] inputBuffer = new byte[8192];
443 while (buffer.hasRemaining())
445 int count = zis.read(inputBuffer);
446 if (count > 0)
448 buffer.put(inputBuffer, 0, count);
449 this.contentLengthRead.getAndAdd(buffer.position() + 1);
453 if (buffer != null)
454 buffer.flip();
456 return buffer;
459 @Override
460 public boolean equals(Object o)
462 if (this == o)
463 return true;
464 if (o == null || getClass() != o.getClass())
465 return false;
467 final URLRetriever that = (URLRetriever) o;
469 // Retrievers are considered identical if they are for the same URL. This convention is used by the
470 // retrieval service to filter out duplicate retreival requests.
471 return !(url != null ? !url.toString().contentEquals(that.url.toString()) : that.url != null);
474 @Override
475 public int hashCode()
477 int result;
478 result = (url != null ? url.hashCode() : 0);
479 return result;
482 @Override
483 public String toString()
485 return this.getName() != null ? this.getName() : super.toString();