Allowing API Server to distribute heron core package (#2724)

* Allowing API Server to distribute heron core package

* modifying endpoint
diff --git a/heron/config/src/yaml/conf/nomad/client.yaml b/heron/config/src/yaml/conf/nomad/client.yaml
index 6a50658..e51afc4 100644
--- a/heron/config/src/yaml/conf/nomad/client.yaml
+++ b/heron/config/src/yaml/conf/nomad/client.yaml
@@ -2,3 +2,7 @@
 heron.package.core.uri:                      file://${HERON_DIST}/heron-core.tar.gz
 
 heron.package.use_core_uri:                  true
+
+# Can use API Server to distribute core
+#heron.package.core.uri:                      http://localhost:9000/api/v1/file/download/core
+
diff --git a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/Runtime.java b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/Runtime.java
index 14de304..84a39a1 100644
--- a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/Runtime.java
+++ b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/Runtime.java
@@ -55,7 +55,8 @@
     Property("D"),
     ReleaseFile("release-file"),
     Verbose("verbose"),
-    DownloadHostName("download-hostname");
+    DownloadHostName("download-hostname"),
+    HeronCorePackagePath("heron-core-package-path");
 
     final String name;
 
@@ -128,6 +129,14 @@
         .required(false)
         .build();
 
+    final Option downloadHeronCoreName = Option.builder()
+        .desc("Path to Heron Core Package. API Server can serve Heron Core Package")
+        .longOpt(Flag.HeronCorePackagePath.name)
+        .hasArg()
+        .argName(Flag.HeronCorePackagePath.name)
+        .required(false)
+        .build();
+
     return new Options()
         .addOption(baseTemplate)
         .addOption(cluster)
@@ -136,7 +145,8 @@
         .addOption(release)
         .addOption(property)
         .addOption(verbose)
-        .addOption(downloadHostName);
+        .addOption(downloadHostName)
+        .addOption(downloadHeronCoreName);
   }
 
   private static Options constructHelpOptions() {
@@ -204,6 +214,13 @@
     return null;
   }
 
+  private static String getHeronCorePackagePath(CommandLine cmd) {
+    if (cmd.hasOption(Flag.HeronCorePackagePath.name)) {
+      return String.valueOf(cmd.getOptionValue(Flag.HeronCorePackagePath.name));
+    }
+    return null;
+  }
+
   private static String loadOverrides(CommandLine cmd) throws IOException {
     return ConfigUtils.createOverrideConfiguration(
         cmd.getOptionProperties(Flag.Property.name));
@@ -258,6 +275,7 @@
     final String configurationOverrides = loadOverrides(cmd);
     final int port = getPort(cmd);
     final String downloadHostName = getDownloadHostName(cmd);
+    final String heronCorePackagePath = getHeronCorePackagePath(cmd);
 
     final Config baseConfiguration =
         ConfigUtils.getBaseConfiguration(heronDirectory,
@@ -265,6 +283,8 @@
             releaseFile,
             configurationOverrides);
 
+    LOG.info("heronCorePackagePath: " + heronCorePackagePath);
+
     final ResourceConfig config = new ResourceConfig(Resources.get());
     final Server server = new Server(port);
 
@@ -284,6 +304,8 @@
         String.valueOf(port));
     contextHandler.setAttribute(HeronResource.ATTRIBUTE_DOWNLOAD_HOSTNAME,
         String.valueOf(downloadHostName));
+    contextHandler.setAttribute(HeronResource.ATTRIBUTE_HERON_CORE_PACKAGE_PATH,
+        String.valueOf(heronCorePackagePath));
 
     server.setHandler(contextHandler);
 
@@ -292,8 +314,6 @@
 
     contextHandler.addServlet(apiServlet, API_BASE_PATH);
 
