XmlProductHandler.java

/*
 * XmlProductHandler
 */
package gov.usgs.earthquake.product.io;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.util.Base64;

import gov.usgs.util.StreamUtils;
import gov.usgs.util.XmlUtils;
import gov.usgs.util.CryptoUtils.Version;
import gov.usgs.earthquake.product.Content;
import gov.usgs.earthquake.product.URLContent;
import gov.usgs.earthquake.product.ProductId;


/**
 * Store a product to an OutputStream using XML.
 */
public class XmlProductHandler implements ProductHandler {

	/** Declaration for XML and version */
	public static final String XML_DECLARATION = "<?xml version=\"1.0\"?>\n";
	/** Namespace for XML product */
	public static final String PRODUCT_XML_NAMESPACE = "http://earthquake.usgs.gov/distribution/product";

	/** Element for product */
	public static final String PRODUCT_ELEMENT = "product";
	/** Product Attribute for id */
	public static final String PRODUCT_ATTRIBUTE_ID = "id";
	/** Product Attribute for updateTime */
	public static final String PRODUCT_ATTRIBUTE_UPDATED = "updateTime";
	/** Product Attribute for status */
	public static final String PRODUCT_ATTRIBUTE_STATUS = "status";
	/** Product Attribute for trackerURL */
	public static final String PRODUCT_ATTRIBUTE_TRACKER_URL = "trackerURL";

	/** Element for property */
	public static final String PROPERTY_ELEMENT = "property";
	/** Property attribute for name */
	public static final String PROPERTY_ATTRIBUTE_NAME = "name";
	/** Property attribute for value */
	public static final String PROPERTY_ATTRIBUTE_VALUE = "value";

	/** Element for link */
	public static final String LINK_ELEMENT = "link";
	/** Link attribute for relation */
	public static final String LINK_ATTRIBUTE_RELATION = "rel";
	/** Link attribute for href */
	public static final String LINK_ATTRIBUTE_HREF = "href";

	/** Element for content */
	public static final String CONTENT_ELEMENT = "content";
	/** Content attribute for path */
	public static final String CONTENT_ATTRIBUTE_PATH = "path";
	/** Content attribute for type */
	public static final String CONTENT_ATTRIBUTE_TYPE = "type";
	/** Content attribute for length */
	public static final String CONTENT_ATTRIBUTE_LENGTH = "length";
	/** Content attribute for modified */
	public static final String CONTENT_ATTRIBUTE_MODIFIED = "modified";
	/** Used with URLContent. */
	public static final String CONTENT_ATTRIBUTE_HREF = "href";
	/** Content attribute for encoded */
	public static final String CONTENT_ATTRIBUTE_ENCODED = "encoded";

	/** Element for signature */
	public static final String SIGNATURE_ELEMENT = "signature";
	/** Signature attribute for version */
	public static final String SIGNATURE_ATTRIBUTE_VERSION = "version";

	/** The OutputStream where xml is written. */
	private OutputStream out;

	/** Controls whether the XML Declaration is output with the XML. */
	private boolean includeDeclaration = true;

	/** Signature version. */
	private Version signatureVersion = Version.SIGNATURE_V1;

	/**
	 * Create a new XmlProductHandler object.
	 *
	 * @param out
	 *            the OutputStream where xml will be written.
	 */
	public XmlProductHandler(final OutputStream out) {
		this.out = out;
	}

	/**
	 * Create a new XmlProductHandler object.
	 *
	 * @param out
	 *            the OutputStream where xml will be written.
	 * @param includeDeclaration
	 * 			  whether to include the XML declaration with output
	 */
	public XmlProductHandler(final OutputStream out, boolean includeDeclaration) {
		this.out = out;
		this.includeDeclaration = includeDeclaration;
	}

	/**
	 * Output the product root element.
	 */
	public void onBeginProduct(ProductId id, String status, URL trackerURL)
			throws Exception {
		StringBuffer buf = new StringBuffer();
		if (includeDeclaration) {
			buf.append(XML_DECLARATION);
		}
		buf.append("<").append(PRODUCT_ELEMENT);
		buf.append(" xmlns=\"").append(PRODUCT_XML_NAMESPACE).append("\"");
		buf.append(" ").append(PRODUCT_ATTRIBUTE_ID).append("=\"")
				.append(XmlUtils.escape(id.toString())).append("\"");
		buf.append(" ").append(PRODUCT_ATTRIBUTE_UPDATED).append("=\"")
				.append(XmlUtils.formatDate(id.getUpdateTime())).append("\"");
		buf.append(" ").append(PRODUCT_ATTRIBUTE_STATUS).append("=\"")
				.append(XmlUtils.escape(status)).append("\"");
		if (trackerURL != null) {
			buf.append(" ").append(PRODUCT_ATTRIBUTE_TRACKER_URL).append("=\"")
					.append(trackerURL.toExternalForm()).append("\"");
		}
		buf.append(">\n");

		out.write(buf.toString().getBytes());
	}

