SearchXML.java

package gov.usgs.earthquake.indexer;

import gov.usgs.earthquake.distribution.FileProductStorage;
import gov.usgs.earthquake.product.Content;
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 java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Formatter for Search XML.
 */
public class SearchXML {

  /** Location of Indexer on distribution site */
  public static final String INDEXER_XMLNS = "http://earthquake.usgs.gov/distribution/indexer";

  /** Element for searchRequests */
  public static final String REQUEST_ELEMENT = "searchrequest";
  /** Element for searchResponses */
  public static final String RESPONSE_ELEMENT = "searchresponse";

  /** Element for results */
  public static final String RESULT_ELEMENT = "result";
  /** Element for queries */
  public static final String QUERY_ELEMENT = "query";
  /** Element for events */
  public static final String EVENT_ELEMENT = "event";
  /** Element for errors */
  public static final String ERROR_ELEMENT = "error";

  /** Attribute for methods */
  public static final String METHOD_ATTRIBUTE = "method";

  /** Attribute for event source */
  public static final String EVENT_SOURCE_ATTRIBUTE = "eventSource";
  /** Attribute for event source code */
  public static final String EVENT_SOURCE_CODE_ATTRIBUTE = "eventSourceCode";
  /** Attribute for min event time */
  public static final String MIN_EVENT_TIME_ATTRIBUTE = "minEventTime";
  /** Attribute for max event time */
  public static final String MAX_EVENT_TIME_ATTRIBUTE = "maxEventTime";
  /** Attribute for min event latitude */
  public static final String MIN_EVENT_LATITUDE_ATTRIBUTE = "minEventLatitude";
  /** Attribute for max event latitude */
  public static final String MAX_EVENT_LATITUDE_ATTRIBUTE = "maxEventLatitude";
  /** Attribute for min event longitude */
  public static final String MIN_EVENT_LONGITUDE_ATTRIBUTE = "minEventLongitude";
  /** Attribute for max event longitude */
  public static final String MAX_EVENT_LONGITUDE_ATTRIBUTE = "maxEventLongitude";
  /** Attribute for min event depth */
  public static final String MIN_EVENT_DEPTH_ATTRIBUTE = "minEventDepth";
  /** Attribute for max event depth */
  public static final String MAX_EVENT_DEPTH_ATTRIBUTE = "maxEventDepth";
  /** Attribute for min event magnitude */
  public static final String MIN_EVENT_MAGNITUDE_ATTRIBUTE = "minEventMagnitude";
  /** Attribute for max event magnitude */
  public static final String MAX_EVENT_MAGNITUDE_ATTRIBUTE = "maxEventMagnitude";
  /** Attribute for min product update time */
  public static final String MIN_PRODUCT_UPDATE_TIME_ATTRIBUTE = "minProductUpdateTime";
  /** Attribute for max product update time */
  public static final String MAX_PRODUCT_UPDATE_TIME_ATTRIBUTE = "maxProductUpdateTime";
  /** Attribute for product source */
  public static final String PRODUCT_SOURCE_ATTRIBUTE = "productSource";
  /** Attribute for product type */
  public static final String PRODUCT_TYPE_ATTRIBUTE = "productType";
  /** Attribute for product code */
  public static final String PRODUCT_CODE_ATTRIBUTE = "productCode";
  /** Attribute for product version */
  public static final String PRODUCT_VERSION_ATTRIBUTE = "productVersion";
  /** Attribute for product Status */
  public static final String PRODUCT_STATUS_ATTRIBUTE = "productStatus";

  /** Element for event summary */
  public static final String EVENT_SUMMARY_ELEMENT = "eventSummary";
  /** Element for product summary */
  public static final String PRODUCT_SUMMARY_ELEMENT = "productSummary";