-
-
     try {
       server.start();
 
diff --git a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/FileResource.java b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/FileResource.java
index f2dce0c..79e32fd 100644
--- a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/FileResource.java
+++ b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/FileResource.java
@@ -18,10 +18,9 @@
 import java.io.InputStream;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
 import java.util.UUID;
 
+import javax.activation.MimetypesFileTypeMap;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -29,7 +28,6 @@
 import javax.ws.rs.PathParam;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.StreamingOutput;
 
 import org.eclipse.jetty.util.StringUtil;
 import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
@@ -124,7 +122,7 @@
  */
   @GET
   @Path("/download/{file}")
-  public Response downloadPdfFile(final @PathParam("file") String file) {
+  public Response downloadFile(final @PathParam("file") String file) {
     Config config = createConfig();
     String uploadDir = config.getStringValue(FILE_SYSTEM_DIRECTORY);
     String filePath = uploadDir + "/" + file;
@@ -132,19 +130,30 @@
       LOG.debug("Download request file " + file + " doesn't exist at " + uploadDir);
       return Response.status(Response.Status.NOT_FOUND).build();
     }
-    StreamingOutput fileStream = new StreamingOutput() {
-      @Override
-      public void write(java.io.OutputStream output) throws IOException {
-          java.nio.file.Path path = Paths.get(filePath);
-          byte[] data = Files.readAllBytes(path);
-          output.write(data);
-          output.flush();
-      }
-    };
-    return Response
-        .ok(fileStream, MediaType.APPLICATION_OCTET_STREAM)
-        .header("content-disposition", "attachment; filename = " + file)
-        .build();
+
+    String mimeType = new MimetypesFileTypeMap().getContentType(file);
+    Response.ResponseBuilder rb = Response.ok(file, mimeType);
+    rb.header("content-disposition", "attachment; filename = "
+        + file);
+    return rb.build();
+  }
+
+  /**
+   * Endpoint for downloading Heron Core
+   */
+  @GET
+  @Path("/download/core")
+  public Response downloadHeronCore() {
+    String corePath = getHeronCorePackagePath();
+    File file = new File(corePath);
+    if (!file.exists()) {
+      return Response.status(Response.Status.NOT_FOUND).build();
+    }
+    String mimeType = new MimetypesFileTypeMap().getContentType(file);
+    Response.ResponseBuilder rb = Response.ok(file, mimeType);
+    rb.header("content-disposition", "attachment; filename = "
+        + file.getName());
+    return rb.build();
   }
 
   private Config createConfig() {
diff --git a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/HeronResource.java b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/HeronResource.java
index 6ed1e66..7620b81 100644
--- a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/HeronResource.java
+++ b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/HeronResource.java
@@ -26,6 +26,7 @@
   public static final String ATTRIBUTE_CONFIGURATION_OVERRIDE_PATH = "configuration_override";
   public static final String ATTRIBUTE_PORT = "port";
   public static final String ATTRIBUTE_DOWNLOAD_HOSTNAME = "download_hostname";
+  public static final String ATTRIBUTE_HERON_CORE_PACKAGE_PATH = "heron_core_package_path";
 
   @Context
   protected ServletContext servletContext;
@@ -36,6 +37,7 @@
   private String cluster;
   private String port;
   private String downloadHostname;
+  private String heronCorePackagePath;
 
   Config getBaseConfiguration() {
     if (baseConfiguration == null) {
@@ -85,4 +87,13 @@
     return downloadHostname;
   }
 
+  String getHeronCorePackagePath() {
+    if (heronCorePackagePath == null) {
+      heronCorePackagePath
+          = (String) servletContext.getAttribute(ATTRIBUTE_HERON_CORE_PACKAGE_PATH);
+    }
+
+    return heronCorePackagePath;
+  }
+
 }
diff --git a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/NotFoundExceptionHandler.java b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/NotFoundExceptionHandler.java
index 63eb4d8..956b639 100644
--- a/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/NotFoundExceptionHandler.java
+++ b/heron/tools/apiserver/src/java/com/twitter/heron/apiserver/resources/NotFoundExceptionHandler.java
@@ -35,6 +35,7 @@
     final ArrayNode arrayNode = node.putArray("paths");
     arrayNode.add("/api/v1/topologies");
     arrayNode.add("/api/v1/version");
+    arrayNode.add("/api/v1/file");
 
     final String response;
     try {