	/**
	 * Output a content object as xml.
	 */
	public void onContent(ProductId id, String path, Content content)
			throws Exception {
		// open element
		StringBuffer buf = new StringBuffer();
		buf.append("\t<").append(CONTENT_ELEMENT);
		buf.append(" ").append(CONTENT_ATTRIBUTE_PATH).append("=\"")
				.append(XmlUtils.escape(path)).append("\"");
		buf.append(" ").append(CONTENT_ATTRIBUTE_TYPE).append("=\"")
				.append(XmlUtils.escape(content.getContentType())).append("\"");
		buf.append(" ").append(CONTENT_ATTRIBUTE_LENGTH).append("=\"")
				.append(content.getLength()).append("\"");
		buf.append(" ").append(CONTENT_ATTRIBUTE_MODIFIED).append("=\"")
				.append(XmlUtils.formatDate(content.getLastModified()))
				.append("\"");

		if (content instanceof URLContent) {
			// URL CONTENT
			buf.append(" ").append(CONTENT_ATTRIBUTE_HREF).append("=\"")
					.append(((URLContent) content).getURL().toString())
					.append("\"");

			// close element early, url is alternative to embedded content
			buf.append("/>");
			out.write(buf.toString().getBytes());
			return;
		}

		else {
			// EMBEDDED CONTENT
			buf.append(" ").append(CONTENT_ATTRIBUTE_ENCODED)
					.append("=\"true\"");

			buf.append(">");
			out.write(buf.toString().getBytes());

			InputStream in = null;
			OutputStream base64out = null;
			try {
				in = content.getInputStream();
				base64out = Base64.getEncoder().wrap(
						new StreamUtils.UnclosableOutputStream(out));
				// write element content
				StreamUtils.transferStream(in, base64out);
			} finally {
				StreamUtils.closeStream(in);
				StreamUtils.closeStream(base64out);
			}

			// close element
			buf = new StringBuffer();
			buf.append("</").append(CONTENT_ELEMENT).append(">\n");
			out.write(buf.toString().getBytes());
		}
	}

	/**
	 * Output the closing product element.
	 */
	public void onEndProduct(ProductId id) throws Exception {
		StringBuffer buf = new StringBuffer();
		buf.append("</").append(PRODUCT_ELEMENT).append(">\n");

		out.write(buf.toString().getBytes());
		out.flush();
		out.close();
	}

	/**
	 * Output a link element as xml.
	 */
	public void onLink(ProductId id, String relation, URI href)
			throws Exception {
		StringBuffer buf = new StringBuffer();
		buf.append("\t<").append(LINK_ELEMENT);
		buf.append(" ").append(LINK_ATTRIBUTE_RELATION).append("=\"")
				.append(XmlUtils.escape(relation)).append("\"");
		buf.append(" ").append(LINK_ATTRIBUTE_HREF).append("=\"")
				.append(XmlUtils.escape(href.toString())).append("\"");
		buf.append("/>\n");

		out.write(buf.toString().getBytes());
	}

	/**
	 * Output the property element as xml.
	 */
	public void onProperty(ProductId id, String name, String value)
			throws Exception {
		StringBuffer buf = new StringBuffer();
		buf.append("\t<").append(PROPERTY_ELEMENT);
		buf.append(" ").append(PROPERTY_ATTRIBUTE_NAME).append("=\"")
				.append(XmlUtils.escape(name)).append("\"");
		buf.append(" ").append(PROPERTY_ATTRIBUTE_VALUE).append("=\"")
				.append(XmlUtils.escape(value)).append("\"");
		buf.append("/>\n");

		out.write(buf.toString().getBytes());
	}

	public void onSignatureVersion(ProductId id, Version version) throws Exception {
		// save for output during onSignature
		this.signatureVersion = version;
	}

	/**
	 * Output the signature element as xml.
	 */
	public void onSignature(ProductId id, String signature) throws Exception {
		if (signature == null) {
			return;
		}

		StringBuffer buf = new StringBuffer();
		buf.append("\t<").append(SIGNATURE_ELEMENT);
		buf.append(" ").append(SIGNATURE_ATTRIBUTE_VERSION).append("=\"")
				.append(signatureVersion.toString()).append("\"");
		buf.append(">");
		buf.append(signature);
		buf.append("</").append(SIGNATURE_ELEMENT).append(">\n");

		out.write(buf.toString().getBytes());
	}

	/**
	 * Free any resources associated with this handler.
	 */
	@Override
	public void close() {
		StreamUtils.closeStream(out);
	}


}