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(), product.getTrackerURL());
					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;
	}

}