  /** Attribute for id */
  public static final String ID_ATTRIBUTE = "id";
  /** Attribute for update time */
  public static final String UPDATE_TIME_ATTRIBUTE = "updateTime";
  /** Attribute for status */
  public static final String STATUS_ATTRIBUTE = "status";
  /** Attribute for source */
  public static final String SOURCE_ATTRIBUTE = "source";
  /** Attribute for source code */
  public static final String SOURCE_CODE_ATTRIBUTE = "sourceCode";
  /** Attribute for time */
  public static final String TIME_ATTRIBUTE = "time";
  /** Attribute for latitude */
  public static final String LATITUDE_ATTRIBUTE = "latitude";
  /** Attribute for longitude */
  public static final String LONGITUDE_ATTRIBUTE = "longitude";
  /** Attribute for depth */
  public static final String DEPTH_ATTRIBUTE = "depth";
  /** Attribute for magnitude */
  public static final String MAGNITUDE_ATTRIBUTE = "magnitude";
  /** Attribute for version */
  public static final String VERSION_ATTRIBUTE = "version";
  /** Attribute for preferred weight */
  public static final String PREFERRED_WEIGHT_ATTRIBUTE = "preferredWeight";

  /**
   * Parse an input stream with xml to a SearchRequest object.
   *
   * @param in the input stream containing xml.
   * @return the parsed SearchRequest object.
   * @throws Exception if error occurs
   */
  public static SearchRequest parseRequest(final InputStream in) throws Exception {
    SearchRequestParser parser = new SearchRequestParser();
    XmlUtils.parse(in, parser);
    return parser.getSearchRequest();
  }

  /**
   * Parse an input stream with xml to a SearchResponse object.
   *
   * @param in      the input stream containing xml.
   * @param storage the storage where received products are stored.
   * @return the parsed SearchResponse object.
   * @throws Exception if error occurs
   */
  public static SearchResponse parseResponse(final InputStream in, final FileProductStorage storage) throws Exception {
    SearchResponseParser parser = new SearchResponseParser(storage);
    XmlUtils.parse(in, parser);
    return parser.getSearchResponse();
  }

  /**
   * Convert a SearchRequest object to xml.
   *
   * @param request the search request object to convert.
   * @param out     the output stream where xml is written.
   * @throws Exception if error occurs
   */
  public static void toXML(final SearchRequest request, final OutputStream out) throws Exception {
    OutputStreamWriter outStream = new OutputStreamWriter(out);
    outStream.write("<?xml version=\"1.0\"?>");
    outStream.write("<" + REQUEST_ELEMENT);
    outStream.write(" xmlns=\"" + INDEXER_XMLNS + "\"");
    outStream.write(">");
    List<SearchQuery> queries = request.getQueries();
    for (Iterator<SearchQuery> queryIterator = queries.iterator(); queryIterator.hasNext();) {
      SearchQuery query = queryIterator.next();
      outStream.write(getQueryXMLString(query));
    }
    outStream.write("</" + REQUEST_ELEMENT + ">");
    outStream.flush();
  }

