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
.retrieve
;
9 import gov
.nasa
.worldwind
.*;
10 import gov
.nasa
.worldwind
.avlist
.AVKey
;
11 import gov
.nasa
.worldwind
.util
.*;
15 import java
.nio
.ByteBuffer
;
16 import java
.nio
.channels
.*;
17 import java
.util
.concurrent
.atomic
.AtomicInteger
;
18 import java
.util
.logging
.Level
;
19 import java
.util
.zip
.*;
23 * @version $Id: URLRetriever.java 3558 2007-11-17 08:36:45Z tgaskins $
25 public abstract class URLRetriever
extends WWObjectImpl
implements Retriever
27 private volatile String state
= RETRIEVER_STATE_NOT_STARTED
;
28 private volatile int contentLength
= 0;
29 private AtomicInteger contentLengthRead
= new AtomicInteger(0);
30 private volatile String contentType
;
31 private volatile ByteBuffer byteBuffer
;
32 private volatile URLConnection connection
;
33 private final URL url
;
34 private final RetrievalPostProcessor postProcessor
;
35 private int connectTimeout
= Configuration
.getIntegerValue(AVKey
.URL_CONNECT_TIMEOUT
, 8000);
36 private int readTimeout
= Configuration
.getIntegerValue(AVKey
.URL_READ_TIMEOUT
, 5000);
37 private int staleRequestLimit
= -1;
38 private long submitTime
;
39 private long beginTime
;
43 * @param url the URL of the resource to retrieve.
44 * @param postProcessor the retrieval post-processor to invoke when the resource is retrieved.
45 * @throws IllegalArgumentException if <code>url</code> or <code>postProcessor</code> is null.
47 public URLRetriever(URL url
, RetrievalPostProcessor postProcessor
)
51 String message
= Logging
.getMessage("nullValue.URLIsNull");
52 Logging
.logger().severe(message
);
53 throw new IllegalArgumentException(message
);
55 if (postProcessor
== null)
57 String message
= Logging
.getMessage("nullValue.PostProcessorIsNull");
58 Logging
.logger().severe(message
);
59 throw new IllegalArgumentException(message
);
63 this.postProcessor
= postProcessor
;
66 public final URL
getUrl()
71 public final int getContentLength()
73 return this.contentLength
;
76 protected void setContentLengthRead(int length
)
78 this.contentLengthRead
.set(length
);
81 public final int getContentLengthRead()
83 return this.contentLengthRead
.get();
86 public final String
getContentType()
88 return this.contentType
;
91 public final ByteBuffer
getBuffer()
93 return this.byteBuffer
;
96 public final String
getName()
98 return this.url
.toString();
101 public final String
getState()
106 protected final URLConnection
getConnection()
108 return this.connection
;
111 public final RetrievalPostProcessor
getPostProcessor()
113 return postProcessor
;
116 public final int getConnectTimeout()
118 return connectTimeout
;
121 public int getReadTimeout()
126 public void setReadTimeout(int readTimeout
)
128 this.readTimeout
= readTimeout
;
131 public int getStaleRequestLimit()
133 return staleRequestLimit
;
136 public void setStaleRequestLimit(int staleRequestLimit
)
138 this.staleRequestLimit
= staleRequestLimit
;
141 public final void setConnectTimeout(int connectTimeout
)
143 this.connectTimeout
= connectTimeout
;
146 public long getSubmitTime()
151 public void setSubmitTime(long submitTime
)
153 this.submitTime
= submitTime
;
156 public long getBeginTime()
161 public void setBeginTime(long beginTime
)
163 this.beginTime
= beginTime
;
166 public long getEndTime()
171 public void setEndTime(long endTime
)
173 this.endTime
= endTime
;
176 public final Retriever
call() throws Exception
178 if (this.interrupted())
183 this.setState(RETRIEVER_STATE_STARTED
);
185 if (!this.interrupted())
187 this.setState(RETRIEVER_STATE_CONNECTING
);
188 this.connection
= this.openConnection();
191 if (!this.interrupted())
193 this.setState(RETRIEVER_STATE_READING
);
194 this.byteBuffer
= this.read();
197 if (!this.interrupted())
198 this.setState(RETRIEVER_STATE_SUCCESSFUL
);
200 WorldWind
.getNetworkStatus().logAvailableHost(this.url
);
202 catch (UnknownHostException e
)
204 this.setState(RETRIEVER_STATE_ERROR
);
205 WorldWind
.getNetworkStatus().logUnavailableHost(this.url
);
208 catch (SocketException e
)
210 this.setState(RETRIEVER_STATE_ERROR
);
211 WorldWind
.getNetworkStatus().logUnavailableHost(this.url
);
216 this.setState(RETRIEVER_STATE_ERROR
);
217 if (!(e
instanceof java
.net
.SocketTimeoutException
))
219 Logging
.logger().log(Level
.SEVERE
,
220 Logging
.getMessage("URLRetriever.ErrorAttemptingToRetrieve", this.url
.toString()), e
);
232 private void setState(String state
)
234 String oldState
= this.state
;
236 this.firePropertyChange(AVKey
.RETRIEVER_STATE
, oldState
, this.state
);
239 private boolean interrupted()
241 if (Thread
.currentThread().isInterrupted())
243 this.setState(RETRIEVER_STATE_INTERRUPTED
);
244 String message
= Logging
.getMessage("URLRetriever.RetrievalInterruptedFor", this.url
.toString());
245 Logging
.logger().fine(message
);
251 private URLConnection
openConnection() throws IOException
255 Proxy proxy
= WWIO
.configureProxy();
257 this.connection
= this.url
.openConnection(proxy
);
259 this.connection
= this.url
.openConnection();
261 catch (java
.io
.IOException e
)
263 Logging
.logger().log(Level
.SEVERE
,
264 Logging
.getMessage("URLRetriever.ErrorOpeningConnection", this.url
.toString()), e
);
268 if (this.connection
== null) // java.net.URL docs imply that this won't happen. We check anyway.
270 String message
= Logging
.getMessage("URLRetriever.NullReturnedFromOpenConnection", this.url
);
271 Logging
.logger().severe(message
);
272 throw new IllegalStateException(message
);
275 this.connection
.setConnectTimeout(this.connectTimeout
);
276 this.connection
.setReadTimeout(this.readTimeout
);
281 private void end() throws Exception
285 if (this.postProcessor
!= null)
287 this.byteBuffer
= this.postProcessor
.run(this);
292 this.setState(RETRIEVER_STATE_ERROR
);
293 Logging
.logger().log(Level
.SEVERE
,
294 Logging
.getMessage("URLRetriever.ErrorPostProcessing", this.url
.toString()), e
);
299 private ByteBuffer
read() throws Exception
303 ByteBuffer buffer
= this.doRead(this.connection
);
305 this.contentLength
= 0;
310 if (!(e
instanceof java
.net
.SocketTimeoutException
|| e
instanceof UnknownHostException
311 || e
instanceof SocketException
))
313 Logging
.logger().log(Level
.SEVERE
,
314 Logging
.getMessage("URLRetriever.ErrorReadingFromConnection", this.url
.toString()), e
);
321 * @param connection the connection to read from.
322 * @return a buffer containing the content read from the connection
323 * @throws Exception if <code>connection</code> is null or an exception occurs during reading.
324 * @throws IllegalArgumentException if <code>connection</code> is null
326 protected ByteBuffer
doRead(URLConnection connection
) throws Exception
328 if (connection
== null)
330 String msg
= Logging
.getMessage("nullValue.ConnectionIsNull");
331 Logging
.logger().severe(msg
);
332 throw new IllegalArgumentException(msg
);
335 this.contentLength
= this.connection
.getContentLength();
338 InputStream inputStream
= null;
341 inputStream
= this.connection
.getInputStream();
342 if (inputStream
== null)
344 Logging
.logger().log(Level
.SEVERE
, "URLRetriever.InputStreamFromConnectionNull", connection
.getURL());
348 // TODO: Make decompression of zip file configurable
349 // TODO: Make this more generally configurable based on content type
350 // todo: make a zip reader that handles streams of unknown length
351 // TODO: add a gzip reader
352 // TODO: this code is checking content type for compression when it should be checking content encoding,
353 // but the WW servers are sending application/zip as the content type, and null for the content encoding.
354 this.contentType
= connection
.getContentType();
355 if (this.contentType
!= null && this.contentType
.equalsIgnoreCase("application/zip"))
357 this.readZipStream(inputStream
, connection
.getURL()); // assume single file in zip and decompress it
359 buffer
= this.readNonSpecificStream(inputStream
, connection
);
361 this.contentType
= connection
.getContentType();
365 if (inputStream
!= null)
370 catch (IOException e
)
372 Logging
.logger().log(Level
.SEVERE
, "URLRetriever.ExceptionClosingInputStreamToConnection",
373 connection
.getURL());
380 private ByteBuffer
readNonSpecificStream(InputStream inputStream
, URLConnection connection
) throws IOException
382 if (inputStream
== null)
384 String message
= Logging
.getMessage("URLRetriever.InputStreamNullFor", connection
.getURL());
385 Logging
.logger().severe(message
);
386 throw new IllegalArgumentException(message
);
389 if (this.contentLength
< 1)
391 return readNonSpecificStreamUnknownLength(inputStream
);
394 ReadableByteChannel channel
= Channels
.newChannel(inputStream
);
395 ByteBuffer buffer
= ByteBuffer
.allocate(this.contentLength
);
397 int numBytesRead
= 0;
398 while (!this.interrupted() && numBytesRead
>= 0 && numBytesRead
< buffer
.limit())
400 int count
= channel
.read(buffer
);
402 this.contentLengthRead
.getAndAdd(numBytesRead
+= count
);
411 private ByteBuffer
readNonSpecificStreamUnknownLength(InputStream inputStream
) throws IOException
413 final int PAGE_SIZE
= 4096;
415 ReadableByteChannel channel
= Channels
.newChannel(inputStream
);
416 ByteBuffer buffer
= ByteBuffer
.allocate(PAGE_SIZE
);
419 int numBytesRead
= 0;
420 while (!this.interrupted() && count
>= 0)
422 count
= channel
.read(buffer
);
424 this.contentLengthRead
.getAndAdd(numBytesRead
+= count
);
426 if (count
> 0 && !buffer
.hasRemaining())
428 ByteBuffer biggerBuffer
= ByteBuffer
.allocate(buffer
.limit() + PAGE_SIZE
);
429 biggerBuffer
.put((ByteBuffer
) buffer
.rewind());
430 buffer
= biggerBuffer
;
441 * @param inputStream a stream to the zip connection.
442 * @param url the URL of the zip resource.
443 * @return a buffer containing the content read from the zip stream.
444 * @throws java.io.IOException if the stream does not refer to a zip resource or an exception occurs during reading.
445 * @throws IllegalArgumentException if <code>inputStream</code> is null
447 private ByteBuffer
readZipStream(InputStream inputStream
, URL url
) throws IOException
449 ZipInputStream zis
= new ZipInputStream(inputStream
);
450 ZipEntry ze
= zis
.getNextEntry();
453 Logging
.logger().severe(Logging
.getMessage("URLRetriever.NoZipEntryFor") + url
);
457 ByteBuffer buffer
= null;
458 if (ze
.getSize() > 0)
460 buffer
= ByteBuffer
.allocate((int) ze
.getSize());
462 byte[] inputBuffer
= new byte[8192];
463 while (buffer
.hasRemaining())
465 int count
= zis
.read(inputBuffer
);
468 buffer
.put(inputBuffer
, 0, count
);
469 this.contentLengthRead
.getAndAdd(buffer
.position() + 1);
480 public boolean equals(Object o
)
484 if (o
== null || getClass() != o
.getClass())
487 final URLRetriever that
= (URLRetriever
) o
;
489 // Retrievers are considered identical if they are for the same URL. This convention is used by the
490 // retrieval service to filter out duplicate retreival requests.
491 return !(url
!= null ?
!url
.toString().contentEquals(that
.url
.toString()) : that
.url
!= null);
495 public int hashCode()
498 result
= (url
!= null ? url
.hashCode() : 0);
503 public String
toString()
505 return this.getName() != null ?
this.getName() : super.toString();