ContentListener.java

package gov.usgs.earthquake.distribution;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;

import gov.usgs.earthquake.product.Content;
import gov.usgs.earthquake.product.Product;
import gov.usgs.earthquake.product.ProductId;
import gov.usgs.util.Config;
import gov.usgs.util.StreamUtils;

/**
 * A listener that listens for a specific content path.
 *
 * This is intended for users who wish to output specific pieces of product
 * content, such as "quakeml.xml", for products that otherwise meet their
 * configured NotificationListener criteria.
 */
public class ContentListener extends DefaultNotificationListener {

	private static final Logger LOGGER = Logger.getLogger(ContentListener.class
			.getName());

	/** configuration property for includePaths - output directory. */
	public static final String OUTPUT_DIRECTORY_PROPERTY = "outputDirectory";
	/** property for temporary directory */
	public static final String TEMP_DIRECTORY_PROPERTY = "tempDirectory";
	/** property for output format */
	public static final String OUTPUT_FORMAT_PROPERTY = "outputFormat";

	/** property for default output format */
	public static final String DEFAULT_OUTPUT_FORMAT = "SOURCE_TYPE_CODE_UPDATETIME_PATH";

	/** Directory where content is output. */
	private File outputDirectory = null;

	/**
	 * Temporary directory where content is written before moving to
	 * outputDirectory, defaults to system temp directory when null.
	 */
	private File tempDirectory = null;

	/** Output format for files inside outputDirectory. */
	private String outputFormat = DEFAULT_OUTPUT_FORMAT;

	/** empty constructor for ContentListener */
	public ContentListener() {
	}

	@Override
	public void configure(final Config config) throws Exception {
		super.configure(config);

		if (getIncludePaths().size() == 0) {
			throw new ConfigurationException("[" + getName()
					+ "] ContentListener requires 'includePaths' be non-empty");
		}

		outputDirectory = new File(
				config.getProperty(OUTPUT_DIRECTORY_PROPERTY));
		LOGGER.config("[" + getName() + "] output directory = "
				+ outputDirectory);

		String tempDirectoryString = config
				.getProperty(TEMP_DIRECTORY_PROPERTY);
		if (tempDirectoryString != null) {
			tempDirectory = new File(tempDirectoryString);
		}
		LOGGER.config("[" + getName() + "] temp directory = " + tempDirectory);

		outputFormat = config.getProperty(OUTPUT_FORMAT_PROPERTY,
				DEFAULT_OUTPUT_FORMAT);
		LOGGER.config("[" + getName() + "] output format = " + outputFormat);
	}

	@Override
	public void onProduct(final Product product) throws Exception {
		Map<String, Content> contents = product.getContents();
		Iterator<String> iter = getIncludePaths().iterator();
		while (iter.hasNext()) {
			String path = iter.next();
			Content content = contents.get(path);
			if (content != null) {
				// product has content at this path
				writeContent(product.getId(), path, content);
			}
		}
	}

	/**
	 * Generate an output path based on product id and content path.
	 *
	 * @param id
	 *            the product id.
	 * @param path
	 *            the content path.
	 * @return relative path to write content within output directory.
	 */
	protected String getOutputPath(final ProductId id, final String path) {
		return outputFormat
				.replace("SOURCE", id.getSource())
				.replace("TYPE", id.getType())
				.replace("CODE", id.getCode())
				.replace("UPDATETIME",
						Long.toString(id.getUpdateTime().getTime()))
				.replace("PATH", path);
	}

	/**
	 * Output a product content that was in includePaths.
	 *
	 * @param id
	 *            the product id.
	 * @param path
	 *            the content path.
	 * @param content
	 *            the content.
	 * @throws Exception
	 *             when unable to output the content.
	 */
	protected void writeContent(final ProductId id, final String path,
			final Content content) throws Exception {
		String outputPath = getOutputPath(id, path);
		if (tempDirectory != null && !tempDirectory.exists()) {
			// make sure parent directories exist
			tempDirectory.mkdirs();
		}
		File tempFile = File.createTempFile(outputPath, null, tempDirectory);

		File outputFile = new File(outputDirectory, outputPath);
		File outputFileParent = outputFile.getParentFile();
		if (!outputFileParent.exists()) {
			// make sure parent directories exist
			outputFileParent.mkdirs();
		}

		// write, then move into place
		InputStream in = null;
		OutputStream out = null;
		try {
			if (!tempFile.getParentFile().exists()) {
				tempFile.getParentFile().mkdirs();
			}
			if (!outputFile.getParentFile().exists()) {
				outputFile.getParentFile().mkdirs();
			}
			in = content.getInputStream();
			out = StreamUtils.getOutputStream(tempFile);
			// write to temp file
			StreamUtils.transferStream(in, out);
			// move to output file
			tempFile.renameTo(outputFile);
		} finally {
			StreamUtils.closeStream(in);
			StreamUtils.closeStream(out);
			// clean up temp file if it wasn't renamed
			if (tempFile.exists()) {
				tempFile.delete();
			}
		}
	}

	/**
	 * @return the outputDirectory
	 */
	public File getOutputDirectory() {
		return outputDirectory;
	}

	/**
	 * @param outputDirectory
	 *            the outputDirectory to set
	 */
	public void setOutputDirectory(File outputDirectory) {
		this.outputDirectory = outputDirectory;
	}

	/**
	 * @return the tempDirectory
	 */
	public File getTempDirectory() {
		return tempDirectory;
	}

	/**
	 * @param tempDirectory
	 *            the tempDirectory to set
	 */
	public void setTempDirectory(File tempDirectory) {
		this.tempDirectory = tempDirectory;
	}

	/**
	 * @return the outputFormat
	 */
	public String getOutputFormat() {
		return outputFormat;
	}

	/**
	 * @param outputFormat
	 *            the outputFormat to set
	 */
	public void setOutputFormat(String outputFormat) {
		this.outputFormat = outputFormat;
	}

}