2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
7 package gov
.nasa
.worldwind
;
9 import java
.util
.concurrent
.atomic
.*;
10 import java
.util
.zip
.*;
12 import java
.nio
.channels
.*;
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
;
39 * @param postProcessor
40 * @throws IllegalArgumentException if <code>url</code> or <code>postProcessor</code> is null
42 public URLRetriever(URL url
, RetrievalPostProcessor postProcessor
)
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
);
58 this.postProcessor
= postProcessor
;
61 public final URL
getUrl()
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()
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()
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()
146 public void setSubmitTime(long submitTime
)
148 this.submitTime
= submitTime
;
151 public long getBeginTime()
156 public void setBeginTime(long beginTime
)
158 this.beginTime
= beginTime
;
161 public long getEndTime()
166 public void setEndTime(long endTime
)
168 this.endTime
= endTime
;
171 public final Retriever
call() throws Exception
173 if (this.interrupted())
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
);
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
);
214 private void setState(String state
)
216 String oldState
= this.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
);
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
);
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
);
260 private void end() throws Exception
264 if (this.postProcessor
!= null)
266 this.byteBuffer
= this.postProcessor
.run(this);
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());
278 private ByteBuffer
read() throws Exception
282 ByteBuffer buffer
= this.doRead(this.connection
);
284 this.contentLength
= 0;
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
);
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();
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());
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
338 buffer
= this.readNonSpecificStream(inputStream
, connection
);
340 this.contentType
= connection
.getContentType();
344 if (inputStream
!= null)
349 catch (IOException e
)
351 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, WorldWind
.retrieveErrMsg(
352 "URLRetriever.ExceptionClosingInputStreamToConnection") + connection
.getURL());
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
);
381 this.contentLengthRead
.getAndAdd(numBytesRead
+= count
);
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
);
398 int numBytesRead
= 0;
399 while (!this.interrupted() && count
>= 0)
401 count
= channel
.read(buffer
);
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
;
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();
432 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, WorldWind
.retrieveErrMsg("URLRetriever.NoZipEntryFor")
433 + connection
.getURL());
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
);
448 buffer
.put(inputBuffer
, 0, count
);
449 this.contentLengthRead
.getAndAdd(buffer
.position() + 1);
460 public boolean equals(Object o
)
464 if (o
== null || getClass() != o
.getClass())
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);
475 public int hashCode()
478 result
= (url
!= null ? url
.hashCode() : 0);
483 public String
toString()
485 return this.getName() != null ?
this.getName() : super.toString();