  /**
   * Convert a SearchResponse object to xml.
   *
   * @param response the search response object to convert.
   * @param out      the output stream where xml is written.
   * @throws Exception if error occurs
   */
  public static void toXML(final SearchResponse response, final OutputStream out) throws Exception {
    OutputStreamWriter writer = new OutputStreamWriter(out);
    writer.write("<?xml version=\"1.0\"?>");
    writer.write("<" + RESPONSE_ELEMENT);
    writer.write(" xmlns=\"" + INDEXER_XMLNS + "\"");
    writer.write(" xmlns:product=\"" + XmlProductHandler.PRODUCT_XML_NAMESPACE + "\"");
    writer.write(">");
    List<SearchQuery> results = response.getResults();
    for (Iterator<SearchQuery> resultsIterator = results.iterator(); resultsIterator.hasNext();) {
      SearchQuery result = resultsIterator.next();
      writer.write("<" + RESULT_ELEMENT);
      writer.write(" " + METHOD_ATTRIBUTE + "=\"" + result.getType().getXmlMethodName() + "\"");
      writer.write(">");
      writer.write(getQueryXMLString(result));

      if (result.getType() == SearchMethod.EVENT_DETAIL) {
        EventDetailQuery edResult = (EventDetailQuery) result;
        List<Event> events = edResult.getResult();
        for (Iterator<Event> eventIter = events.iterator(); eventIter.hasNext();) {
          Event event = eventIter.next();
          writer.write("<" + EVENT_ELEMENT);
          if (event.getSource() != null)
            writer.write(" " + SOURCE_ATTRIBUTE + "=\"" + event.getSource() + "\"");
          if (event.getSourceCode() != null)
            writer.write(" " + SOURCE_CODE_ATTRIBUTE + "=\"" + event.getSourceCode() + "\"");
          if (event.getTime() != null)
            writer.write(" " + TIME_ATTRIBUTE + "=\"" + XmlUtils.formatDate(event.getTime()) + "\"");
          if (event.getLatitude() != null)
            writer.write(" " + LATITUDE_ATTRIBUTE + "=\"" + event.getLatitude().toString() + "\"");
          if (event.getLongitude() != null)
            writer.write(" " + LONGITUDE_ATTRIBUTE + "=\"" + event.getLongitude().toString() + "\"");
          if (event.getDepth() != null)
            writer.write(" " + DEPTH_ATTRIBUTE + "=\"" + event.getDepth().toString() + "\"");
          if (event.getMagnitude() != null)
            writer.write(" " + MAGNITUDE_ATTRIBUTE + "=\"" + event.getMagnitude().toString() + "\"");
          writer.write(">");
          if (!event.getProducts().isEmpty()) {
            Map<String, List<ProductSummary>> products = event.getProducts();
            for (Iterator<String> prodIter = products.keySet().iterator(); prodIter.hasNext();) {
              String prodType = prodIter.next();
              for (Iterator<ProductSummary> summaryIter = products.get(prodType).iterator(); summaryIter.hasNext();) {
                writer.write(getProductSummaryXmlString(summaryIter.next()));
              }
            }
          }
          writer.write("</" + EVENT_ELEMENT + ">");
        }
      } else if (result.getType() == SearchMethod.EVENTS_SUMMARY) {
        EventsSummaryQuery esResult = (EventsSummaryQuery) result;
        List<EventSummary> summaries = esResult.getResult();
        for (Iterator<EventSummary> summaryIter = summaries.iterator(); summaryIter.hasNext();) {
          writer.write("<" + EVENT_SUMMARY_ELEMENT);
          EventSummary summary = summaryIter.next();
          if (summary.getSource() != null)
            writer.write(" " + SOURCE_ATTRIBUTE + "=\"" + summary.getSource() + "\"");
          if (summary.getSourceCode() != null)
            writer.write(" " + SOURCE_CODE_ATTRIBUTE + "=\"" + summary.getSourceCode() + "\"");
          if (summary.getTime() != null)
            writer.write(" " + TIME_ATTRIBUTE + "=\"" + XmlUtils.formatDate(summary.getTime()) + "\"");
          if (summary.getLatitude() != null)
            writer.write(" " + LATITUDE_ATTRIBUTE + "=\"" + summary.getLatitude().toString() + "\"");
          if (summary.getLongitude() != null)
            writer.write(" " + LONGITUDE_ATTRIBUTE + "=\"" + summary.getLongitude().toString() + "\"");
          if (summary.getDepth() != null)
            writer.write(" " + DEPTH_ATTRIBUTE + "=\"" + summary.getDepth().toString() + "\"");
          if (summary.getMagnitude() != null)
            writer.write(" " + MAGNITUDE_ATTRIBUTE + "=\"" + summary.getMagnitude().toString() + "\"");
          writer.write(">");
          if (!summary.getProperties().isEmpty()) {
            Map<String, String> properties = summary.getProperties();
            for (Iterator<String> propIter = properties.keySet().iterator(); propIter.hasNext();) {
              String property = propIter.next();
              String value = properties.get(property);
              writer.write("<product:" + XmlProductHandler.PROPERTY_ELEMENT);
              writer.write(" " + XmlProductHandler.PROPERTY_ATTRIBUTE_NAME + "=\"" + property + "\"");
              writer.write(" " + XmlProductHandler.PROPERTY_ATTRIBUTE_VALUE + "=\"" + value + "\"");
              writer.write(" />");
            }
          }
          writer.write("</" + EVENT_SUMMARY_ELEMENT + ">");
        }
      } else if (result.getType() == SearchMethod.PRODUCT_DETAIL) {
        ProductDetailQuery pdResult = (ProductDetailQuery) result;
        List<Product> products = pdResult.getResult();
        writer.flush();
        for (Iterator<Product> prodIter = products.iterator(); prodIter.hasNext();) {
          Product product = prodIter.next();
          XmlProductHandler handler = new XmlProductHandler(out, false);
          handler.onBeginProduct(product.getId(), product.getStatus());
          Map<String, String> properties = product.getProperties();
          for (Iterator<String> propIter = properties.keySet().iterator(); propIter.hasNext();) {
            String name = propIter.next();
            handler.onProperty(product.getId(), name, properties.get(name));
          }
          Map<String, List<URI>> links = product.getLinks();
          for (Iterator<String> relIter = links.keySet().iterator(); relIter.hasNext();) {
            String relation = relIter.next();
            for (Iterator<URI> uriIter = links.get(relation).iterator(); uriIter.hasNext();) {
              URI href = uriIter.next();
              handler.onLink(product.getId(), relation, href);
            }
          }
          Map<String, Content> contents = product.getContents();
          for (Iterator<String> pathIter = contents.keySet().iterator(); pathIter.hasNext();) {
            String path = pathIter.next();
            handler.onContent(product.getId(), path, contents.get(path));
          }
          if (product.getSignature() != null)
            handler.onSignature(product.getId(), product.getSignature());
          handler.onEndProduct(product.getId());
        }
      } else if (result.getType() == SearchMethod.PRODUCTS_SUMMARY) {
        ProductsSummaryQuery psQuery = (ProductsSummaryQuery) result;
        List<ProductSummary> summaries = psQuery.getResult();
        for (Iterator<ProductSummary> summaryIter = summaries.iterator(); summaryIter.hasNext();) {
          writer.write(getProductSummaryXmlString(summaryIter.next()));
        }
      }

      writer.write("</" + RESULT_ELEMENT + ">");
    }
    writer.write("</" + RESPONSE_ELEMENT + ">");
    writer.flush();
  }

