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 1792 2007-05-08 21:28:37Z 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 long submitTime
;
33 private long beginTime
;
38 * @param postProcessor
39 * @throws IllegalArgumentException if <code>url</code> or <code>postProcessor</code> is null
41 public URLRetriever(URL url
, RetrievalPostProcessor postProcessor
)
45 String message
= WorldWind
.retrieveErrMsg("nullValue.URLIsNull");
46 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
);
47 throw new IllegalArgumentException(message
);
49 if (postProcessor
== null)
51 String message
= WorldWind
.retrieveErrMsg("nullValue.PostProcessorIsNull");
52 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
);
53 throw new IllegalArgumentException(message
);
57 this.postProcessor
= postProcessor
;
60 public final URL
getUrl()
65 public final int getContentLength()
67 return this.contentLength
;
70 protected void setContentLengthRead(int length
)
72 this.contentLengthRead
.set(length
);
75 public final int getContentLengthRead()
77 return this.contentLengthRead
.get();
80 public final String
getContentType()
82 return this.contentType
;
85 public final ByteBuffer
getBuffer()
87 return this.byteBuffer
;
90 public final String
getName()
92 return this.url
.toString();
95 public final String
getState()
100 protected final URLConnection
getConnection()
102 return this.connection
;
105 public final RetrievalPostProcessor
getPostProcessor()
107 return postProcessor
;
110 public final int getConnectTimeout()
112 return connectTimeout
;
116 * @param connectTimeout
117 * @throws IllegalArgumentException if <code>connectTimeOut</code> is less than zero
119 public final void setConnectTimeout(int connectTimeout
)
121 if (connectTimeout
< 0)
123 String message
= WorldWind
.retrieveErrMsg("URLRetriever.ConnectTimeoutLessThanZero");
124 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
);
125 throw new IllegalArgumentException(message
);
128 this.connectTimeout
= connectTimeout
;
131 public long getSubmitTime()
136 public void setSubmitTime(long submitTime
)
138 this.submitTime
= submitTime
;
141 public long getBeginTime()
146 public void setBeginTime(long beginTime
)
148 this.beginTime
= beginTime
;
151 public long getEndTime()
156 public void setEndTime(long endTime
)
158 this.endTime
= endTime
;
161 public final Retriever
call() throws Exception
163 if (this.interrupted())
168 this.setState(RETRIEVER_STATE_STARTED
);
170 if (!this.interrupted())
172 this.setState(RETRIEVER_STATE_CONNECTING
);
173 this.connection
= this.openConnection();
176 if (!this.interrupted())
178 this.setState(RETRIEVER_STATE_READING
);
179 this.byteBuffer
= this.read();
182 if (!this.interrupted())
183 this.setState(RETRIEVER_STATE_SUCCESSFUL
);
187 this.setState(RETRIEVER_STATE_ERROR
);
188 if (!(e
instanceof java
.net
.SocketTimeoutException
))
190 String message
= WorldWind
.retrieveErrMsg("URLRetriever.ErrorAttemptingToRetrieve")
191 + this.url
.toString();
192 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
, e
);
204 private void setState(String state
)
206 String oldState
= this.state
;
208 this.firePropertyChange(AVKey
.RETRIEVER_STATE
, oldState
, this.state
);
211 private boolean interrupted()
213 if (Thread
.currentThread().isInterrupted())
215 this.setState(RETRIEVER_STATE_INTERRUPTED
);
216 String message
= WorldWind
.retrieveErrMsg("URLRetriever.RetrievalInterruptedFor") + this.url
.toString();
217 WorldWind
.logger().log(java
.util
.logging
.Level
.FINER
, message
);
223 private URLConnection
openConnection() throws IOException
227 this.connection
= this.url
.openConnection();
229 catch (java
.io
.IOException e
)
231 String message
= WorldWind
.retrieveErrMsg("URLRetriever.ErrorOpeningConnection") + this.url
.toString()
232 + " " + e
.getLocalizedMessage();
233 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
, e
);
237 if (this.connection
== null) // java.net.URL docs imply that this won't happen. We check anyway.
239 String message
= WorldWind
.retrieveErrMsg("URLRetriever.NullReturnedFromOpenConnection") + this.url
;
240 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
);
241 throw new IllegalStateException(message
);
244 this.connection
.setConnectTimeout(this.connectTimeout
);
245 this.connection
.setReadTimeout(this.readTimeout
);
250 private void end() throws Exception
254 if (this.postProcessor
!= null)
256 this.byteBuffer
= this.postProcessor
.run(this);
261 this.setState(RETRIEVER_STATE_ERROR
);
262 String message
= WorldWind
.retrieveErrMsg("URLRetriever.ErrorPostProcessing") + this.url
.toString();
263 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
+ " " + e
.getLocalizedMessage());
268 private ByteBuffer
read() throws Exception
272 ByteBuffer buffer
= this.doRead(this.connection
);
274 this.contentLength
= 0;
279 if (!(e
instanceof java
.net
.SocketTimeoutException
))
281 String message
= WorldWind
.retrieveErrMsg("URLRetriever.ErrorReadingFromConnection")
282 + this.url
.toString() + e
.getLocalizedMessage();
283 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
, e
);
293 * @throws IllegalArgumentException if <code>connection</code> is null
295 protected ByteBuffer
doRead(URLConnection connection
) throws Exception
297 if (connection
== null)
299 String msg
= WorldWind
.retrieveErrMsg("nullValue.ConnectionIsNull");
300 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, msg
);
301 throw new IllegalArgumentException(msg
);
304 this.contentLength
= this.connection
.getContentLength();
307 InputStream inputStream
= null;
310 inputStream
= this.connection
.getInputStream();
311 if (inputStream
== null)
313 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, WorldWind
.retrieveErrMsg(
314 "URLRetriever.InputStreamFromConnectionNull") + connection
.getURL());
317 // TODO: Make decompression of zip file configurable
318 // TODO: Make this more generally configurable based on content type
319 // todo: make a zip reader that handles streams of unknown length
320 // TODO: add a gzip reader
321 // TODO: this code is checking content type for compression when it should be checking content encoding,
322 // but the WW servers are sending application/zip as the content type, and null for the content encoding.
323 if (connection
.getContentType().equalsIgnoreCase("application/zip"))
324 buffer
= this.readZipStream(inputStream
, connection
); // assume single file in zip and decompress it
326 buffer
= this.readNonSpecificStream(inputStream
, connection
);
328 this.contentType
= connection
.getContentType();
332 if (inputStream
!= null)
337 catch (IOException e
)
339 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, WorldWind
.retrieveErrMsg(
340 "URLRetriever.ExceptionClosingInputStreamToConnection") + connection
.getURL());
347 private ByteBuffer
readNonSpecificStream(InputStream inputStream
, URLConnection connection
) throws IOException
349 if (inputStream
== null)
351 String message
= WorldWind
.retrieveErrMsg("URLRetriever.InputStreamNullFor") + connection
.getURL();
352 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, message
);
353 throw new IllegalArgumentException(message
);
356 if (this.contentLength
< 1)
358 return readNonSpecificStreamUnknownLength(inputStream
);
361 ReadableByteChannel channel
= Channels
.newChannel(inputStream
);
362 ByteBuffer buffer
= ByteBuffer
.allocate(this.contentLength
);
364 int numBytesRead
= 0;
365 while (!this.interrupted() && numBytesRead
>= 0 && numBytesRead
< buffer
.limit())
367 int count
= channel
.read(buffer
);
369 this.contentLengthRead
.getAndAdd(numBytesRead
+= count
);
378 private ByteBuffer
readNonSpecificStreamUnknownLength(InputStream inputStream
) throws IOException
380 final int PAGE_SIZE
= 4096;
382 ReadableByteChannel channel
= Channels
.newChannel(inputStream
);
383 ByteBuffer buffer
= ByteBuffer
.allocate(PAGE_SIZE
);
386 int numBytesRead
= 0;
387 while (!this.interrupted() && count
>= 0)
389 count
= channel
.read(buffer
);
391 this.contentLengthRead
.getAndAdd(numBytesRead
+= count
);
393 if (count
> 0 && !buffer
.hasRemaining())
395 ByteBuffer biggerBuffer
= ByteBuffer
.allocate(buffer
.limit() + PAGE_SIZE
);
396 biggerBuffer
.put((ByteBuffer
) buffer
.rewind());
397 buffer
= biggerBuffer
;
411 * @throws java.io.IOException
412 * @throws IllegalArgumentException if <code>inputStream</code> is null
414 private ByteBuffer
readZipStream(InputStream inputStream
, URLConnection connection
) throws IOException
416 ZipInputStream zis
= new ZipInputStream(inputStream
);
417 ZipEntry ze
= zis
.getNextEntry();
420 WorldWind
.logger().log(java
.util
.logging
.Level
.FINE
, WorldWind
.retrieveErrMsg("URLRetriever.NoZipEntryFor")
421 + connection
.getURL());
425 ByteBuffer buffer
= null;
426 if (ze
.getSize() > 0)
428 buffer
= ByteBuffer
.allocate((int) ze
.getSize());
430 byte[] inputBuffer
= new byte[8192];
431 while (buffer
.hasRemaining())
433 int count
= zis
.read(inputBuffer
);
436 buffer
.put(inputBuffer
, 0, count
);
437 this.contentLengthRead
.getAndAdd(buffer
.position() + 1);
448 public boolean equals(Object o
)
452 if (o
== null || getClass() != o
.getClass())
455 final URLRetriever that
= (URLRetriever
) o
;
457 // Retrievers are considered identical if they are for the same URL. This convention is used by the
458 // retrieval service to filter out duplicate retreival requests.
459 return !(url
!= null ?
!url
.equals(that
.url
) : that
.url
!= null);
463 public int hashCode()
466 result
= (url
!= null ? url
.hashCode() : 0);
471 public String
toString()
473 return this.getName() != null ?
this.getName() : super.toString();