src.gov.nasa.worldwind.retrieve.AbstractRetrievalPostProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.retrieve;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.formats.dds.DDSCompressor;
import gov.nasa.worldwind.util.*;
import javax.imageio.ImageIO;
import java.awt.image.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
/**
* Abstract base class for retrieval post-processors. Verifies the retrieval operation and dispatches the content to the
* a subclasses content handlers.
*
* Subclasses are expected to override the methods necessary to handle their particular post-processing operations.
*
* @author Tom Gaskins
* @version $Id: AbstractRetrievalPostProcessor.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public abstract class AbstractRetrievalPostProcessor implements RetrievalPostProcessor
{
/** Holds miscellaneous parameters examined by this and subclasses. */
protected AVList avList;
/** The retriever associated with the post-processor. Only non-null after {@link #run(Retriever)} is called. */
protected Retriever retriever;
/**
* Abstract method that subclasses must provide to identify the output file for the post-processor's retrieval
* content.
*
* @return the output file.
*/
abstract protected File doGetOutputFile();
/** Create a default post-processor. */
public AbstractRetrievalPostProcessor()
{
}
/**
* Create a post-processor and pass it attributes that can be examined during content handling.
*
* @param avList an attribute-value list with values that might be used during post-processing.
*/
public AbstractRetrievalPostProcessor(AVList avList)
{
this.avList = avList;
}
/**
* Runs the post-processor.
*
* @param retriever the retriever to associate with the post-processor.
*
* @return a buffer containing the downloaded data, perhaps converted during content handling. null is returned if a
* fatal problem occurred during post-processing.
*
* @throws IllegalArgumentException if the retriever is null.
*/
public ByteBuffer run(Retriever retriever)
{
if (retriever == null)
{
String message = Logging.getMessage("nullValue.RetrieverIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.retriever = retriever;
if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL))
{
this.handleUnsuccessfulRetrieval();
return null;
}
if (!this.validateResponseCode())
{
this.handleInvalidResponseCode();
return null;
}
return this.handleSuccessfulRetrieval();
}
/**
* Returns the retriever associarted with this post-processor.
*
* @return the retriever associated with the post-processor, or null if no retriever is associated.
*/
public Retriever getRetriever()
{
return this.retriever;
}
/**
* Called when the retrieval state is other than {@link Retriever#RETRIEVER_STATE_SUCCESSFUL}. Can be overridden by
* subclasses to handle special error cases. The default implementation calls {@link #markResourceAbsent()} if the
* retrieval state is {@link Retriever#RETRIEVER_STATE_ERROR}.
*/
protected void handleUnsuccessfulRetrieval()
{
if (this.getRetriever().getState().equals(Retriever.RETRIEVER_STATE_ERROR))
this.markResourceAbsent();
}
/**
* Process the retrieved data if it has been retrieved successfully.
*
* @return a buffer containing the downloaded data, perhaps converted during content handling.
*/
protected ByteBuffer handleSuccessfulRetrieval()
{
try
{
return this.handleContent();
}
catch (Exception e)
{
this.handleContentException(e);
return null;
}
}
/**
* Checks the retrieval response code.
*
* @return true if the response code is the OK value for the protocol, e.g., ({@link HttpURLConnection#HTTP_OK} for
* HTTP protocol), otherwise false.
*/
protected boolean validateResponseCode()
{
//noinspection SimplifiableIfStatement
if (this.getRetriever() instanceof HTTPRetriever)
return this.validateHTTPResponseCode();
else if (this.getRetriever() instanceof JarRetriever)
return this.validateJarResponseCode();
else if (this.getRetriever() instanceof LocalRasterServerRetriever)
return true;
return false;
}
/**
* Checks the retrieval's HTTP response code. Must only be called when the retriever is a subclass of {@link
* gov.nasa.worldwind.retrieve.HTTPRetriever}.
*
* @return true if the response code is {@link HttpURLConnection#HTTP_OK}, otherwise false.
*/
protected boolean validateHTTPResponseCode()
{
HTTPRetriever htr = (HTTPRetriever) this.getRetriever();
return htr.getResponseCode() == HttpURLConnection.HTTP_OK;
}
/**
* Checks the retrieval's HTTP response code. Must only be called when the retriever is a subclass of {@link
* gov.nasa.worldwind.retrieve.HTTPRetriever}.
*
* @return true if the response code is {@link HttpURLConnection#HTTP_OK}, otherwise false.
*/
protected boolean validateJarResponseCode()
{
JarRetriever htr = (JarRetriever) this.getRetriever();
return htr.getResponseCode() == HttpURLConnection.HTTP_OK; // Re-using the HTTP response code for OK
}
/**
* Handle the case of an invalid response code. Subclasses can override this method to handle special cases. The
* default implementation calls {@link #markResourceAbsent()} and logs the contents of the retrieval buffer if it
* contains content of type "text".
*/
protected void handleInvalidResponseCode()
{
this.markResourceAbsent();
if (this.isWMSException())
this.handleWMSExceptionContent();
else if (this.isPrimaryContentType("text", this.getRetriever().getContentType()))
this.logTextBuffer(this.getRetriever().getBuffer()); // the buffer might contain error info, so log it
}
/**
* Marks the retrieval target absent. Subclasses should override this method if they keep track of absent-resources.
* The default implementation does nothing.
*/
protected void markResourceAbsent()
{
}
/**
* Saves the retrieved and possibly transformed data. The data may have been transformed during content handling.
*
* The default implementation of this method simply calls {@link #saveBuffer(java.nio.ByteBuffer)} with an argument
* of null.
*
* @return true if the buffer was saved, false if the output file could not be determined or already exists and not
* overwritten.
*
* @throws IOException if an IO error occurs while attempting to save the buffer.
*/
protected boolean saveBuffer() throws IOException
{
return this.saveBuffer(null);
}
/**
* Saves the retrieved and possibly transformed data. The data may have been transformed during content handling.
* The data is not saved if the output file already exists unless {@link #overwriteExistingFile()} returns true.
*
* @param buffer the buffer to save.
*
* @return true if the buffer was saved, false if the output file could not be determined or already exists and not
* overwritten.
*
* @throws IOException if an IO error occurred when attempting to save the buffer.
*/
protected boolean saveBuffer(ByteBuffer buffer) throws IOException
{
File outFile = this.getOutputFile();
if (outFile == null)
return false;
if (outFile.exists() && !this.overwriteExistingFile())
return false;
synchronized (this.getFileLock()) // synchronize with read of file in another class
{
WWIO.saveBuffer(buffer != null ? buffer : this.getRetriever().getBuffer(), outFile);
}
return true;
}
/**
* Determines and returns the output file for the retrieved data.
*
* @return the output file, or null if a file could not be determined.
*/
protected File getOutputFile()
{
File outFile = this.doGetOutputFile();
if (outFile != null && this.isDeleteOnExit(outFile))
outFile.deleteOnExit();
return outFile;
}
/**
* Indicates whether the retrieved data should be written to the output file if a file of the same name already
* exists. The default implementation of this method returns false (files are not overwritten).
*
* @return true if an existing file should be overwritten, otherwise false.
*/
protected boolean overwriteExistingFile()
{
return false;
}
/**
* Indicates whether the output file should have its delete-on-exit flag set so that it's deleted when the JVM
* terminates.
*
* @param outFile the output file.
*
* @return true if the output file's delete-on-exit flag should be set, otherwise false.
*/
protected boolean isDeleteOnExit(File outFile)
{
return !outFile.exists() && this.avList != null && this.avList.getValue(AVKey.DELETE_CACHE_ON_EXIT) != null;
}
/**
* Returns an object that can be used to synchronize writing to the output file. Superclasses should override this
* method and return the object used as a lock by other objects that read or otherwise interact with the output
* file.
*
* @return an object to use for read/write synchronization, or null if no lock is needed.
*/
protected Object getFileLock()
{
return this;
}
protected boolean isPrimaryContentType(String typeOfContent, String contentType)
{
if (WWUtil.isEmpty(contentType) || WWUtil.isEmpty(typeOfContent))
return false;
return contentType.trim().toLowerCase().startsWith(typeOfContent);
}
protected boolean isWMSException()
{
String contentType = this.getRetriever().getContentType();
if (WWUtil.isEmpty(contentType))
return false;
return contentType.trim().equalsIgnoreCase("application/vnd.ogc.se_xml");
}
/**
* Process the retrieved data. Dispatches content handling to content-type specific handlers: {@link
* #handleZipContent()} for content types containing "zip", {@link #handleTextContent()} for content types starting
* with "text", and {@link #handleImageContent()} for contents types starting with "image".
*
* @return a buffer containing the retrieved data, which may have been transformed during content handling.
*
* @throws IOException if an IO error occurs while processing the data.
*/
protected ByteBuffer handleContent() throws IOException
{
String contentType = this.getRetriever().getContentType();
if (WWUtil.isEmpty(contentType))
{
// Try to determine the content type from the URL's suffix, if any.
String suffix = WWIO.getSuffix(this.getRetriever().getName().split(";")[0]);
if (!WWUtil.isEmpty(suffix))
contentType = WWIO.makeMimeTypeForSuffix(suffix);
if (WWUtil.isEmpty(contentType))
{
Logging.logger().severe(Logging.getMessage("nullValue.ContentTypeIsNullOrEmpty"));
return null;
}
}
contentType = contentType.trim().toLowerCase();
if (this.isWMSException())
return this.handleWMSExceptionContent();
if (contentType.contains("zip"))
return this.handleZipContent();
if (this.isPrimaryContentType("text", contentType))
return this.handleTextContent();
if (this.isPrimaryContentType("image", contentType))
return this.handleImageContent();
if (this.isPrimaryContentType("application", contentType))
return this.handleApplicationContent();
return this.handleUnknownContentType();
}
/**
* Reacts to exceptions occurring during content handling. Subclasses may override this method to perform special
* exception handling. The default implementation logs a message specific to the exception.
*
* @param e the exception to handle.
*/
protected void handleContentException(Exception e)
{
if (e instanceof ClosedByInterruptException)
{
Logging.logger().log(java.util.logging.Level.FINE,
Logging.getMessage("generic.OperationCancelled",
"retrieval post-processing for " + this.getRetriever().getName()), e);
}
else if (e instanceof IOException)
{
this.markResourceAbsent();
Logging.logger().log(java.util.logging.Level.SEVERE,
Logging.getMessage("generic.ExceptionWhileSavingRetreivedData", this.getRetriever().getName()), e);
}
}
/**
* Handles content types that are not recognized by the content handler. Subclasses may override this method to
* handle such cases. The default implementation logs an error message and returns null.
*
* @return null if no further processing should occur, otherwise the retrieved data, perhaps transformed.
*/
protected ByteBuffer handleUnknownContentType()
{
Logging.logger().log(java.util.logging.Level.WARNING,
Logging.getMessage("generic.UnknownContentType", this.getRetriever().getContentType()));
return null;
}
/**
* Handles Text content. If the content type is text/xml, {@link #handleXMLContent()} is called. If the content type
* is text/html, {@link #handleHTMLContent()} is called. For all other sub-types the content is logged as a message
* with level {@link java.util.logging.Level#SEVERE}.
*
* @return a buffer containing the retrieved text.
*
* @throws IOException if an IO error occurs while processing the data.
*/
protected ByteBuffer handleTextContent() throws IOException
{
String contentType = this.getRetriever().getContentType().trim().toLowerCase();
if (contentType.contains("xml"))
return this.handleXMLContent();
if (contentType.contains("html"))
return this.handleHTMLContent();
this.logTextBuffer(this.getRetriever().getBuffer());
return null;
}
/**
* Handles XML content. The default implementation only calls {@link #logTextBuffer(java.nio.ByteBuffer)} and
* returns.
*
* @return a buffer containing the retrieved XML.
*
* @throws IOException if an IO error occurs while processing the data.
*/
protected ByteBuffer handleXMLContent() throws IOException
{
this.logTextBuffer(this.getRetriever().getBuffer());
return null;
}
/**
* Handles HTML content. The default implementation only calls {@link #logTextBuffer(java.nio.ByteBuffer)} and
* returns.
*
* @return a buffer containing the retrieved HTML.
*
* @throws IOException if an IO error occurs while processing the data.
*/
protected ByteBuffer handleHTMLContent() throws IOException
{
this.logTextBuffer(this.getRetriever().getBuffer());
return null;
}
/**
* Log the content of a buffer as a String. If the buffer is null or empty, nothing is logged. Only the first 2,048
* characters of the buffer are included in the log message.
*
* @param buffer the content to log. The content is assumed to be of type "text".
*/
protected void logTextBuffer(ByteBuffer buffer)
{
if (buffer == null || !buffer.hasRemaining())
return;
Logging.logger().warning(WWIO.byteBufferToString(buffer, 2048, null));
}
/**
* Handles zipped content. The default implementation saves the data to the retriever's output file without
* unzipping it.
*
* @return a buffer containing the retrieved data.
*
* @throws IOException if an IO error occurs while processing the data.
*/
protected ByteBuffer handleZipContent() throws IOException
{
File outFile = this.getOutputFile();
if (outFile == null)
return null;
this.saveBuffer();
return this.getRetriever().getBuffer();
}
/**
* Handles application content. The default implementation saves the retrieved data without modification via {@link
* #saveBuffer()} without.
*
* @return a buffer containing the retrieved data.
*
* @throws IOException if an IO error occurs while processing the data.
*/
protected ByteBuffer handleApplicationContent() throws IOException
{
this.saveBuffer();
return this.getRetriever().getBuffer();
}
/**
* Handles WMS exceptions.
*
* @return a buffer containing the retrieved XML.
*/
protected ByteBuffer handleWMSExceptionContent()
{
// TODO: Parse the xml and include only the message text in the log message.
StringBuilder sb = new StringBuilder(this.getRetriever().getName());
sb.append("\n");
sb.append(WWIO.byteBufferToString(this.getRetriever().getBuffer(), 2048, null));
Logging.logger().warning(sb.toString());
return null;
}
/**
* Handles image content. The default implementation simply saves the retrieved data via {@link #saveBuffer()},
* first converting it to DDS if the suffix of the output file is .dds.
*
* The default implementation of this method returns immediately if the output file cannot be determined or it
* exists and {@link #overwriteExistingFile()} returns false.
*
* @return a buffer containing the retrieved data.
*
* @throws IOException if an IO error occurs while processing the data.
*/
protected ByteBuffer handleImageContent() throws IOException
{
// BE CAREFUL: This method may be overridden by subclasses to handle special image cases. It's also implemented
// to handle elevations as images correctly (just save them to the filestore).
File outFile = this.getOutputFile();
if (outFile == null || (outFile.exists() && !this.overwriteExistingFile()))
return this.getRetriever().getBuffer();
if (outFile.getPath().endsWith("dds"))
return this.saveDDS();
BufferedImage image = this.transformPixels();
if (image != null)
{
synchronized (this.getFileLock()) // synchronize with read of file in another class
{
ImageIO.write(image, this.getRetriever().getContentType().split("/")[1], outFile);
}
}
else
this.saveBuffer();
return this.getRetriever().getBuffer();
}
/**
* Transform the retrieved data in some purpose-specific way. May be overridden by subclasses to perform special
* transformations. The default implementation calls {@link ImageUtil#mapTransparencyColors(java.awt.image.BufferedImage,
* int[])} if the attribute-value list specified at construction contains transparency colors (includes the {@link
* AVKey#TRANSPARENCY_COLORS} key).
*
* @return returns the transformed data if a transform is performed, otherwise returns the original data.
*/
protected BufferedImage transformPixels()
{
if (this.avList != null)
{
int[] colors = (int[]) this.avList.getValue(AVKey.TRANSPARENCY_COLORS);
if (colors != null)
return ImageUtil.mapTransparencyColors(this.getRetriever().getBuffer(), colors);
}
return null;
}
/**
* Saves a DDS image file after first converting any other image format to DDS.
*
* @return the converted image data if a conversion is performed, otherwise the original image data.
*
* @throws IOException if an IO error occurs while converting or saving the image.
*/
protected ByteBuffer saveDDS() throws IOException
{
ByteBuffer buffer = this.getRetriever().getBuffer();
if (!this.getRetriever().getContentType().contains("dds"))
buffer = this.convertToDDS();
this.saveBuffer(buffer);
return buffer;
}
/**
* Converts an image to DDS. If the image format is not originally DDS, calls {@link #transformPixels()} to perform
* any defined image transform.
*
* @return the converted image data if a conversion is performed, otherwise the original image data.
*
* @throws IOException if an IO error occurs while converting the image.
*/
protected ByteBuffer convertToDDS() throws IOException
{
ByteBuffer buffer;
BufferedImage image = this.transformPixels();
if (image != null)
buffer = DDSCompressor.compressImage(image);
else
buffer = DDSCompressor.compressImageBuffer(this.getRetriever().getBuffer());
return buffer;
}
}