  /**
   * Private helper method to convert query elements into XML
   *
   * @param query the query element to convert to xml
   * @return an xml string representing the query object
   */
  private static String getQueryXMLString(SearchQuery query) {
    StringBuffer queryXmlString = new StringBuffer();
    queryXmlString.append("<" + QUERY_ELEMENT);
    queryXmlString.append(" " + METHOD_ATTRIBUTE + "=\"");
    queryXmlString.append(query.getType().getXmlMethodName());
    queryXmlString.append("\"");
    ProductIndexQuery prodIndexQuery = query.getProductIndexQuery();
    if (prodIndexQuery.getEventSource() != null)
      queryXmlString.append(" " + EVENT_SOURCE_ATTRIBUTE + "=\"" + prodIndexQuery.getEventSource() + "\"");
    if (prodIndexQuery.getEventSourceCode() != null)
      queryXmlString.append(" " + EVENT_SOURCE_CODE_ATTRIBUTE + "=\"" + prodIndexQuery.getEventSourceCode() + "\"");
    if (prodIndexQuery.getMinEventTime() != null)
      queryXmlString.append(
          " " + MIN_EVENT_TIME_ATTRIBUTE + "=\"" + XmlUtils.formatDate(prodIndexQuery.getMinEventTime()) + "\"");
    if (prodIndexQuery.getMaxEventTime() != null)
      queryXmlString.append(
          " " + MAX_EVENT_TIME_ATTRIBUTE + "=\"" + XmlUtils.formatDate(prodIndexQuery.getMaxEventTime()) + "\"");
    if (prodIndexQuery.getMinEventLatitude() != null)
      queryXmlString.append(" " + MIN_EVENT_LATITUDE_ATTRIBUTE + "=\"" + prodIndexQuery.getMinEventLatitude() + "\"");
    if (prodIndexQuery.getMaxEventLatitude() != null)
      queryXmlString.append(" " + MAX_EVENT_LATITUDE_ATTRIBUTE + "=\"" + prodIndexQuery.getMaxEventLatitude() + "\"");
    if (prodIndexQuery.getMinEventLongitude() != null)
      queryXmlString.append(" " + MIN_EVENT_LONGITUDE_ATTRIBUTE + "=\"" + prodIndexQuery.getMinEventLongitude() + "\"");
    if (prodIndexQuery.getMaxEventLongitude() != null)
      queryXmlString.append(" " + MAX_EVENT_LONGITUDE_ATTRIBUTE + "=\"" + prodIndexQuery.getMaxEventLongitude() + "\"");
    if (prodIndexQuery.getMinEventDepth() != null)
      queryXmlString.append(" " + MIN_EVENT_DEPTH_ATTRIBUTE + "=\"" + prodIndexQuery.getMinEventDepth() + "\"");
    if (prodIndexQuery.getMaxEventDepth() != null)
      queryXmlString.append(" " + MAX_EVENT_DEPTH_ATTRIBUTE + "=\"" + prodIndexQuery.getMaxEventDepth() + "\"");
    if (prodIndexQuery.getMinEventMagnitude() != null)
      queryXmlString.append(" " + MIN_EVENT_MAGNITUDE_ATTRIBUTE + "=\"" + prodIndexQuery.getMinEventMagnitude() + "\"");
    if (prodIndexQuery.getMaxEventMagnitude() != null)
      queryXmlString.append(" " + MAX_EVENT_MAGNITUDE_ATTRIBUTE + "=\"" + prodIndexQuery.getMaxEventMagnitude() + "\"");
    if (prodIndexQuery.getMinProductUpdateTime() != null)
      queryXmlString.append(" " + MIN_PRODUCT_UPDATE_TIME_ATTRIBUTE + "=\""
          + XmlUtils.formatDate(prodIndexQuery.getMinProductUpdateTime()) + "\"");
    if (prodIndexQuery.getMaxProductUpdateTime() != null)
      queryXmlString.append(" " + MAX_PRODUCT_UPDATE_TIME_ATTRIBUTE + "=\""
          + XmlUtils.formatDate(prodIndexQuery.getMaxProductUpdateTime()) + "\"");
    if (prodIndexQuery.getProductSource() != null)
      queryXmlString.append(" " + PRODUCT_SOURCE_ATTRIBUTE + "=\"" + prodIndexQuery.getProductSource() + "\"");
    if (prodIndexQuery.getProductType() != null)
      queryXmlString.append(" " + PRODUCT_TYPE_ATTRIBUTE + "=\"" + prodIndexQuery.getProductType() + "\"");
    if (prodIndexQuery.getProductCode() != null)
      queryXmlString.append(" " + PRODUCT_CODE_ATTRIBUTE + "=\"" + prodIndexQuery.getProductCode() + "\"");
    if (prodIndexQuery.getProductVersion() != null)
      queryXmlString.append(" " + PRODUCT_VERSION_ATTRIBUTE + "=\"" + prodIndexQuery.getProductVersion() + "\"");
    if (prodIndexQuery.getProductStatus() != null)
      queryXmlString.append(" " + PRODUCT_STATUS_ATTRIBUTE + "=\"" + prodIndexQuery.getProductStatus() + "\"");

    queryXmlString.append(">");

    Iterator<ProductId> ids = prodIndexQuery.getProductIds().iterator();
    while (ids.hasNext()) {
      queryXmlString.append("<" + PRODUCT_SUMMARY_ELEMENT);
      queryXmlString.append(" " + ID_ATTRIBUTE + "=\"" + ids.next().toString() + "\"");
      queryXmlString.append(" />");
    }

    queryXmlString.append("</" + QUERY_ELEMENT + ">");

    return queryXmlString.toString();
  }

