Repository: zeppelin
Updated Branches:
  refs/heads/master 3505625c2 -> 549bce673


[ZEPPELIN-3014] NPE bug fix and Error message enhancement with Kylin Interpreter

### What is this PR for?
A few sentences describing the overall goals of the pull request's commits.
First time? Check out the contributing guide - 
https://zeppelin.apache.org/contribution/contributions.html

### What type of PR is it?
Bug Fix

### Todos
* [ ] - Task

### What is the Jira issue?
* https://issues.apache.org/jira/browse/ZEPPELIN-3014

### How should this be tested?
*  Setup Travis CI as described on 
https://zeppelin.apache.org/contribution/contributions.html#continuous-integration
* Use existing unit tests in kylin module.

### Screenshots (if appropriate)

#### before: NPE when result set is empty
![image](https://user-images.githubusercontent.com/18542573/32154048-f1b8ba58-bcfb-11e7-98cc-98cdf484f2d5.png)
#### after: no NPE when result set is empty, just an empty table
![image](https://user-images.githubusercontent.com/18542573/32154069-110215d0-bcfc-11e7-87e9-cc049001f1c7.png)

#### before: when query fails, only error code is returned, no error message
![image](https://user-images.githubusercontent.com/18542573/32154088-29651938-bcfc-11e7-9e66-cd2cfccba054.png)
#### after: when query fails, both error code and error message are displayed 
to users
![image](https://user-images.githubusercontent.com/18542573/32154096-3d3ab01c-bcfc-11e7-8cf3-d710d96b8c5a.png)

### Questions:
* Does the licenses files need update? No.
* Is there breaking changes for older versions? No.
* Does this needs documentation? No.

Author: Liu <jinx...@ebay.com>

Closes #2645 from jinxliu/kylin-intp-new and squashes the following commits:

d5692bf [Liu] refactor
85b6424 [Liu] add test for empty result set
4596470 [Liu] ZEPPELIN-3014: NPE bug fix and Error message enhancement with 
Kylin Interpreter


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/549bce67
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/549bce67
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/549bce67

Branch: refs/heads/master
Commit: 549bce6738ffd7f460867d3f5ee00a9e2ec14125
Parents: 3505625
Author: Liu <jinx...@ebay.com>
Authored: Tue Dec 5 15:24:20 2017 +0800
Committer: Jeff Zhang <zjf...@apache.org>
Committed: Wed Dec 13 18:54:06 2017 +0800

----------------------------------------------------------------------
 .../zeppelin/kylin/KylinErrorResponse.java      | 63 +++++++++++++++++
 .../apache/zeppelin/kylin/KylinInterpreter.java | 71 ++++++++++++--------
 .../zeppelin/kylin/KylinInterpreterTest.java    | 24 +++++++
 3 files changed, 130 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/549bce67/kylin/src/main/java/org/apache/zeppelin/kylin/KylinErrorResponse.java
----------------------------------------------------------------------
diff --git 
a/kylin/src/main/java/org/apache/zeppelin/kylin/KylinErrorResponse.java 
b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinErrorResponse.java
new file mode 100644
index 0000000..00439e8
--- /dev/null
+++ b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinErrorResponse.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.kylin;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import org.apache.zeppelin.common.JsonSerializable;
+
+/**
+ * class for Kylin Error Response.
+ */
+class KylinErrorResponse implements JsonSerializable {
+  private static final Gson gson = new Gson();
+
+  private String stacktrace;
+  private String exception;
+  private String url;
+  private String code;
+  private Object data;
+  private String msg;
+
+  public KylinErrorResponse(String stacktrace, String exception, String url,
+      String code, Object data, String msg) {
+    this.stacktrace = stacktrace;
+    this.exception = exception;
+    this.url = url;
+    this.code = code;
+    this.data = data;
+    this.msg = msg;
+  }
+
+  public String getException() {
+    return exception;
+  }
+
+  public String toJson() {
+    return gson.toJson(this);
+  }
+
+  public static KylinErrorResponse fromJson(String json) {
+    try {
+      return gson.fromJson(json, KylinErrorResponse.class);
+    } catch (JsonSyntaxException ex) {
+      return null;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/549bce67/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java
----------------------------------------------------------------------
diff --git 
a/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java 
b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java
index 6b68d28..c7cd689 100755
--- a/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java
+++ b/kylin/src/main/java/org/apache/zeppelin/kylin/KylinInterpreter.java
@@ -18,6 +18,7 @@
 package org.apache.zeppelin.kylin;
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpPost;
@@ -30,9 +31,7 @@ import 
org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.util.List;
 import java.util.Properties;
 import java.util.regex.Matcher;
@@ -166,28 +165,42 @@ public class KylinInterpreter extends Interpreter {
   }
 
   private InterpreterResult executeQuery(String sql) throws IOException {
-
     HttpResponse response = prepareRequest(sql);
+    String result;
 
-    if (response.getStatusLine().getStatusCode() != 200) {
-      logger.error("failed to execute query: " + 
response.getEntity().getContent().toString());
-      return new InterpreterResult(InterpreterResult.Code.ERROR,
-          "Failed : HTTP error code " + 
response.getStatusLine().getStatusCode());
-    }
-
-    BufferedReader br = new BufferedReader(
-        new InputStreamReader((response.getEntity().getContent())));
-    StringBuilder sb = new StringBuilder();
+    try {
+      int code = response.getStatusLine().getStatusCode();
+      result = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+
+      if (code != 200) {
+        StringBuilder errorMessage = new StringBuilder("Failed : HTTP error 
code " + code + " .");
+        logger.error("Failed to execute query: " + result);
+
+        KylinErrorResponse kylinErrorResponse = 
KylinErrorResponse.fromJson(result);
+        if (kylinErrorResponse == null) {
+          logger.error("Cannot get json from string: " + result);
+          // when code is 401, the response is html, not json
+          if (code == 401) {
+            errorMessage.append(" Error message: Unauthorized. This request 
requires "
+                + "HTTP authentication. Please make sure your have set your 
credentials "
+                + "correctly.");
+          } else {
+            errorMessage.append(" Error message: " + result + " .");
+          }
+        } else {
+          String exception = kylinErrorResponse.getException();
+          logger.error("The exception is " + exception);
+          errorMessage.append(" Error message: " + exception + " .");
+        }
 
-    String output;
-    logger.info("Output from Server .... \n");
-    while ((output = br.readLine()) != null) {
-      logger.info(output);
-      sb.append(output).append('\n');
+        return new InterpreterResult(InterpreterResult.Code.ERROR, 
errorMessage.toString());
+      }
+    } catch (NullPointerException | IOException e) {
+      throw new IOException(e);
     }
-    InterpreterResult rett = new 
InterpreterResult(InterpreterResult.Code.SUCCESS, 
-        formatResult(sb.toString()));
-    return rett;
+
+    return new InterpreterResult(InterpreterResult.Code.SUCCESS,
+        formatResult(result));
   }
 
   String formatResult(String msg) {
@@ -205,16 +218,18 @@ public class KylinInterpreter extends Interpreter {
       table = mr.group(1);
     }
 
-    String[] row = table.split("],\\[");
-    for (int i = 0; i < row.length; i++) {
-      String[] col = row[i].split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
-      for (int j = 0; j < col.length; j++) {
-        if (col[j] != null) {
-          col[j] = col[j].replaceAll("^\"|\"$", "");
+    if (table != null && !table.isEmpty()) {
+      String[] row = table.split("],\\[");
+      for (int i = 0; i < row.length; i++) {
+        String[] col = row[i].split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
+        for (int j = 0; j < col.length; j++) {
+          if (col[j] != null) {
+            col[j] = col[j].replaceAll("^\"|\"$", "");
+          }
+          res.append(col[j] + " \t");
         }
-        res.append(col[j] + " \t");
+        res.append(" \n");
       }
-      res.append(" \n");
     }
     return res.toString();
   }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/549bce67/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java
----------------------------------------------------------------------
diff --git 
a/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java 
b/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java
index 4471a07..35f0f3c 100755
--- a/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java
+++ b/kylin/src/test/java/org/apache/zeppelin/kylin/KylinInterpreterTest.java
@@ -108,6 +108,30 @@ public class KylinInterpreterTest {
     Assert.assertEquals(expected, actual);
   }
 
+  @Test
+  public void testParseEmptyResult() {
+    String msg = 
"{\"columnMetas\":[{\"isNullable\":1,\"displaySize\":256,\"label\":\"COUNTRY\",\"name\":\"COUNTRY\","
+        + 
"\"schemaName\":\"DEFAULT\",\"catelogName\":null,\"tableName\":\"SALES_TABLE\",\"precision\":256,"
+        + 
"\"scale\":0,\"columnType\":12,\"columnTypeName\":\"VARCHAR\",\"writable\":false,\"readOnly\":true,"
+        + 
"\"definitelyWritable\":false,\"autoIncrement\":false,\"caseSensitive\":true,\"searchable\":false,"
+        + 
"\"currency\":false,\"signed\":true},{\"isNullable\":1,\"displaySize\":256,\"label\":\"CURRENCY\","
+        + 
"\"name\":\"CURRENCY\",\"schemaName\":\"DEFAULT\",\"catelogName\":null,\"tableName\":\"SALES_TABLE\","
+        + 
"\"precision\":256,\"scale\":0,\"columnType\":12,\"columnTypeName\":\"VARCHAR\",\"writable\":false,"
+        + 
"\"readOnly\":true,\"definitelyWritable\":false,\"autoIncrement\":false,\"caseSensitive\":true,"
+        + 
"\"searchable\":false,\"currency\":false,\"signed\":true},{\"isNullable\":0,\"displaySize\":19,"
+        + 
"\"label\":\"COUNT__\",\"name\":\"COUNT__\",\"schemaName\":\"DEFAULT\",\"catelogName\":null,"
+        + 
"\"tableName\":\"SALES_TABLE\",\"precision\":19,\"scale\":0,\"columnType\":-5,\"columnTypeName\":"
+        + 
"\"BIGINT\",\"writable\":false,\"readOnly\":true,\"definitelyWritable\":false,\"autoIncrement\":false,"
+        + 
"\"caseSensitive\":true,\"searchable\":false,\"currency\":false,\"signed\":true}],\"results\":"
+        + "[]," + 
"\"cube\":\"Sample_Cube\",\"affectedRowCount\":0,\"isException\":false,\"exceptionMessage\":null,"
+        + 
"\"duration\":134,\"totalScanCount\":1,\"hitExceptionCache\":false,\"storageCacheUsed\":false,"
+        + "\"partial\":false}";
+    String expected="%table COUNTRY \tCURRENCY \tCOUNT__ \t \n";
+    KylinInterpreter t = new MockKylinInterpreter(getDefaultProperties());
+    String actual = t.formatResult(msg);
+    Assert.assertEquals(expected, actual);
+  }
+
   private Properties getDefaultProperties() {
     Properties prop = new Properties();
     prop.put("kylin.api.username", "ADMIN");

Reply via email to