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));
		}
	}

}