  /**
   * Private helper method to convert product summary elements into XML
   *
   * @param summary the product summary element to convert to xml
   * @return an xml string representing the product summary object
   */
  private static String getProductSummaryXmlString(ProductSummary summary) {
    String summaryXmlString = "";
    summaryXmlString += "<" + PRODUCT_SUMMARY_ELEMENT;
    if (summary.getId() != null)
      summaryXmlString += " " + ID_ATTRIBUTE + "=\"" + summary.getId().toString() + "\"";
    if (summary.getId().getUpdateTime() != null)
      summaryXmlString += " " + UPDATE_TIME_ATTRIBUTE + "=\"" + XmlUtils.formatDate(summary.getId().getUpdateTime())
          + "\"";
    if (summary.getStatus() != null)
      summaryXmlString += " " + STATUS_ATTRIBUTE + "=\"" + summary.getStatus() + "\"";
    if (summary.getSource() != null)
      summaryXmlString += " " + SOURCE_ATTRIBUTE + "=\"" + summary.getSource() + "\"";
    if (summary.getCode() != null)
      summaryXmlString += " " + SOURCE_CODE_ATTRIBUTE + "=\"" + summary.getCode() + "\"";
    if (summary.getEventTime() != null)
      summaryXmlString += " " + TIME_ATTRIBUTE + "=\"" + XmlUtils.formatDate(summary.getEventTime()) + "\"";
    if (summary.getEventLatitude() != null)
      summaryXmlString += " " + LATITUDE_ATTRIBUTE + "=\"" + summary.getEventLatitude().toString() + "\"";
    if (summary.getEventLongitude() != null)
      summaryXmlString += " " + LONGITUDE_ATTRIBUTE + "=\"" + summary.getEventLongitude().toString() + "\"";
    if (summary.getEventDepth() != null)
      summaryXmlString += " " + DEPTH_ATTRIBUTE + "=\"" + summary.getEventDepth().toString() + "\"";
    if (summary.getEventMagnitude() != null)
      summaryXmlString += " " + MAGNITUDE_ATTRIBUTE + "=\"" + summary.getEventMagnitude().toString() + "\"";
    if (summary.getVersion() != null)
      summaryXmlString += " " + VERSION_ATTRIBUTE + "=\"" + summary.getVersion() + "\"";
    summaryXmlString += " " + PREFERRED_WEIGHT_ATTRIBUTE + "=\"" + summary.getPreferredWeight() + "\"";
    summaryXmlString += ">";

    Map<String, String> properties = summary.getProperties();
    for (Iterator<String> propIter = properties.keySet().iterator(); propIter.hasNext();) {
      String name = propIter.next();
      String value = properties.get(name);
      summaryXmlString += "<product:" + XmlProductHandler.PROPERTY_ELEMENT;
      summaryXmlString += " " + XmlProductHandler.PROPERTY_ATTRIBUTE_NAME + "=\"" + name + "\"";
      summaryXmlString += " " + XmlProductHandler.PROPERTY_ATTRIBUTE_VALUE + "=\"" + value + "\"";
      summaryXmlString += " />";
    }

    Map<String, List<URI>> links = summary.getLinks();
    for (Iterator<String> linkIter = links.keySet().iterator(); linkIter.hasNext();) {
      String relation = linkIter.next();
      for (Iterator<URI> uriIter = links.get(relation).iterator(); uriIter.hasNext();) {
        URI href = uriIter.next();
        summaryXmlString += "<product:" + XmlProductHandler.LINK_ELEMENT;
        summaryXmlString += " " + XmlProductHandler.LINK_ATTRIBUTE_RELATION + "=\"" + relation + "\"";
        summaryXmlString += " " + XmlProductHandler.LINK_ATTRIBUTE_HREF + "=\"" + href + "\"";
        summaryXmlString += " />";
      }
    }

    summaryXmlString += "</" + PRODUCT_SUMMARY_ELEMENT + ">";
    return summaryXmlString;
  }

}