ZipProductHandler.java

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

import gov.usgs.earthquake.product.Content;
import gov.usgs.earthquake.product.Product;
import gov.usgs.earthquake.product.ProductId;

import gov.usgs.util.StreamUtils;

import java.io.OutputStream;

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;

/**
 * Store a product to an OutputStream using ZIP.
 * 
 * Accumulates entire product the same as ObjectProductOutput, then onEndProduct
 * outputs a zip file containing "product.xml" as the first entry and all
 * product content as other entries.
 * 
 * Because the zip file is not written until after all content has been
 * "received", all product content may result in in-memory buffering. This is
 * not the case when dealing with File-backed products. If this is a concern,
 * users are encouraged to use the Xml Input and Output classes, which always
 * use streams and never buffer content.
 */
public class ZipProductHandler extends ObjectProductHandler {

	/** The entry filename used for product metadata. */
	public static final String PRODUCT_XML_ZIP_ENTRYNAME = "product.xml";

	/** The output stream where zip content is written. */
	private OutputStream out;

	/**
	 * Construct a new ZipProductHandler object.
	 * 
	 * @param out
	 *            the output stream where zip content is written.
	 */
	public ZipProductHandler(final OutputStream out) {
		this.out = out;
	}

	/**
	 * Creates and outputs the zip stream.
	 */
	public void onEndProduct(ProductId id) throws Exception {
		super.onEndProduct(id);
		Product product = getProduct();

		// separate all contents that will be zip entries from product
		Map<String, Content> contents = new HashMap<String, Content>(
				product.getContents());
		product.getContents().clear();

		// check for inline content that won't have a zip entry
		Content inlineContent = contents.get("");
		if (inlineContent != null) {
			product.getContents().put("", inlineContent);
		}

		// write zip stream
		ZipOutputStream zos = null;
		try {
			zos = new ZipOutputStream(out);

			// product xml entry
			ZipEntry entry = new ZipEntry(PRODUCT_XML_ZIP_ENTRYNAME);
			entry.setTime(product.getId().getUpdateTime().getTime());
			zos.putNextEntry(entry);
			new ObjectProductSource(product).streamTo(new XmlProductHandler(
					new StreamUtils.UnclosableOutputStream(zos)));
			// zos.closeEntry();

			// all other content entries
			Iterator<String> paths = contents.keySet().iterator();
			while (paths.hasNext()) {
				String path = paths.next();
				if ("".equals(path)) {
					// inline content doesn't get separate entry
					continue;
				}
				Content content = contents.get(path);
				entry = new ZipEntry(path);
				entry.setTime(content.getLastModified().getTime());
				entry.setComment(content.getContentType());
				Long length = content.getLength();
				if (length != null && length > 0) {
					entry.setSize(content.getLength());
				}
				zos.putNextEntry(entry);
				StreamUtils.transferStream(content.getInputStream(),
						new StreamUtils.UnclosableOutputStream(zos));
				// zos.closeEntry();
			}
		} finally {
			// done
			zos.finish();
			zos.flush();
			StreamUtils.closeStream(zos);
		}
	}


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

}