All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.retrieve.AbstractRetrievalPostProcessor Maven / Gradle / Ivy

The newest version!
/*
 * 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy