More consistency between the HTTP APIs

Change-Id: Ie0e58cac20c1976610f38796d06f0518a3174c50
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1550
Sonar-Qube: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: abdullah alamoudi <bamousaa@gmail.com>
BAD: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java
index 8d934ee..f156de5 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/AbstractQueryApiServlet.java
@@ -22,6 +22,8 @@
 import static org.apache.asterix.api.http.servlet.ServletConstants.HYRACKS_DATASET_ATTR;
 
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -32,6 +34,7 @@
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.dataset.IHyracksDataset;
 import org.apache.hyracks.client.dataset.HyracksDataset;
+import org.apache.hyracks.http.api.IServletRequest;
 import org.apache.hyracks.http.server.AbstractServlet;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -40,6 +43,46 @@
 
 class AbstractQueryApiServlet extends AbstractServlet {
 
+    public enum ResultFields {
+        REQUEST_ID("requestID"),
+        CLIENT_ID("clientContextID"),
+        SIGNATURE("signature"),
+        TYPE("type"),
+        STATUS("status"),
+        RESULTS("results"),
+        HANDLE("handle"),
+        ERRORS("errors"),
+        METRICS("metrics");
+
+        private final String str;
+
+        ResultFields(String str) {
+            this.str = str;
+        }
+
+        public String str() {
+            return str;
+        }
+    }
+
+    public enum ResultStatus {
+        RUNNING("running"),
+        SUCCESS("success"),
+        TIMEOUT("timeout"),
+        FAILED("failed"),
+        FATAL("fatal");
+
+        private final String str;
+
+        ResultStatus(String str) {
+            this.str = str;
+        }
+
+        public String str() {
+            return str;
+        }
+    }
+
     AbstractQueryApiServlet(ConcurrentMap<String, Object> ctx, String[] paths) {
         super(ctx, paths);
     }
@@ -82,4 +125,42 @@
         }
         return null;
     }
+
+    protected static UUID printRequestId(PrintWriter pw) {
+        UUID requestId = UUID.randomUUID();
+        printField(pw, ResultFields.REQUEST_ID.str(), requestId.toString());
+        return requestId;
+    }
+
+    protected static void printStatus(PrintWriter pw, ResultStatus rs) {
+        printField(pw, ResultFields.STATUS.str(), rs.str());
+    }
+
+    protected static void printHandle(PrintWriter pw, String handle) {
+        printField(pw, ResultFields.HANDLE.str(), handle);
+    }
+
+    protected static void printField(PrintWriter pw, String name, String value) {
+        printField(pw, name, value, true);
+    }
+
+    protected static void printField(PrintWriter pw, String name, String value, boolean comma) {
+        printFieldInternal(pw, name, "\"" + value + "\"", comma);
+    }
+
+    protected static void printField(PrintWriter pw, String name, long value, boolean comma) {
+        printFieldInternal(pw, name, String.valueOf(value), comma);
+    }
+
+    protected static void printFieldInternal(PrintWriter pw, String name, String value, boolean comma) {
+        pw.print("\t\"");
+        pw.print(name);
+        pw.print("\": ");
+        pw.print(value);
+        if (comma) {
+            pw.print(',');
+        }
+        pw.print('\n');
+    }
+
 }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index c0a38e8..9d22452 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -18,8 +18,6 @@
  */
 package org.apache.asterix.api.http.server;
 
-import static org.apache.asterix.api.http.servlet.ServletConstants.HYRACKS_CONNECTION_ATTR;
-import static org.apache.asterix.api.http.servlet.ServletConstants.HYRACKS_DATASET_ATTR;
 import static org.apache.asterix.translator.IStatementExecutor.ResultDelivery;
 
 import java.io.IOException;
@@ -27,12 +25,10 @@
 import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
-import java.util.UUID;
 import java.util.concurrent.ConcurrentMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.apache.asterix.app.result.ResultReader;
 import org.apache.asterix.app.result.ResultUtil;
 import org.apache.asterix.common.api.IClusterManagementWork;
 import org.apache.asterix.common.config.GlobalConfig;
@@ -51,12 +47,8 @@
 import org.apache.asterix.translator.SessionConfig;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksAppendable;
-import org.apache.hyracks.api.client.IHyracksClientConnection;
-import org.apache.hyracks.api.dataset.IHyracksDataset;
-import org.apache.hyracks.client.dataset.HyracksDataset;
 import org.apache.hyracks.http.api.IServletRequest;
 import org.apache.hyracks.http.api.IServletResponse;
-import org.apache.hyracks.http.server.AbstractServlet;
 import org.apache.hyracks.http.server.utils.HttpUtil;
 
 import com.fasterxml.jackson.core.JsonParseException;
@@ -66,10 +58,9 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpMethod;
 import io.netty.handler.codec.http.HttpResponseStatus;
 
-public class QueryServiceServlet extends AbstractServlet {
+public class QueryServiceServlet extends AbstractQueryApiServlet {
     private static final Logger LOGGER = Logger.getLogger(QueryServiceServlet.class.getName());
     private final ILangCompilationProvider compilationProvider;
     private final IStatementExecutorFactory statementExecutorFactory;
@@ -128,46 +119,6 @@
         }
     }
 
-    public enum ResultFields {
-        REQUEST_ID("requestID"),
-        CLIENT_ID("clientContextID"),
-        SIGNATURE("signature"),
-        TYPE("type"),
-        STATUS("status"),
-        RESULTS("results"),
-        HANDLE("handle"),
-        ERRORS("errors"),
-        METRICS("metrics");
-
-        private final String str;
-
-        ResultFields(String str) {
-            this.str = str;
-        }
-
-        public String str() {
-            return str;
-        }
-    }
-
-    public enum ResultStatus {
-        STARTED("started"),
-        SUCCESS("success"),
-        TIMEOUT("timeout"),
-        ERRORS("errors"),
-        FATAL("fatal");
-
-        private final String str;
-
-        ResultStatus(String str) {
-            this.str = str;
-        }
-
-        public String str() {
-            return str;
-        }
-    }
-
     private enum ErrorField {
         CODE("code"),
         MSG("msg"),
@@ -327,35 +278,6 @@
         return sessionConfig;
     }
 
-    private static void printField(PrintWriter pw, String name, String value) {
-        printField(pw, name, value, true);
-    }
-
-    private static void printField(PrintWriter pw, String name, String value, boolean comma) {
-        printFieldInternal(pw, name, "\"" + value + "\"", comma);
-    }
-
-    private static void printField(PrintWriter pw, String name, long value, boolean comma) {
-        printFieldInternal(pw, name, String.valueOf(value), comma);
-    }
-
-    private static void printFieldInternal(PrintWriter pw, String name, String value, boolean comma) {
-        pw.print("\t\"");
-        pw.print(name);
-        pw.print("\": ");
-        pw.print(value);
-        if (comma) {
-            pw.print(',');
-        }
-        pw.print('\n');
-    }
-
-    private static UUID printRequestId(PrintWriter pw) {
-        UUID requestId = UUID.randomUUID();
-        printField(pw, ResultFields.REQUEST_ID.str(), requestId.toString());
-        return requestId;
-    }
-
     private static void printClientContextID(PrintWriter pw, RequestParameters params) {
         if (params.clientContextID != null && !params.clientContextID.isEmpty()) {
             printField(pw, ResultFields.CLIENT_ID.str(), params.clientContextID);
@@ -381,10 +303,6 @@
         }
     }
 
-    private static void printStatus(PrintWriter pw, ResultStatus rs) {
-        printField(pw, ResultFields.STATUS.str(), rs.str());
-    }
-
     private static void printError(PrintWriter pw, Throwable e) throws JsonProcessingException {
         Throwable rootCause = ResultUtil.getRootCause(e);
         if (rootCause == null) {
@@ -501,26 +419,15 @@
             if (param.statement == null || param.statement.isEmpty()) {
                 throw new AsterixException("Empty request, no statement provided");
             }
-            IHyracksClientConnection hcc = (IHyracksClientConnection) ctx.get(HYRACKS_CONNECTION_ATTR);
-            IHyracksDataset hds = (IHyracksDataset) ctx.get(HYRACKS_DATASET_ATTR);
-            if (hds == null) {
-                synchronized (ctx) {
-                    hds = (IHyracksDataset) ctx.get(HYRACKS_DATASET_ATTR);
-                    if (hds == null) {
-                        hds = new HyracksDataset(hcc, ResultReader.FRAME_SIZE, ResultReader.NUM_READERS);
-                        ctx.put(HYRACKS_DATASET_ATTR, hds);
-                    }
-                }
-            }
             IParser parser = compilationProvider.getParserFactory().createParser(param.statement);
             List<Statement> statements = parser.parse();
             MetadataManager.INSTANCE.init();
             IStatementExecutor translator =
                     statementExecutorFactory.create(statements, sessionConfig, compilationProvider, componentProvider);
             execStart = System.nanoTime();
-            translator.compileAndExecute(hcc, hds, delivery, stats);
+            translator.compileAndExecute(getHyracksClientConnection(), getHyracksDataset(), delivery, stats);
             execEnd = System.nanoTime();
-            printStatus(resultWriter, ResultDelivery.ASYNC == delivery ? ResultStatus.STARTED : ResultStatus.SUCCESS);
+            printStatus(resultWriter, ResultDelivery.ASYNC == delivery ? ResultStatus.RUNNING : ResultStatus.SUCCESS);
         } catch (AsterixException | TokenMgrError | org.apache.asterix.aqlplus.parser.TokenMgrError pe) {
             GlobalConfig.ASTERIX_LOGGER.log(Level.SEVERE, pe.getMessage(), pe);
             printError(resultWriter, pe);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
index 33c5c8f..9aa74c5 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryStatusApiServlet.java
@@ -18,6 +18,8 @@
  */
 package org.apache.asterix.api.http.server;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.concurrent.ConcurrentMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -33,7 +35,6 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
 
 import io.netty.handler.codec.http.HttpResponseStatus;
 
@@ -59,16 +60,54 @@
         IHyracksDataset hds = getHyracksDataset();
         ResultReader resultReader = new ResultReader(hds, jobId, rsId);
 
-        ObjectNode jsonResponse = om.createObjectNode();
-        final DatasetJobRecord.Status status = resultReader.getStatus();
-        if (status == null) {
+        ResultStatus resultStatus = resultStatus(resultReader.getStatus());
+
+        if (resultStatus == null) {
             LOGGER.log(Level.INFO, "No results for: \"" + strHandle + "\"");
             response.setStatus(HttpResponseStatus.NOT_FOUND);
             return;
         }
-        jsonResponse.put("status", status.name());
-        HttpUtil.setContentType(response, HttpUtil.ContentType.TEXT_PLAIN, HttpUtil.Encoding.UTF8);
-        response.setStatus(HttpResponseStatus.OK);
-        response.writer().write(jsonResponse.toString());
+
+        final StringWriter stringWriter = new StringWriter();
+        final PrintWriter resultWriter = new PrintWriter(stringWriter);
+
+        HttpUtil.setContentType(response, HttpUtil.ContentType.APPLICATION_JSON, HttpUtil.Encoding.UTF8);
+        HttpResponseStatus httpStatus = HttpResponseStatus.OK;
+
+        resultWriter.print("{\n");
+        printStatus(resultWriter, resultStatus);
+
+        if (ResultStatus.SUCCESS == resultStatus) {
+            String servletPath = servletPath(request).replace("status", "result");
+            String resHandle = "http://" + host(request) + servletPath + localPath(request);
+            printHandle(resultWriter, resHandle);
+        }
+
+        resultWriter.print("}\n");
+        resultWriter.flush();
+        String result = stringWriter.toString();
+
+        response.setStatus(httpStatus);
+        response.writer().print(result);
+        if (response.writer().checkError()) {
+            LOGGER.warning("Error flushing output writer");
+        }
+    }
+
+    ResultStatus resultStatus(DatasetJobRecord.Status status) {
+        if (status == null) {
+            return null;
+        }
+        switch (status) {
+            case IDLE:
+            case RUNNING:
+                return ResultStatus.RUNNING;
+            case SUCCESS:
+                return ResultStatus.SUCCESS;
+            case FAILED:
+                return ResultStatus.FAILED;
+            default:
+                return ResultStatus.FATAL;
+        }
     }
 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-failed/async-failed.2.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-failed/async-failed.2.json
index dd665eb..246785b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-failed/async-failed.2.json
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-failed/async-failed.2.json
@@ -1 +1,3 @@
-{"status":"FAILED"}
+{
+	"status": "failed",
+}
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.2.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.2.json
index 6cffe65..2dc2832 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.2.json
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.2.json
@@ -1 +1,3 @@
-{"status":"RUNNING"}
+{
+	"status": "running",
+}
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.3.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.3.json
deleted file mode 100644
index 6213a6b..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.3.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"SUCCESS"}
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.3.regex b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.3.regex
new file mode 100644
index 0000000..4308ba2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async-running/async-running.3.regex
@@ -0,0 +1,2 @@
+/"status": "success"/
+/"handle": ".*"/
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.2.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.2.json
deleted file mode 100644
index 6213a6b..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.2.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"SUCCESS"}
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.2.regex b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.2.regex
new file mode 100644
index 0000000..4308ba2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/async-deferred/async/async.2.regex
@@ -0,0 +1,2 @@
+/"status": "success"/
+/"handle": ".*"/
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/at00/at00.5.json b/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/at00/at00.5.json
deleted file mode 100644
index 6213a6b..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/at00/at00.5.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"SUCCESS"}
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/at00/at00.5.regex b/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/at00/at00.5.regex
new file mode 100644
index 0000000..4308ba2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/flwor/at00/at00.5.regex
@@ -0,0 +1,2 @@
+/"status": "success"/
+/"handle": ".*"/