AbstractContent.java

/*
 * AbstractContent
 */
package gov.usgs.earthquake.product;

import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Date;

import gov.usgs.earthquake.util.NullOutputStream;
import gov.usgs.util.StreamUtils;

/**
 * AbstractContent is a base class for other content classes and implements
 * common functionality.
 */
public abstract class AbstractContent implements Content {

  /** The content mime type. */
  private String contentType;

  /** When this content was created. */
  private Date lastModified;

  /** How much content there is. */
  private Long length;

  /** Base64 encoded hash of content */
  String sha256;

  /**
   * Null values are replaced with defaults.
   *
   * @param contentType  defaults to "text/plain".
   * @param lastModified defaults to new Date().
   * @param length       defaults to -1L.
   */
  public AbstractContent(final String contentType, final Date lastModified, final Long length) {
    setContentType(contentType);
    setLastModified(lastModified);
    setLength(length);
  }

  /**
   * Copy constructor from another content.
   *
   * @param content the content to copy.
   */
  public AbstractContent(final Content content) {
    this(content.getContentType(), content.getLastModified(), content.getLength());
  }

  /**
   * Default Constructor which requires no arguments. Default values are used for
   * all fields.
   */
  public AbstractContent() {
    this(null, null, null);
  }

  /**
   * @return the content mime type.
   */
  public String getContentType() {
    return contentType;
  }

  /**
   * Set the content mime type.
   *
   * @param contentType the content mime type.
   */
  public void setContentType(final String contentType) {
    if (contentType == null) {
      this.contentType = "text/plain";
    } else {
      this.contentType = contentType;
    }
  }

  /**
   * @return the content creation date.
   */
  public Date getLastModified() {
    return lastModified;
  }

  /**
   * Set when this content was created.
   *
   * @param lastModified when this content was created.
   */
  public void setLastModified(final Date lastModified) {
    if (lastModified == null) {
      this.lastModified = new Date();
    } else {
      this.lastModified = new Date(lastModified.getTime());
    }

    // Round to nearest second. Some file systems do not preserve
    // milliseconds.
    this.lastModified.setTime((this.lastModified.getTime() / 1000L) * 1000L);
  }

  /**
   * @return the content length, or -1 if unknown.
   */
  public Long getLength() {
    return length;
  }

  /**
   * Set the content length.
   *
   * @param length long to set
   */
  public void setLength(final Long length) {
    if (length == null) {
      this.length = -1L;
    } else {
      this.length = length;
    }
  }

  /** Get the Sha256 hash. */
  @Override
  public String getSha256() throws Exception {
    return getSha256(true);
  }

  /**
   * Get or generate the MD5 hash of content.
   *
   * @param computeIfMissing Use getInputStream to generate hash if missing.
   * @return sha256 string
   * @throws Exception if error occurs
   */
  public String getSha256(final boolean computeIfMissing) throws Exception {
    if (sha256 == null && computeIfMissing) {
      try (
          final DigestOutputStream contentDigest = new DigestOutputStream(new NullOutputStream(),
              MessageDigest.getInstance("SHA-256"));
          final OutputStream unclosable = new StreamUtils.UnclosableOutputStream(contentDigest)) {
        StreamUtils.transferStream(this.getInputStream(), unclosable);
        contentDigest.flush();
        setSha256(Base64.getEncoder().encodeToString(contentDigest.getMessageDigest().digest()));
      }
    }
    return sha256;
  }

  /**
   * Set the sha256 hash of content.
   *
   * @param sha256 to set
   */
  public void setSha256(final String sha256) {
    this.sha256 = sha256;
  }
}