SearchResponseParser.java

package gov.usgs.earthquake.indexer;

import java.math.BigDecimal;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import gov.usgs.earthquake.distribution.FileProductStorage;
import gov.usgs.earthquake.product.Product;
import gov.usgs.earthquake.product.ProductId;
import gov.usgs.earthquake.product.io.XmlProductHandler;
import gov.usgs.util.XmlUtils;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Parser for SearchXML response.
 */
public class SearchResponseParser extends DefaultHandler {

  private SearchResponse response = null;

  private SearchQuery query = null;

  private ProductSummary pSummary = null;
  private EventSummary eSummary = null;

  private Event event = null;

  private boolean inQueryElement = false;
  private boolean inErrorElement = false;

  private FileProductStorage storage;
  private SearchResponseXmlProductSource productHandler = null;

  /**
   * Constructor
   *
   * @param storage a FileProductStorage
   */
  public SearchResponseParser(final FileProductStorage storage) {
    this.storage = storage;
  }

  /** @return SearchResponse */
  public SearchResponse getSearchResponse() {
    return response;
  }

  @Override
  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
    if (productHandler != null) {
      productHandler.startElement(uri, localName, qName, attributes);
    } else if (SearchXML.INDEXER_XMLNS.equals(uri)) {
      if (SearchXML.RESPONSE_ELEMENT.equals(localName)) {
        response = new SearchResponse();
      } else if (SearchXML.RESULT_ELEMENT.equals(localName)) {
        if (response == null)
          throw new SAXException("Unexpected result element without response element parent.");
        SearchMethod method = SearchMethod
            .fromXmlMethodName(XmlUtils.getAttribute(attributes, uri, SearchXML.METHOD_ATTRIBUTE));
        query = SearchQuery.getSearchQuery(method, new ProductIndexQuery());
        // create results container now
        if (query instanceof EventDetailQuery) {
          ((EventDetailQuery) query).setResult(new ArrayList<Event>());
        } else if (query instanceof EventsSummaryQuery) {
          ((EventsSummaryQuery) query).setResult(new ArrayList<EventSummary>());
        } else if (query instanceof ProductDetailQuery) {
          ((ProductDetailQuery) query).setResult(new ArrayList<Product>());
        } else if (query instanceof ProductsSummaryQuery) {
          ((ProductsSummaryQuery) query).setResult(new ArrayList<ProductSummary>());
        }
      } else if (SearchXML.QUERY_ELEMENT.equals(localName)) {
        if (query == null)
          throw new SAXException("Unexpected query element without result element parent.");
        inQueryElement = true;
        ProductIndexQuery piQuery = query.getProductIndexQuery();

        // Update the ProductIndexQuery with each given attribute
        // Event Source Attribute
        String eventSource = XmlUtils.getAttribute(attributes, uri, SearchXML.EVENT_SOURCE_ATTRIBUTE);
        if (eventSource != null) {
          piQuery.setEventSource(eventSource);
        }
        // Event Source Code Attribute
        String eventSourceCode = XmlUtils.getAttribute(attributes, uri, SearchXML.EVENT_SOURCE_CODE_ATTRIBUTE);
        if (eventSourceCode != null) {
          piQuery.setEventSourceCode(eventSourceCode);
        }
        // Max Event Depth Attribute
        String maxEventDepth = XmlUtils.getAttribute(attributes, uri, SearchXML.MAX_EVENT_DEPTH_ATTRIBUTE);
        if (maxEventDepth != null) {
          piQuery.setMaxEventDepth(new BigDecimal(maxEventDepth));
        }
        // Max Event Latitude Attribute
        String maxEventLatitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MAX_EVENT_LATITUDE_ATTRIBUTE);
        if (maxEventLatitude != null) {
          piQuery.setMaxEventLatitude(new BigDecimal(maxEventLatitude));
        }
        // Max Event Longitude Attribute
        String maxEventLongitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MAX_EVENT_LONGITUDE_ATTRIBUTE);
        if (maxEventLongitude != null) {
          piQuery.setMaxEventLongitude(new BigDecimal(maxEventLongitude));
        }
        // Max Event Magnitude Attribute
        String maxEventMagnitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MAX_EVENT_MAGNITUDE_ATTRIBUTE);
        if (maxEventMagnitude != null) {
          piQuery.setMaxEventMagnitude(new BigDecimal(maxEventMagnitude));
        }
        // Max Event Time Attribute
        String maxEventTime = XmlUtils.getAttribute(attributes, uri, SearchXML.MAX_EVENT_TIME_ATTRIBUTE);
        if (maxEventTime != null) {
          piQuery.setMaxEventTime(XmlUtils.getDate(maxEventTime));
        }
        // Max Product Update Time Attribute
        String maxProductUpdateTime = XmlUtils.getAttribute(attributes, uri,
            SearchXML.MAX_PRODUCT_UPDATE_TIME_ATTRIBUTE);
        if (maxProductUpdateTime != null) {
          piQuery.setMaxProductUpdateTime(XmlUtils.getDate(maxProductUpdateTime));
        }
        // Min Event Depth Attribute
        String minEventDepth = XmlUtils.getAttribute(attributes, uri, SearchXML.MIN_EVENT_DEPTH_ATTRIBUTE);
        if (minEventDepth != null) {
          piQuery.setMinEventDepth(new BigDecimal(minEventDepth));
        }
        // Min Event Latitude Attribute
        String minEventLatitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MIN_EVENT_LATITUDE_ATTRIBUTE);
        if (minEventLatitude != null) {
          piQuery.setMinEventLatitude(new BigDecimal(minEventLatitude));
        }
        // Min Event Longitude Attribute
        String minEventLongitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MIN_EVENT_LONGITUDE_ATTRIBUTE);
        if (minEventLongitude != null) {
          piQuery.setMinEventLongitude(new BigDecimal(minEventLongitude));
        }
        // Min Event Magnitude Attribute
        String minEventMagnitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MIN_EVENT_MAGNITUDE_ATTRIBUTE);
        if (minEventMagnitude != null) {
          piQuery.setMinEventMagnitude(new BigDecimal(minEventMagnitude));
        }
        // Min Event Time Attribute
        String minEventTime = XmlUtils.getAttribute(attributes, uri, SearchXML.MIN_EVENT_TIME_ATTRIBUTE);
        if (minEventTime != null) {
          piQuery.setMinEventTime(XmlUtils.getDate(minEventTime));
        }
        // Min Product Update Time Attribute
        String minProductUpdateTime = XmlUtils.getAttribute(attributes, uri,
            SearchXML.MIN_PRODUCT_UPDATE_TIME_ATTRIBUTE);
        if (minProductUpdateTime != null) {
          piQuery.setMinProductUpdateTime(XmlUtils.getDate(minProductUpdateTime));
        }
        // Product Code Attribute
        String productCode = XmlUtils.getAttribute(attributes, uri, SearchXML.PRODUCT_CODE_ATTRIBUTE);
        if (productCode != null) {
          piQuery.setProductCode(productCode);
        }
        // Product Source Attribute
        String productSource = XmlUtils.getAttribute(attributes, uri, SearchXML.PRODUCT_SOURCE_ATTRIBUTE);
        if (productSource != null) {
          piQuery.setProductSource(productSource);
        }
        // Product Status Attribute
        String productStatus = XmlUtils.getAttribute(attributes, uri, SearchXML.PRODUCT_STATUS_ATTRIBUTE);
        if (productStatus != null) {
          piQuery.setProductStatus(productStatus);
        }
        // Product Type Attribute
        String productType = XmlUtils.getAttribute(attributes, uri, SearchXML.PRODUCT_TYPE_ATTRIBUTE);
        if (productType != null) {
          piQuery.setProductType(productType);
        }
        // Product Version Attribute
        String productVersion = XmlUtils.getAttribute(attributes, uri, SearchXML.PRODUCT_VERSION_ATTRIBUTE);
        if (productVersion != null) {
          piQuery.setProductVersion(productVersion);
        }

        // Set result type. At the moment we ony support the "current"
        // type.
        piQuery.setResultType(ProductIndexQuery.RESULT_TYPE_CURRENT);
      } else if (SearchXML.PRODUCT_SUMMARY_ELEMENT.equals(localName)) {
        if (inQueryElement) {
          // This product summary is being used to pass ID information
          // for the query
          ProductIndexQuery piQuery = query.getProductIndexQuery();
          piQuery.getProductIds().add(ProductId.parse(XmlUtils.getAttribute(attributes, uri, SearchXML.ID_ATTRIBUTE)));
        } else {
          // This is a more complete returned product summary
          pSummary = new ProductSummary();

          // Set the attributes of the ProductSummary
          // Depth attribute
          String depth = XmlUtils.getAttribute(attributes, uri, SearchXML.DEPTH_ATTRIBUTE);
          if (depth != null) {
            pSummary.setEventDepth(new BigDecimal(depth));
          }
          // Latitude attribute
          String latitude = XmlUtils.getAttribute(attributes, uri, SearchXML.LATITUDE_ATTRIBUTE);
          if (latitude != null) {
            pSummary.setEventLatitude(new BigDecimal(latitude));
          }
          // Longitude attribute
          String longitude = XmlUtils.getAttribute(attributes, uri, SearchXML.LONGITUDE_ATTRIBUTE);
          if (longitude != null) {
            pSummary.setEventLongitude(new BigDecimal(longitude));
          }
          // Magnitude attribute
          String magnitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MAGNITUDE_ATTRIBUTE);
          if (magnitude != null) {
            pSummary.setEventMagnitude(new BigDecimal(magnitude));
          }
          // Event Source attribute
          String eventSource = XmlUtils.getAttribute(attributes, uri, SearchXML.EVENT_SOURCE_ATTRIBUTE);
          if (eventSource != null) {
            pSummary.setEventSource(eventSource);
          }
          // Event Source Code attribute
          String eventSourceCode = XmlUtils.getAttribute(attributes, uri, SearchXML.EVENT_SOURCE_CODE_ATTRIBUTE);
          if (eventSourceCode != null) {
            pSummary.setEventSourceCode(eventSourceCode);
          }
          // Time attribute
          String time = XmlUtils.getAttribute(attributes, uri, SearchXML.TIME_ATTRIBUTE);
          if (time != null) {
            pSummary.setEventTime(XmlUtils.getDate(time));
          }
          // ID attribute
          String id = XmlUtils.getAttribute(attributes, uri, SearchXML.ID_ATTRIBUTE);
          if (id != null) {
            pSummary.setId(ProductId.parse(id));
          }
          // Preferred Weight attribute
          String preferredWeight = XmlUtils.getAttribute(attributes, uri, SearchXML.PREFERRED_WEIGHT_ATTRIBUTE);
          if (preferredWeight != null) {
            pSummary.setPreferredWeight(Integer.parseInt(preferredWeight));
          }
          // Status attribute
          String status = XmlUtils.getAttribute(attributes, uri, SearchXML.STATUS_ATTRIBUTE);
          if (status != null) {
            pSummary.setStatus(status);
          }
          // Version attribute
          String version = XmlUtils.getAttribute(attributes, uri, SearchXML.VERSION_ATTRIBUTE);
          if (version != null) {
            pSummary.setVersion(version);
          }
        }
      } else if (SearchXML.EVENT_ELEMENT.equals(localName)) {
        event = new Event();
        // Nothing further needs to be done here as properties are set
        // by ProductSummaries for the event
      } else if (SearchXML.EVENT_SUMMARY_ELEMENT.equals(localName)) {
        eSummary = new EventSummary();

        // Configure EventSummary attributes
        // Depth attribute
        String depth = XmlUtils.getAttribute(attributes, uri, SearchXML.DEPTH_ATTRIBUTE);
        if (depth != null) {
          eSummary.setDepth(new BigDecimal(depth));
        }
        // Latitude attribute
        String latitude = XmlUtils.getAttribute(attributes, uri, SearchXML.LATITUDE_ATTRIBUTE);
        if (latitude != null) {
          eSummary.setLatitude(new BigDecimal(latitude));
        }
        // Longitude attribute
        String longitude = XmlUtils.getAttribute(attributes, uri, SearchXML.LONGITUDE_ATTRIBUTE);
        if (longitude != null) {
          eSummary.setLongitude(new BigDecimal(longitude));
        }
        // Magnitude attribute
        String magnitude = XmlUtils.getAttribute(attributes, uri, SearchXML.MAGNITUDE_ATTRIBUTE);
        if (magnitude != null) {
          eSummary.setMagnitude(new BigDecimal(magnitude));
        }
        // Source attribute
        String source = XmlUtils.getAttribute(attributes, uri, SearchXML.SOURCE_ATTRIBUTE);
        if (source != null) {
          eSummary.setSource(source);
        }
        // Source code attribute
        String sourceCode = XmlUtils.getAttribute(attributes, uri, SearchXML.SOURCE_CODE_ATTRIBUTE);
        if (sourceCode != null) {
          eSummary.setSourceCode(sourceCode);
        }
        // Time attribute
        String time = XmlUtils.getAttribute(attributes, uri, SearchXML.TIME_ATTRIBUTE);
        if (time != null) {
          eSummary.setTime(XmlUtils.getDate(time));
        }
      } else if (SearchXML.ERROR_ELEMENT.equals(localName)) {
        inErrorElement = true;
      }
    } else if (XmlProductHandler.PRODUCT_XML_NAMESPACE.equals(uri)) {
      if (XmlProductHandler.PROPERTY_ELEMENT.equals(localName)) {
        if (pSummary != null) {
          // Currently working on a Product Summary
          pSummary.getProperties().put(
              XmlUtils.getAttribute(attributes, uri, XmlProductHandler.PROPERTY_ATTRIBUTE_NAME),
              XmlUtils.getAttribute(attributes, uri, XmlProductHandler.PROPERTY_ATTRIBUTE_VALUE));
        } else if (eSummary != null) {
          // Currently working on an Event Summary
          eSummary.getProperties().put(
              XmlUtils.getAttribute(attributes, uri, XmlProductHandler.PROPERTY_ATTRIBUTE_NAME),
              XmlUtils.getAttribute(attributes, uri, XmlProductHandler.PROPERTY_ATTRIBUTE_VALUE));
        } else if (inQueryElement) {
          // Currently working on a query
          // This is defined in the schema, but not in the class
          // for either query
        } else {
          throw new SAXException("Property element without appropriate parent encountered.");
        }
      } else if (XmlProductHandler.LINK_ELEMENT.equals(localName)) {
        if (pSummary != null) {
          // This link is occurring as part of a product summary
          String relation = XmlUtils.getAttribute(attributes, uri, XmlProductHandler.LINK_ATTRIBUTE_RELATION);
          Map<String, List<URI>> links = pSummary.getLinks();
          if (links.containsKey(relation))
            links.get(relation)
                .add(URI.create(XmlUtils.getAttribute(attributes, uri, XmlProductHandler.LINK_ATTRIBUTE_HREF)));
          else {
            List<URI> newList = new ArrayList<URI>();
            newList.add(URI.create(XmlUtils.getAttribute(attributes, uri, XmlProductHandler.LINK_ATTRIBUTE_HREF)));
            links.put(relation, newList);
          }
        } else {
          throw new SAXException("Link element without appropriate parent encountered.");
        }
      } else if (XmlProductHandler.PRODUCT_ELEMENT.equals(localName)) {
        // We are starting to process a product and need to pass data
        // through
        // until that is complete.
        productHandler = new SearchResponseXmlProductSource(storage);
        productHandler.startElement(uri, localName, qName, attributes);
      }
    }
  }

  @Override
  public void endElement(String uri, String localName, String qName) throws SAXException {
    if (productHandler != null) {
      productHandler.endElement(uri, localName, qName);
      if (XmlProductHandler.PRODUCT_ELEMENT.equals(localName)) {
        ProductDetailQuery pdQuery = (ProductDetailQuery) query;
        pdQuery.getResult().add(productHandler.getProduct());
        productHandler = null;
      }
    } else if (SearchXML.INDEXER_XMLNS.equals(uri)) {
      if (SearchXML.RESPONSE_ELEMENT.equals(localName)) {
        // Do nothing, this is the end element of the document.
      } else if (SearchXML.RESULT_ELEMENT.equals(localName)) {
        // One of the results has completed
        if (response == null)
          throw new SAXException("result element found without response parent");
        else {
          response.addResult(query);
          query = null;
        }
      } else if (SearchXML.QUERY_ELEMENT.equals(localName)) {
        // The query element of a result has completed
        // Because the ProductIndexQuery is part of the
        // query object controlled by the result element
        // nothing particular needs to be done other than
        // set the flag for being in the query element to false
        inQueryElement = false;
      } else if (SearchXML.PRODUCT_SUMMARY_ELEMENT.equals(localName)) {
        if (inQueryElement) {
          // Nothing needs to be done because this was just used
          // to get the product ID into the list of IDs for the
          // query.
        } else {
          if (event != null) {
            // We're adding product summaries to events.
            Map<String, List<ProductSummary>> eventProducts = event.getAllProducts();
            String productType = pSummary.getType();
            if (eventProducts.containsKey(productType)) {
              // Key exists, so just add the product summary to it
              eventProducts.get(productType).add(pSummary);
            } else {
              List<ProductSummary> newList = new ArrayList<ProductSummary>();
              newList.add(pSummary);
              eventProducts.put(productType, newList);
            }
          } else if (query != null && query.getType() == SearchMethod.PRODUCTS_SUMMARY) {
            // This was a product summary query and these are its
            // results
            ProductsSummaryQuery psQuery = (ProductsSummaryQuery) query;
            psQuery.getResult().add(pSummary);
          } else {
            throw new SAXException("productSummary element encountered without recognized parent");
          }
          pSummary = null;
        }
      } else if (SearchXML.EVENT_ELEMENT.equals(localName)) {
        if (query != null && query.getType() == SearchMethod.EVENT_DETAIL) {
          // This was an event detail query and has opened properly
          EventDetailQuery edQuery = (EventDetailQuery) query;
          edQuery.getResult().add(event);
          event = null;
        } else {
          throw new SAXException("event element encountered without recognized parent");
        }
      } else if (SearchXML.EVENT_SUMMARY_ELEMENT.equals(localName)) {
        if (query != null && query.getType() == SearchMethod.EVENTS_SUMMARY) {
          // This was an event summary query and has opened properly
          EventsSummaryQuery esQuery = (EventsSummaryQuery) query;
          esQuery.getResult().add(eSummary);
          esQuery = null;
        } else {
          throw new SAXException("eventSummary element encountered without recognized parent");
        }
      } else if (SearchXML.ERROR_ELEMENT.equals(localName)) {
        inErrorElement = false;
      }
    } else if (XmlProductHandler.PRODUCT_XML_NAMESPACE.equals(uri)) {
      if (XmlProductHandler.PROPERTY_ELEMENT.equals(localName)) {
        // Simple tag is handled when the start element is encountered
      } else if (XmlProductHandler.LINK_ELEMENT.equals(localName)) {
        // Simple tag is handled when the start element is encountered
      }
    }
  }

  @Override
  public void characters(char[] ch, int start, int length) throws SAXException {
    if (productHandler != null) {
      // Pass through if product is being generated
      productHandler.characters(ch, start, length);
    } else if (inErrorElement) {
      query.setError(new String(ch));
    }
  }

}