BinaryProductHandler.java

package gov.usgs.earthquake.product.io;

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

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

/**
 * Generator of binary format for product data.
 *
 * Binary representation of data types:
 * <dl>
 * <dt>Integer</dt>
 * <dd>4-bytes</dd>
 * <dt>Long</dt>
 * <dd>8-bytes</dd>
 * <dt>Date</dt>
 * <dd>Long (Date.getTime())</dd>
 * <dt>byte[]</dt>
 * <dd>Integer length, raw bytes</dd>
 * <dt>String</dt>
 * <dd>byte[] (String.getBytes(StandardCharsets.UTF_8))</dd>
 * <dt>URL/URI</dt>
 * <dd>String (URL.toString())</dd>
 * </dl>
 *
 *
 * Product is stored in this order:
 *
 * <ol>
 *
 * <li>Header, exactly 1
 * <ol>
 * <li>"BEGINPRODUCT" (string)</li>
 * <li>ProductId (String)</li>
 * <li>Status (String)</li>
 * <li>TrackerURL (URL)</li>
 * </ol>
 * </li>
 *
 * <li>Properties, 0 to many:
 * <ol>
 * <li>"PROPERTY" (String)</li>
 * <li>name (String)</li>
 * <li>value (String)</li>
 * </ol>
 * </li>
 *
 * <li>Links, 0 to many:
 * <ol>
 * <li>"LINK" (String)</li>
 * <li>relation (String)</li>
 * <li>href (URI)</li>
 * </ol>
 * </li>
 *
 * <li>Contents, 0 to many:
 * <ol>
 * <li>"CONTENT" (String)</li>
 * <li>path (String)</li>
 * <li>contentType (String)</li>
 * <li>lastModified (Date)</li>
 * <li>length (Long)</li>
 * <li>raw bytes</li>
 * </ol>
 * </li>
 *
 * <li>Signature Version, 0 or 1.
 * <em>Note, only sent when version != SIGNATURE_V1 for backward compatibility</em>
 * <ol>
 * <li>"SIGNATUREVERSION" (String)</li>
 * <li>version (String)</li>
 * </ol>
 * </li>
 *
 * <li>Signature, 0 or 1:
 * <ol>
 * <li>"SIGNATURE" (String)</li>
 * <li>signature (String)</li>
 * </ol>
 * </li>
 *
 * <li>Footer, exactly 1:
 * <ol>
 * <li>"ENDPRODUCT" (String)</li>
 * </ol>
 * </li>
 *
 * </ol>
 */
public class BinaryProductHandler implements ProductHandler {

	/** HEADER - BEGINPRODUCT */
	public static final String HEADER = "BEGINPRODUCT";
	/** PROPERTY */
	public static final String PROPERTY = "PROPERTY";
	/** LINK */
	public static final String LINK = "LINK";
	/** CONTENT */
	public static final String CONTENT = "CONTENT";
	/** SIGNATURE VERSION */
	public static final String SIGNATUREVERSION = "SIGNATUREVERSION";
	/** SIGNATURE */
	public static final String SIGNATURE = "SIGNATURE";
	/** ENDPRODUCT */
	public static final String FOOTER = "ENDPRODUCT";

	private OutputStream out;
	private BinaryIO io;

	/**
	 * Constructor. Sets up a new BinaryIO
	 * @param out an OutputStream
	 */
	public BinaryProductHandler(final OutputStream out) {
		this.out = out;
		this.io = new BinaryIO();
	}

	@Override
	public void onBeginProduct(ProductId id, String status, URL trackerURL)
			throws Exception {
		io.writeString(HEADER, out);
		io.writeString(id.toString(), out);
		io.writeString(status, out);
		// allow trackerURL to be null
		if (trackerURL == null) {
			io.writeString("null", out);
		} else {
			io.writeString(trackerURL.toString(), out);
		}
	}

	@Override
	public void onProperty(ProductId id, String name, String value)
			throws Exception {
		io.writeString(PROPERTY, out);
		io.writeString(name, out);
		io.writeString(value, out);
	}

	@Override
	public void onLink(ProductId id, String relation, URI href)
			throws Exception {
		io.writeString(LINK, out);
		io.writeString(relation, out);
		io.writeString(href.toString(), out);
	}

	@Override
	public void onContent(ProductId id, String path, Content content)
			throws Exception {
		if (content.getLength() == null || content.getLength() < 0) {
			// binary io only handles streams with length, convert to content
			// with length
			content = new ByteContent(content);
		}

		io.writeString(CONTENT, out);
		io.writeString(path, out);

		io.writeString(content.getContentType(), out);
		io.writeDate(content.getLastModified(), out);
		InputStream contentInputStream = content.getInputStream();
		try {
			io.writeStream(content.getLength().longValue(), contentInputStream, out);
		} finally {
			StreamUtils.closeStream(contentInputStream);
		}
	}

	@Override
	public void onSignatureVersion(ProductId id, Version version) throws Exception {
		if (version != Version.SIGNATURE_V1) {
			io.writeString(SIGNATUREVERSION, out);
			io.writeString(version.toString(), out);
		}
	}

	@Override
	public void onSignature(ProductId id, String signature) throws Exception {
		// allow signature to be null
		if (signature == null) {
			return;
		}

		io.writeString(SIGNATURE, out);
		io.writeString(signature, out);
	}

	@Override
	public void onEndProduct(ProductId id) throws Exception {
		io.writeString(FOOTER, out);

		out.flush();
		out.close();
	}

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

}