JsonProductSource.java

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

import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
import javax.json.JsonValue;

import gov.usgs.util.StreamUtils;
import gov.usgs.earthquake.product.ByteContent;
import gov.usgs.earthquake.product.Content;
import gov.usgs.earthquake.product.FileContent;
import gov.usgs.earthquake.product.Product;
import gov.usgs.earthquake.product.URLContent;

/**
 * Load a product from an InputStream containing Json.
 */
public class JsonProductSource implements ProductSource {

  /** The input stream where Json is read. */
  private InputStream in;

  /** The directory where a product exists. */
  private Path directory;

  /**
   * Create a new JsonProductSource.
   *
   * @param in the input stream where Json is read.
   */
  public JsonProductSource(final InputStream in) {
    this.in = in;
    this.directory = null;
  }

  /**
   * Create a new JsonProductSource.
   *
   * @param in        the input stream where Json is read.
   * @param directory the directory where a product exists.
   */
  public JsonProductSource(final InputStream in, final Path directory) {
    this.in = in;
    this.directory = directory;
  }

  public JsonObject parseContents(JsonObject contentsObject) throws Exception {
    JsonObjectBuilder contentsCopy = Json.createObjectBuilder();
    for (String key : contentsObject.keySet()) {
      JsonObject content = contentsObject.get(key).asJsonObject();
      JsonObjectBuilder contentCopy = Json.createObjectBuilder();
      Content inputContent;
      if (content.containsKey("bytes") && content.containsKey("url")) {
        throw new Error("Keys 'bytes' and 'url' cannot be in the same content json.");
      } else if (content.containsKey("bytes")) {
        byte[] bytes = content.getString("bytes").getBytes();
        ByteContent bytesContent = new ByteContent(bytes);
        if ("".equals(key)) {
          inputContent = bytesContent;
          contentCopy.add("url", "data:" + bytesContent.getContentType() + ";base64,"
              + Base64.getEncoder().encodeToString(StreamUtils.readStream(inputContent.getInputStream())));
        } else {
          // write bytes that have a path to a file
          URLContent urlContent = new URLContent(
              new FileContent(bytesContent, directory.resolve(key).toAbsolutePath().toFile()));
          inputContent = urlContent;
          contentCopy.add("url", urlContent.getURL().toString());
        }
      } else if (content.containsKey("url") && content.getString("url").startsWith("file:")) {
        URI uri = new URI(content.getString("url"));
        Path filePath = Paths.get(uri);
        File file = filePath.toAbsolutePath().toFile();
        FileContent fileContent = new FileContent(file);
        inputContent = fileContent;
        URLContent urlContent = new URLContent(fileContent);
        contentCopy.add("url", urlContent.getURL().toString());
      } else if (content.containsKey("url")) {
        URLContent urlContent = new URLContent(new URL(content.getString("url")));
        inputContent = urlContent;
        contentCopy.add("url", urlContent.getURL().toString());
      } else {
        URLContent urlContent = new URLContent(
            new FileContent(directory.resolve(key).toAbsolutePath().toFile()));
        inputContent = urlContent;
        contentCopy.add("url", urlContent.getURL().toString());
      }
      contentCopy.add("length", inputContent.getLength());
      contentCopy.add("modified", inputContent.getLastModified().toString());
      contentCopy.add("sha256", inputContent.getSha256());
      contentCopy.add("type", inputContent.getContentType());
      contentsCopy.add(key, contentCopy.build());
    }
    return contentsCopy.build();
  }

  public Product parseDirectoryJson(JsonObject json) throws Exception {
    // if we are reading a json product from a directory
    // Example: directory poller / input wedge)
    // then we need to prepare the contents that may have simple user input
    JsonObjectBuilder jsonCopy = Json.createObjectBuilder();
    for (final String key : json.keySet()) {
      if (key != "contents") {
        jsonCopy.add(key, json.get(key));
      }
    }
    if (json.containsKey("contents")) {
      JsonValue contents = json.get("contents");
      JsonObject contentsCopy;
      // loop through keys and handle files and bytes
      if (contents instanceof JsonArray) {
        JsonObjectBuilder contentsMap = Json.createObjectBuilder();
        for (JsonValue value : contents.asJsonArray()) {
          JsonObject content = value.asJsonObject();
          contentsMap.add(content.getString("path"), content);
        }
        contentsCopy = parseContents(contentsMap.build());
      } else {
        JsonObject contentsObject = contents.asJsonObject();
        contentsCopy = parseContents(contentsObject);
      }
      jsonCopy.add("contents", contentsCopy);
    }
    if (!json.containsKey("links")) {
      JsonArrayBuilder linksArrayBuilder = Json.createArrayBuilder();
      jsonCopy.add("links", linksArrayBuilder
          .build());
    }
    Product product = new JsonProduct().getProduct(jsonCopy.build());
    return product;
  }

  /**
   * Begin reading the input stream, sending events to out.
   *
   * @param out the receiving ProductOutput.
   */
  public synchronized void streamTo(ProductHandler out) throws Exception {
    final Product product;
    try (final JsonReader reader = Json.createReader(new InputStreamReader(in))) {
      JsonObject json = reader.readObject();
      if (directory != null) {
        product = parseDirectoryJson(json);
      } else {
        product = new JsonProduct().getProduct(json);
      }
    }
    final ObjectProductSource source = new ObjectProductSource(product);
    source.streamTo(out);
  }

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

}