You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2018/03/04 18:00:26 UTC
[1/2] zeppelin git commit: [ZEPPELIN-3194][NEW-INTERPRETER] SAP
Universe interpreter
Repository: zeppelin
Updated Branches:
refs/heads/master bfc93dc03 -> 63c53fcc5
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java
new file mode 100644
index 0000000..dc9099d
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseUtil.java
@@ -0,0 +1,643 @@
+/*
+ * 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.sap.universe;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.util.*;
+
+/**
+ * Util class for convert request from Zeppelin to SAP
+ */
+public class UniverseUtil {
+
+ private static final String COMPRASION_START_TEMPLATE = "<comparisonFilter path=\"%s\" " +
+ "operator=\"%s\" id=\"%s\">\n";
+ private static final String COMPRASION_END_TEMPLATE = "</comparisonFilter>\n";
+ private static final String COMPARISON_FILTER = "<comparisonFilter id=\"%s\" path=\"%s\" " +
+ "operator=\"%s\"/>\n";
+ private static final String CONST_OPERAND_START_TEMPLATE = "<constantOperand>\n";
+ private static final String CONST_OPERAND_END_TEMPLATE = "</constantOperand>\n";
+ private static final String CONST_OPERAND_VALUE_TEMPLATE = "<value>\n" +
+ "<caption type=\"%s\">%s</caption>\n</value>\n";
+ private static final String PREDEFINED_FILTER_TEMPLATE = "<predefinedFilter path=\"%s\"" +
+ " id=\"%s\"/>\n";
+ private static final String OBJECT_OPERAND_TEMPLATE = "<objectOperand id=\"%s\" path=\"%s\"/>\n";
+ private static final String RESULT_START_TEMPLATE = "<resultObjects>\n";
+ private static final String RESULT_END_TEMPLATE = "</resultObjects>\n";
+ private static final String RESULT_OBJ_TEMPLATE = "<resultObject path=\"%s\" id=\"%s\"/>\n";
+
+ private static final String MARKER_EQUAL = "#EqualTo#";
+ private static final String MARKER_LESS_EQUAL = "#LessThanOrEqualTo#";
+ private static final String MARKER_NOT_EQUAL = "#NotEqualTo#";
+ private static final String MARKER_LESS = "#LessThan#";
+ private static final String MARKER_GREATER_EQUALS = "#GreaterThanOrEqualTo#";
+ private static final String MARKER_GREATER = "#GreaterThan#";
+ private static final String MARKER_IN = "#InList#";
+ private static final String MARKER_NOT_IN = "#NotInList#";
+ private static final String MARKER_NULL = "#IsNull#";
+ private static final String MARKER_NOT_NULL = "#IsNotNull#";
+ private static final String MARKER_FILTER = "#filter#";
+ private static final String MARKER_AND = "#and#";
+ private static final String MARKER_OR = "#or#";
+ private static final String MARKER_BACKSPACE = "#backspace#";
+ private static final String MARKER_LEFT_BRACE = "#left_brace#";
+ private static final String MARKER_RIGHT_BRACE = "#right_brace#";
+
+
+ private static final String LEFT_BRACE = "(";
+ private static final String RIGHT_BRACE = ")";
+
+ public static final Map<String, Integer> OPERATIONS;
+
+ static {
+ OPERATIONS = new HashMap<>();
+ OPERATIONS.put(MARKER_EQUAL, 1);
+ OPERATIONS.put(MARKER_LESS_EQUAL, 1);
+ OPERATIONS.put(MARKER_NOT_EQUAL, 1);
+ OPERATIONS.put(MARKER_LESS, 1);
+ OPERATIONS.put(MARKER_GREATER_EQUALS, 1);
+ OPERATIONS.put(MARKER_GREATER, 1);
+ OPERATIONS.put(MARKER_IN, 1);
+ OPERATIONS.put(MARKER_NOT_IN, 1);
+ OPERATIONS.put(MARKER_NULL, 1);
+ OPERATIONS.put(MARKER_NOT_NULL, 1);
+ OPERATIONS.put(MARKER_FILTER, 1);
+ OPERATIONS.put(MARKER_AND, 2);
+ OPERATIONS.put(MARKER_OR, 3);
+ }
+
+ public UniverseQuery convertQuery(String text, UniverseClient client, String token)
+ throws UniverseException {
+ StringBuilder select = new StringBuilder();
+ StringBuilder universe = new StringBuilder();
+ StringBuilder buf = new StringBuilder();
+ StringBuilder resultObj = new StringBuilder();
+ StringBuilder whereBuf = new StringBuilder();
+ UniverseInfo universeInfo = null;
+ String where = null;
+ boolean singleQuoteClosed = true;
+ boolean pathClosed = true;
+ boolean universePart = false;
+ boolean selectPart = false;
+ boolean wherePart = false;
+ boolean listOperator = false;
+ boolean operatorPosition = false;
+ Map<String, UniverseNodeInfo> nodeInfos = null;
+
+ char[] array = text.toCharArray();
+ for (int i = 0; i < array.length; i++) {
+ char c = array[i];
+ buf.append(c);
+ if (c == '\'') {
+ if (i == 0 || array[i - 1] != '\\') {
+ singleQuoteClosed = !singleQuoteClosed;
+ }
+ }
+ if (c == '[' && pathClosed && singleQuoteClosed) {
+ pathClosed = false;
+ if (wherePart) {
+ operatorPosition = false;
+ }
+ }
+ if (c == ']' && !pathClosed && singleQuoteClosed) {
+ pathClosed = true;
+ if (wherePart) {
+ operatorPosition = true;
+ if (i + 1 == array.length || (array[i + 1] != '.'
+ && isFilter(String.format("%s]", whereBuf.toString()), text.substring(i + 1)))) {
+ whereBuf.append(c);
+ whereBuf.append(MARKER_FILTER);
+ if (i + 1 == array.length) {
+ wherePart = false;
+ where = parseWhere(whereBuf.toString(), nodeInfos);
+ }
+ continue;
+ }
+ }
+ }
+ if (c == '(' && wherePart && pathClosed && singleQuoteClosed) {
+ if (listOperator) {
+ whereBuf.append(MARKER_LEFT_BRACE);
+ continue;
+ } else {
+ whereBuf.append(c);
+ continue;
+ }
+ }
+ if (c == ')' && wherePart && pathClosed && singleQuoteClosed) {
+ if (listOperator) {
+ whereBuf.append(MARKER_RIGHT_BRACE);
+ listOperator = false;
+ continue;
+ } else {
+ whereBuf.append(c);
+ continue;
+ }
+ }
+
+ if (!universePart && singleQuoteClosed
+ && buf.toString().toLowerCase().endsWith("universe")) {
+ universePart = true;
+ continue;
+ }
+
+ if (universePart) {
+ if (c == ';' && singleQuoteClosed) {
+ universePart = false;
+ if (universe.toString().trim().length() > 2) {
+ String universeName =
+ universe.toString().trim().substring(1, universe.toString().trim().length() - 1);
+ universeInfo = client.getUniverseInfo(universeName);
+ nodeInfos = client.getUniverseNodesInfo(token, universeName);
+ }
+ } else {
+ universe.append(c);
+ }
+ continue;
+ }
+
+ if (!selectPart && pathClosed && singleQuoteClosed
+ && buf.toString().toLowerCase().endsWith("select")) {
+ if (StringUtils.isBlank(universe.toString())) {
+ throw new UniverseException("Not found universe name");
+ }
+ selectPart = true;
+ select.append(RESULT_START_TEMPLATE);
+ continue;
+ }
+
+ if (!wherePart && pathClosed && singleQuoteClosed) {
+ if (buf.toString().toLowerCase().endsWith("where")) {
+ wherePart = true;
+ }
+ if (buf.toString().toLowerCase().endsWith("where") || i == array.length - 1) {
+ selectPart = false;
+ select.append(parseResultObj(resultObj.toString().replaceAll("(?i)wher$", ""), nodeInfos));
+ select.append(RESULT_END_TEMPLATE);
+ continue;
+ }
+ }
+
+ if (selectPart) {
+ if (pathClosed && singleQuoteClosed && c == ',') {
+ select.append(parseResultObj(resultObj.toString(), nodeInfos));
+ resultObj = new StringBuilder();
+ } else {
+ resultObj.append(c);
+ }
+ continue;
+ }
+
+ if (wherePart) {
+ if (c == ';' && pathClosed && singleQuoteClosed) {
+ wherePart = false;
+ where = parseWhere(whereBuf.toString(), nodeInfos);
+ } else {
+ if (!singleQuoteClosed || !pathClosed) {
+ switch (c) {
+ case ' ':
+ case '\n':
+ whereBuf.append(MARKER_BACKSPACE);
+ break;
+ case '(':
+ whereBuf.append(MARKER_LEFT_BRACE);
+ break;
+ case ')':
+ whereBuf.append(MARKER_RIGHT_BRACE);
+ break;
+ default:
+ whereBuf.append(c);
+ }
+ } else if (pathClosed) {
+ if ((c == 'a' || c == 'A') && i < array.length - 2 &&
+ text.substring(i, i + 3).equalsIgnoreCase("and")) {
+ i += 2;
+ whereBuf.append(MARKER_AND);
+ operatorPosition = false;
+ continue;
+ }
+ if ((c == 'o' || c == 'O') && i < array.length - 1 &&
+ text.substring(i, i + 2).equalsIgnoreCase("or")) {
+ i += 1;
+ whereBuf.append(MARKER_OR);
+ operatorPosition = false;
+ continue;
+ }
+ if (operatorPosition) {
+ switch (c) {
+ case '=':
+ whereBuf.append(MARKER_EQUAL);
+ operatorPosition = false;
+ break;
+ case '<':
+ if (i + 1 < array.length) {
+ if (array[i + 1] == '=') {
+ whereBuf.append(MARKER_LESS_EQUAL);
+ operatorPosition = false;
+ i++;
+ break;
+ } else if (array[i + 1] == '>') {
+ whereBuf.append(MARKER_NOT_EQUAL);
+ operatorPosition = false;
+ i++;
+ break;
+ }
+ }
+ operatorPosition = false;
+ whereBuf.append(MARKER_LESS);
+ break;
+ case '>':
+ if (i + 1 < array.length) {
+ if (array[i + 1] == '=') {
+ whereBuf.append(MARKER_GREATER_EQUALS);
+ operatorPosition = false;
+ i++;
+ break;
+ }
+ }
+ operatorPosition = false;
+ whereBuf.append(MARKER_GREATER);
+ break;
+ case 'i':
+ case 'I':
+ boolean whileI = true;
+ StringBuilder operI = new StringBuilder();
+ operI.append(c);
+ while (whileI) {
+ i++;
+ if (i >= array.length) {
+ whileI = false;
+ }
+
+ if (array[i] != ' ' && array[i] != '\n') {
+ operI.append(array[i]);
+ } else {
+ continue;
+ }
+ String tmp = operI.toString().toLowerCase();
+ if (tmp.equals("in")) {
+ whereBuf.append(MARKER_IN);
+ listOperator = true;
+ whileI = false;
+ operatorPosition = false;
+ } else if (tmp.equals("isnull")) {
+ whereBuf.append(MARKER_NULL);
+ whileI = false;
+ operatorPosition = false;
+ } else if (tmp.equals("isnotnull")) {
+ whereBuf.append(MARKER_NOT_NULL);
+ whileI = false;
+ operatorPosition = false;
+ }
+ // longest 9 - isnotnull
+ if (tmp.length() > 8) {
+ whileI = false;
+ }
+ }
+ break;
+ case 'n':
+ case 'N':
+ boolean whileN = true;
+ StringBuilder operN = new StringBuilder();
+ operN.append(c);
+ while (whileN) {
+ i++;
+ if (i >= array.length) {
+ whileN = false;
+ }
+
+ if (array[i] != ' ' && array[i] != '\n') {
+ operN.append(array[i]);
+ } else {
+ continue;
+ }
+
+ String tmp = operN.toString().toLowerCase();
+
+ if (tmp.equals("notin")) {
+ whereBuf.append(MARKER_NOT_IN);
+ listOperator = true;
+ whileN = false;
+ operatorPosition = false;
+ }
+
+ // longest 5 - notin
+ if (tmp.length() > 4) {
+ whileN = false;
+ }
+ }
+ break;
+ default:
+ whereBuf.append(c);
+ }
+ } else {
+ whereBuf.append(c);
+ }
+ } else {
+ whereBuf.append(c);
+ }
+ }
+ }
+ }
+
+ if (wherePart && StringUtils.isBlank(where)) {
+ throw new UniverseException("Incorrect block where");
+ }
+
+ UniverseQuery universeQuery = new UniverseQuery(select.toString().trim(),
+ where, universeInfo);
+
+ if (!universeQuery.isCorrect()) {
+ throw new UniverseException("Incorrect query");
+ }
+
+ return universeQuery;
+ }
+
+ private String parseWhere(String where, Map<String, UniverseNodeInfo> nodeInfos)
+ throws UniverseException {
+ List<String> out = new ArrayList<>();
+ Stack<String> stack = new Stack<>();
+
+ where = where.replaceAll("\\s*", "");
+
+ Set<String> operationSymbols = new HashSet<>(OPERATIONS.keySet());
+ operationSymbols.add(LEFT_BRACE);
+ operationSymbols.add(RIGHT_BRACE);
+
+ int index = 0;
+
+ boolean findNext = true;
+ while (findNext) {
+ int nextOperationIndex = where.length();
+ String nextOperation = "";
+ for (String operation : operationSymbols) {
+ int i = where.indexOf(operation, index);
+ if (i >= 0 && i < nextOperationIndex) {
+ nextOperation = operation;
+ nextOperationIndex = i;
+ }
+ }
+ if (nextOperationIndex == where.length()) {
+ findNext = false;
+ } else {
+ if (index != nextOperationIndex) {
+ out.add(where.substring(index, nextOperationIndex));
+ }
+ if (nextOperation.equals(LEFT_BRACE)) {
+ stack.push(nextOperation);
+ }
+ else if (nextOperation.equals(RIGHT_BRACE)) {
+ while (!stack.peek().equals(LEFT_BRACE)) {
+ out.add(stack.pop());
+ if (stack.empty()) {
+ throw new UniverseException("Unmatched brackets");
+ }
+ }
+ stack.pop();
+ }
+ else {
+ while (!stack.empty() && !stack.peek().equals(LEFT_BRACE) &&
+ (OPERATIONS.get(nextOperation) >= OPERATIONS.get(stack.peek()))) {
+ out.add(stack.pop());
+ }
+ stack.push(nextOperation);
+ }
+ index = nextOperationIndex + nextOperation.length();
+ }
+ }
+ if (index != where.length()) {
+ out.add(where.substring(index));
+ }
+ while (!stack.empty()) {
+ out.add(stack.pop());
+ }
+ StringBuffer result = new StringBuffer();
+ if (!out.isEmpty())
+ result.append(out.remove(0));
+ while (!out.isEmpty())
+ result.append(" ").append(out.remove(0));
+
+ // result contains the reverse polish notation
+ return convertWhereToXml(result.toString(), nodeInfos);
+ }
+
+ private String parseResultObj(String resultObj, Map<String, UniverseNodeInfo> nodeInfos)
+ throws UniverseException {
+ if (StringUtils.isNotBlank(resultObj)) {
+ UniverseNodeInfo nodeInfo = nodeInfos.get(resultObj.trim());
+ if (nodeInfo != null) {
+ return String.format(RESULT_OBJ_TEMPLATE, nodeInfo.getNodePath(), nodeInfo.getId());
+ }
+ throw new UniverseException(String.format("Not found information about: \"%s\"",
+ resultObj.trim()));
+ }
+
+ return StringUtils.EMPTY;
+ }
+
+ private String convertWhereToXml(String rpn, Map<String, UniverseNodeInfo> nodeInfos)
+ throws UniverseException {
+ StringTokenizer tokenizer = new StringTokenizer(rpn, " ");
+
+ Stack<String> stack = new Stack();
+
+ while (tokenizer.hasMoreTokens()) {
+ StringBuilder tmp = new StringBuilder();
+ String token = tokenizer.nextToken();
+ if (!OPERATIONS.keySet().contains(token)) {
+ stack.push(token.trim());
+ } else {
+ String rightOperand = revertReplace(stack.pop());
+ String operator = token.replaceAll("^#|#$", "");
+
+ if (token.equalsIgnoreCase(MARKER_NOT_NULL) || token.equalsIgnoreCase(MARKER_NULL)) {
+ UniverseNodeInfo rightOperandInfo = nodeInfos.get(rightOperand);
+ stack.push(String.format(COMPARISON_FILTER, rightOperandInfo.getId(),
+ rightOperandInfo.getNodePath(), operator));
+ continue;
+ }
+
+ if (token.equalsIgnoreCase(MARKER_FILTER)) {
+ UniverseNodeInfo rightOperandInfo = nodeInfos.get(rightOperand);
+ stack.push(String.format(PREDEFINED_FILTER_TEMPLATE, rightOperandInfo.getNodePath(),
+ rightOperandInfo.getId()));
+ continue;
+ }
+
+ String leftOperand = stack.empty() ? null : revertReplace(stack.pop());
+
+ if (token.equalsIgnoreCase(MARKER_AND) || token.equalsIgnoreCase(MARKER_OR)) {
+ if (rightOperand.matches("^\\[.*\\]$")) {
+ UniverseNodeInfo rightOperandInfo = nodeInfos.get(rightOperand);
+ if (rightOperandInfo == null) {
+ throw new UniverseException(String.format("Not found information about: \"%s\"",
+ rightOperand));
+ }
+ rightOperand = String.format(PREDEFINED_FILTER_TEMPLATE,
+ rightOperandInfo.getNodePath(), rightOperandInfo.getId());
+ }
+ if (leftOperand.matches("^\\[.*\\]$")) {
+ UniverseNodeInfo leftOperandInfo = nodeInfos.get(leftOperand);
+ if (leftOperandInfo == null) {
+ throw new UniverseException(String.format("Not found information about: \"%s\"",
+ leftOperand));
+ }
+ leftOperand = String.format(PREDEFINED_FILTER_TEMPLATE, leftOperandInfo.getNodePath(),
+ leftOperandInfo.getId());
+ }
+ tmp.append(String.format("<%s>\n", operator));
+ tmp.append(leftOperand);
+ tmp.append("\n");
+ tmp.append(rightOperand);
+ tmp.append("\n");
+ tmp.append(String.format("</%s>\n", operator));
+ stack.push(tmp.toString());
+ continue;
+ }
+
+ UniverseNodeInfo leftOperandInfo = nodeInfos.get(leftOperand);
+ if (leftOperandInfo == null) {
+ throw new UniverseException(String.format("Not found information about: \"%s\"",
+ leftOperand));
+ }
+ if (token.equalsIgnoreCase(MARKER_IN) || token.equalsIgnoreCase(MARKER_NOT_IN)) {
+ String listValues = rightOperand.replaceAll("^\\(|\\)$", "").trim();
+ boolean startItem = false;
+ List<String> values = new ArrayList<>();
+ StringBuilder value = new StringBuilder();
+ boolean isNumericList = false;
+ if (listValues.charAt(0) != '\'') {
+ isNumericList = true;
+ }
+ if (isNumericList) {
+ String[] nums = listValues.split(",");
+ for (String num : nums) {
+ values.add(num.trim());
+ }
+ } else {
+ for (int i = 0; i < listValues.length(); i++) {
+ char c = listValues.charAt(i);
+ if (c == '\'' && (i == 0 || listValues.charAt(i - 1) != '\\')) {
+ startItem = !startItem;
+ if (!startItem) {
+ values.add(value.toString());
+ value = new StringBuilder();
+ }
+ continue;
+ }
+ if (startItem) {
+ value.append(c);
+ }
+ }
+ }
+
+ if (!values.isEmpty()) {
+ tmp.append(String.format(COMPRASION_START_TEMPLATE, leftOperandInfo.getNodePath(),
+ operator, leftOperandInfo.getId()));
+ tmp.append(CONST_OPERAND_START_TEMPLATE);
+ String type = isNumericList ? "Numeric" : "String";
+ for (String v : values) {
+ tmp.append(String.format(CONST_OPERAND_VALUE_TEMPLATE, type, v));
+ }
+ tmp.append(CONST_OPERAND_END_TEMPLATE);
+ tmp.append(COMPRASION_END_TEMPLATE);
+ stack.push(tmp.toString());
+ }
+ continue;
+ }
+
+ // EqualTo, LessThanOrEqualTo, NotEqualTo, LessThan, GreaterThanOrEqualTo, GreaterThan
+ UniverseNodeInfo rightOperandInfo = null;
+ if (rightOperand.startsWith("[") && rightOperand.endsWith("]")) {
+ rightOperandInfo = nodeInfos.get(rightOperand);
+ if (rightOperandInfo == null) {
+ throw new UniverseException(String.format("Not found information about: \"%s\"",
+ rightOperand));
+ }
+ }
+ if (OPERATIONS.containsKey(token)) {
+ if (rightOperandInfo != null) {
+ tmp.append(String.format(COMPRASION_START_TEMPLATE, leftOperandInfo.getNodePath(),
+ operator, leftOperandInfo.getId()));
+ tmp.append(String.format(OBJECT_OPERAND_TEMPLATE, rightOperandInfo.getId(),
+ rightOperandInfo.getNodePath()));
+ tmp.append(COMPRASION_END_TEMPLATE);
+ } else {
+ String type = rightOperand.startsWith("'") ? "String" : "Numeric";
+ String value = rightOperand.replaceAll("^'|'$", "");
+ tmp.append(String.format(COMPRASION_START_TEMPLATE, leftOperandInfo.getNodePath(),
+ operator, leftOperandInfo.getId()));
+ tmp.append(CONST_OPERAND_START_TEMPLATE);
+ tmp.append(String.format(CONST_OPERAND_VALUE_TEMPLATE, type, value));
+ tmp.append(CONST_OPERAND_END_TEMPLATE);
+ tmp.append(COMPRASION_END_TEMPLATE);
+ }
+ stack.push(tmp.toString());
+ continue;
+ }
+ throw new UniverseException(String.format("Incorrect syntax after: \"%s\"", leftOperand));
+ }
+ }
+
+ return stack.pop();
+ }
+
+ private String revertReplace(String s) {
+ return s.replaceAll(MARKER_BACKSPACE, " ")
+ .replaceAll(MARKER_LEFT_BRACE, "(")
+ .replaceAll(MARKER_RIGHT_BRACE, ")");
+ }
+
+ private boolean isFilter(String buf, String after) {
+ boolean result = false;
+ String[] parts = buf.trim().split("\\s");
+ if (parts[parts.length - 1].matches("^\\[.*\\]$")) {
+ // check before
+ if (parts.length == 1) {
+ result = true;
+ } else {
+ int count = parts.length - 2;
+ Set<String> operations = new HashSet(OPERATIONS.keySet());
+ operations.remove(MARKER_AND);
+ operations.remove(MARKER_OR);
+ while (count >= 0) {
+ String p = parts[count];
+ if (StringUtils.isNotBlank(p)) {
+ if (!operations.contains(p)) {
+ result = true;
+ break;
+ } else {
+ return false;
+ }
+ }
+ count--;
+ }
+ }
+ after = after.trim();
+ // check after
+ if (result && !after.startsWith("and") && !after.startsWith("or") &&
+ !after.startsWith(";") && StringUtils.isNotBlank(after)) {
+ result = false;
+ }
+ }
+
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/resources/interpreter-setting.json
----------------------------------------------------------------------
diff --git a/sap/src/main/resources/interpreter-setting.json b/sap/src/main/resources/interpreter-setting.json
new file mode 100644
index 0000000..cb5cf94
--- /dev/null
+++ b/sap/src/main/resources/interpreter-setting.json
@@ -0,0 +1,42 @@
+[
+ {
+ "group": "sap",
+ "name": "universe",
+ "className": "org.apache.zeppelin.sap.UniverseInterpreter",
+ "defaultInterpreter": true,
+ "properties": {
+ "universe.api.url": {
+ "envName": null,
+ "propertyName": "universe.api.url",
+ "defaultValue": "http://localhost:6405/biprws",
+ "description": "API url of Universe",
+ "type": "url"
+ },
+ "universe.user": {
+ "envName": null,
+ "propertyName": "universe.user",
+ "defaultValue": "",
+ "description": "Username for API of Universe",
+ "type": "string"
+ },
+ "universe.password": {
+ "envName": null,
+ "propertyName": "universe.password",
+ "defaultValue": "",
+ "description": "Password for API of Universe",
+ "type": "password"
+ },
+ "universe.authType": {
+ "envName": null,
+ "propertyName": "universe.password",
+ "defaultValue": "secEnterprise",
+ "description": "Type of authentication for API of Universe. Available values: secEnterprise, secLDAP, secWinAD, secSAPR3",
+ "type": "string"
+ }
+ },
+ "editor": {
+ "editOnDblClick": false,
+ "completionKey": "TAB"
+ }
+ }
+]
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/resources/universe.keywords
----------------------------------------------------------------------
diff --git a/sap/src/main/resources/universe.keywords b/sap/src/main/resources/universe.keywords
new file mode 100644
index 0000000..0811bfb
--- /dev/null
+++ b/sap/src/main/resources/universe.keywords
@@ -0,0 +1 @@
+universe,select,where,and,or,is null,is not null,in
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java
----------------------------------------------------------------------
diff --git a/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java
new file mode 100644
index 0000000..91a4217
--- /dev/null
+++ b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseCompleterTest.java
@@ -0,0 +1,134 @@
+/**
+ * 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.sap.universe;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.completer.CachedCompleter;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Universe completer unit tests
+ */
+public class UniverseCompleterTest {
+
+ private UniverseCompleter universeCompleter;
+ private UniverseUtil universeUtil;
+ private UniverseClient universeClient;
+
+ @Before
+ public void beforeTest() throws UniverseException {
+ universeCompleter = new UniverseCompleter(0);
+ universeUtil = new UniverseUtil();
+ Map<String, UniverseInfo> universes = new HashMap<>();
+
+ universes.put("testUniverse", new UniverseInfo("1", "testUniverse", "uvx"));
+ universes.put("test with space", new UniverseInfo("2", "test with space", "uvx"));
+ universes.put("(GLOBAL) universe", new UniverseInfo("3", "(GLOBAL) universe", "uvx"));
+ UniverseInfo universeInfo = new UniverseInfo("1", "testUniverse", "uvx");
+ Map<String, UniverseNodeInfo> testUniverseNodes = new HashMap<>();
+ testUniverseNodes.put("[Dimension].[Test].[name1]",
+ new UniverseNodeInfo("name1id", "name1", "dimension", "Dimension\\Test",
+ "Dimension|folder\\Test|folder\\name1|dimension"));
+ testUniverseNodes.put("[Dimension].[Test].[name2]",
+ new UniverseNodeInfo("name2id", "name2", "dimension", "Dimension\\Test",
+ "Dimension|folder\\Test|folder\\name2|dimension"));
+ testUniverseNodes.put("[Filter].[name3]",
+ new UniverseNodeInfo("name3id", "name3", "filter", "Filter",
+ "Filter|folder\\name3|filter"));
+ testUniverseNodes.put("[Filter].[name4]",
+ new UniverseNodeInfo("name4id", "name4", "filter", "Filter",
+ "Filter|folder\\name4|filter"));
+ testUniverseNodes.put("[Measure].[name5]",
+ new UniverseNodeInfo("name5id", "name5", "measure", "Measure",
+ "Measure|folder\\name5|measure"));
+
+ universeClient = mock(UniverseClient.class);
+ when(universeClient.getUniverseInfo(anyString())).thenReturn(universeInfo);
+ when(universeClient.getUniverseNodesInfo(anyString(), anyString()))
+ .thenReturn(testUniverseNodes);
+ when(universeClient.getUniversesMap()).thenReturn(universes);
+ }
+
+ @Test
+ public void testCreateUniverseNameCompleter() {
+ String buffer = "universe [";
+ List<CharSequence> candidates = new ArrayList<>();
+ universeCompleter.createOrUpdate(universeClient, null, buffer, 9);
+ CachedCompleter completer = universeCompleter.getUniverseCompleter();
+ assertNull(completer);
+ universeCompleter.createOrUpdate(universeClient, null, buffer, 10);
+ completer = universeCompleter.getUniverseCompleter();
+ assertNotNull(completer);
+
+ completer.getCompleter().complete(StringUtils.EMPTY, 0, candidates);
+ assertEquals(3, candidates.size());
+ }
+
+ @Test
+ public void testCreateUniverseNodesCompleter() {
+ String buffer = "universe [testUniverse]; select [";
+ List<CharSequence> candidates = new ArrayList<>();
+ universeCompleter.createOrUpdate(universeClient, null, buffer, 32);
+ Map<String, CachedCompleter> completerMap = universeCompleter.getUniverseInfoCompletersMap();
+ assertFalse(completerMap.containsKey("testUniverse"));
+ universeCompleter.createOrUpdate(universeClient, null, buffer, 33);
+ completerMap = universeCompleter.getUniverseInfoCompletersMap();
+ assertTrue(completerMap.containsKey("testUniverse"));
+ CachedCompleter completer = completerMap.get("testUniverse");
+
+ completer.getCompleter().complete(StringUtils.EMPTY, 0, candidates);
+ assertEquals(3, candidates.size());
+ List<String> candidatesStrings = new ArrayList<>();
+ for (Object o : candidates) {
+ UniverseNodeInfo info = (UniverseNodeInfo) o;
+ candidatesStrings.add(info.getName());
+ }
+ List<String> expected = Arrays.asList("Filter", "Measure", "Dimension");
+ Collections.sort(candidatesStrings);
+ Collections.sort(expected);
+ assertEquals(expected, candidatesStrings);
+ }
+
+ @Test
+ public void testNestedUniverseNodes() {
+ String buffer = "universe [testUniverse]; select [Dimension].[Test].[n";
+ List<CharSequence> candidates = new ArrayList<>();
+
+ universeCompleter.createOrUpdate(universeClient, null, buffer, 53);
+ Map<String, CachedCompleter> completerMap = universeCompleter.getUniverseInfoCompletersMap();
+ assertTrue(completerMap.containsKey("testUniverse"));
+ CachedCompleter completer = completerMap.get("testUniverse");
+
+ completer.getCompleter().complete("[Dimension].[Test].[n", 21, candidates);
+ assertEquals(2, candidates.size());
+ List<String> candidatesStrings = new ArrayList<>();
+ for (Object o : candidates) {
+ UniverseNodeInfo info = (UniverseNodeInfo) o;
+ candidatesStrings.add(info.getName());
+ }
+ List<String> expected = Arrays.asList("name1", "name2");
+ Collections.sort(candidatesStrings);
+ Collections.sort(expected);
+ assertEquals(expected, candidatesStrings);
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java
----------------------------------------------------------------------
diff --git a/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java
new file mode 100644
index 0000000..81a027e
--- /dev/null
+++ b/sap/src/test/java/org/apache/zeppelin/sap/universe/UniverseUtilTest.java
@@ -0,0 +1,371 @@
+/*
+ * 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.sap.universe;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class UniverseUtilTest {
+
+ private UniverseClient universeClient;
+ private UniverseUtil universeUtil;
+
+ @Before
+ public void beforeTest() throws UniverseException {
+ universeUtil = new UniverseUtil();
+ UniverseInfo universeInfo = new UniverseInfo("1", "testUniverse", "uvx");
+ Map<String, UniverseNodeInfo> testUniverseNodes = new HashMap<>();
+ testUniverseNodes.put("[Dimension].[Test].[name1]",
+ new UniverseNodeInfo("name1id", "name1", "dimension", "Dimension\\Test",
+ "Dimension|folder\\Test|folder\\name1|dimension"));
+ testUniverseNodes.put("[Dimension].[Test].[name2]",
+ new UniverseNodeInfo("name2id", "name2", "dimension", "Filter\\Test",
+ "Dimension|folder\\Test|folder\\name2|dimension"));
+ testUniverseNodes.put("[Filter].[name3]",
+ new UniverseNodeInfo("name3id", "name3", "filter", "Filter",
+ "Filter|folder\\name3|filter"));
+ testUniverseNodes.put("[Filter].[name4]",
+ new UniverseNodeInfo("name4id", "name4", "filter", "Filter",
+ "Filter|folder\\name4|filter"));
+ testUniverseNodes.put("[Measure].[name5]",
+ new UniverseNodeInfo("name5id", "name5", "measure", "Measure",
+ "Measure|folder\\name5|measure"));
+
+ universeClient = mock(UniverseClient.class);
+ when(universeClient.getUniverseInfo(anyString())).thenReturn(universeInfo);
+ when(universeClient.getUniverseNodesInfo(anyString(), anyString()))
+ .thenReturn(testUniverseNodes);
+ }
+
+ @Test
+ public void testForConvert() throws UniverseException {
+ String request = "universe [testUniverse];\n" +
+ "select [Measure].[name5]\n" +
+ "where [Filter].[name3] and [Dimension].[Test].[name2] > 1;";
+ UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null);
+ assertNotNull(universeQuery);
+ assertNotNull(universeQuery.getUniverseInfo());
+ assertEquals("<resultObjects>\n" +
+ "<resultObject path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n" +
+ "</resultObjects>", universeQuery.getSelect());
+ assertEquals("<and>\n" +
+ "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n" +
+ "\n<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" +
+ " operator=\"GreaterThan\" id=\"name2id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">1</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n", universeQuery.getWhere());
+ assertEquals("testUniverse", universeQuery.getUniverseInfo().getName());
+ }
+
+ @Test
+ public void testConvertConditions() throws UniverseException {
+ String request = "universe [testUniverse];\n" +
+ "select [Measure].[name5]\n" +
+ "where [Filter].[name3] " +
+ "and [Dimension].[Test].[name2] >= 1 " +
+ "and [Dimension].[Test].[name2] < 20 " +
+ "and [Dimension].[Test].[name1] <> 'test' " +
+ "and [Dimension].[Test].[name1] is not null " +
+ "and [Measure].[name5] is null" +
+ "and [Dimension].[Test].[name1] in ('var1', 'v a r 2') " +
+ "and [Dimension].[Test].[name1] in ('var1','withoutspaces')" +
+ "and [Dimension].[Test].[name1] in ('one value')" +
+ "and [Dimension].[Test].[name2] in (1,3,4);";
+ UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null);
+ assertNotNull(universeQuery);
+ assertEquals("<and>\n" +
+ "<and>\n" +
+ "<and>\n" +
+ "<and>\n" +
+ "<and>\n" +
+ "<and>\n" +
+ "<and>\n" +
+ "<and>\n" +
+ "<and>\n" +
+ "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" +
+ " operator=\"GreaterThanOrEqualTo\" id=\"name2id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">1</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" +
+ " operator=\"LessThan\" id=\"name2id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">20</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" +
+ " operator=\"NotEqualTo\" id=\"name1id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">test</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter id=\"name1id\" path=\"Dimension|folder\\Test|folder\\name1|dimension\"" +
+ " operator=\"IsNotNull\"/>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter id=\"name5id\" path=\"Measure|folder\\name5|measure\" operator=\"IsNull\"/>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" +
+ " operator=\"InList\" id=\"name1id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">var1</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">v a r 2</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" +
+ " operator=\"InList\" id=\"name1id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">var1</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">withoutspaces</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\"" +
+ " operator=\"InList\" id=\"name1id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">one value</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\"" +
+ " operator=\"InList\" id=\"name2id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">1</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">3</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">4</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</and>\n",
+ universeQuery.getWhere());
+ }
+
+ @Test(expected = UniverseException.class)
+ public void testFailConvertWithoutUniverse() throws UniverseException {
+ String request = "universe ;\n" +
+ "select [Measure].[name5]\n" +
+ "where [Filter].[name3] and [Dimension].[Test].[name2] > 1;";
+ universeUtil.convertQuery(request, universeClient, null);
+ }
+
+ @Test(expected = UniverseException.class)
+ public void testFailConvertWithIncorrectSelect() throws UniverseException {
+ String request = "universe [testUniverse];\n" +
+ "select [not].[exist];";
+ universeUtil.convertQuery(request, universeClient, null);
+ }
+
+
+ @Test(expected = UniverseException.class)
+ public void testFailConvertWithIncorrectCondition() throws UniverseException {
+ String request = "universe [testUniverse];\n" +
+ "select [Measure].[name5]\n" +
+ "where [Filter].[name;";
+ universeUtil.convertQuery(request, universeClient, null);
+ }
+
+ @Test
+ public void testFiltersConditions() throws UniverseException {
+ String request1 = "universe [testUniverse];\n" +
+ "select [Measure].[name5]\n" +
+ "where [Filter].[name3];";
+ String request2 = "universe [testUniverse];\n" +
+ "select [Measure].[name5]\n" +
+ "where [Measure].[name5] > 2 and [Filter].[name3];";
+ String request3 = "universe [testUniverse];\n" +
+ "select [Measure].[name5]\n" +
+ "where [Filter].[name3] or [Measure].[name5];";
+ String request4 = "universe [testUniverse];\n" +
+ "select [Measure].[name5]\n" +
+ "where [Filter].[name3] and [Measure].[name5] is null;";
+ UniverseQuery universeQuery = universeUtil.convertQuery(request1, universeClient, null);
+ assertEquals("<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n",
+ universeQuery.getWhere());
+ universeQuery = universeUtil.convertQuery(request2, universeClient, null);
+ assertEquals("<and>\n" +
+ "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"GreaterThan\" id=\"name5id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">2</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" +
+ "</and>\n",
+ universeQuery.getWhere());
+ universeQuery = universeUtil.convertQuery(request3, universeClient, null);
+ assertEquals("<or>\n" +
+ "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" +
+ "<predefinedFilter path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n\n" +
+ "</or>\n",
+ universeQuery.getWhere());
+ universeQuery = universeUtil.convertQuery(request4, universeClient, null);
+ assertEquals("<and>\n" +
+ "<predefinedFilter path=\"Filter|folder\\name3|filter\" id=\"name3id\"/>\n\n" +
+ "<comparisonFilter id=\"name5id\" path=\"Measure|folder\\name5|measure\" operator=\"IsNull\"/>\n\n" +
+ "</and>\n",
+ universeQuery.getWhere());
+ }
+
+ @Test
+ public void testNestedConditions() throws UniverseException {
+ String request = "universe [testUniverse];\n" +
+ "select [Dimension].[Test].[name2]\n" +
+ "where ([Measure].[name5] = 'text' or ([Dimension].[Test].[name1] in ('1','2', '3') and\n" +
+ "[Dimension].[Test].[name2] is not null)) and ([Filter].[name4] or [Measure].[name5] >=12)\n" +
+ "or [Dimension].[Test].[name2] not in (31, 65, 77);";
+ UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null);
+ assertEquals("<or>\n" +
+ "<and>\n" +
+ "<or>\n" +
+ "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"EqualTo\" id=\"name5id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">text</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "<and>\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name1|dimension\" operator=\"InList\" id=\"name1id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">1</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">2</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"String\">3</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "<comparisonFilter id=\"name2id\" path=\"Dimension|folder\\Test|folder\\name2|dimension\" operator=\"IsNotNull\"/>\n\n" +
+ "</and>\n\n" +
+ "</or>\n\n" +
+ "<or>\n" +
+ "<predefinedFilter path=\"Filter|folder\\name4|filter\" id=\"name4id\"/>\n\n" +
+ "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"GreaterThanOrEqualTo\" id=\"name5id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">12</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</or>\n\n" +
+ "</and>\n\n" +
+ "<comparisonFilter path=\"Dimension|folder\\Test|folder\\name2|dimension\" operator=\"NotInList\" id=\"name2id\">\n" +
+ "<constantOperand>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">31</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">65</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">77</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "</or>\n",
+ universeQuery.getWhere());
+ }
+
+ @Test
+ public void testWithoutConditions() throws UniverseException {
+ String request = "universe [testUniverse];\n" +
+ "select [Dimension].[Test].[name2], [Measure].[name5],\n" +
+ "[Dimension].[Test].[name1] ;";
+ UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null);
+ assertNull(universeQuery.getWhere());
+ assertEquals("<resultObjects>\n" +
+ "<resultObject path=\"Dimension|folder\\Test|folder\\name2|dimension\" id=\"name2id\"/>\n" +
+ "<resultObject path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n" +
+ "<resultObject path=\"Dimension|folder\\Test|folder\\name1|dimension\" id=\"name1id\"/>\n" +
+ "</resultObjects>",
+ universeQuery.getSelect());
+ }
+
+ @Test
+ public void testCaseSensitive() throws UniverseException {
+ String request = "uniVersE [testUniverse];\n" +
+ "seLEct [Dimension].[Test].[name2], [Measure].[name5]\n" +
+ "whERE [Dimension].[Test].[name2] Is NULl Or [Measure].[name5] IN (1,2) aNd [Measure].[name5] is NOT nUll;";
+ UniverseQuery universeQuery = universeUtil.convertQuery(request, universeClient, null);
+ assertEquals("<resultObjects>\n" +
+ "<resultObject path=\"Dimension|folder\\Test|folder\\name2|dimension\" id=\"name2id\"/>\n" +
+ "<resultObject path=\"Measure|folder\\name5|measure\" id=\"name5id\"/>\n" +
+ "</resultObjects>",
+ universeQuery.getSelect());
+ assertEquals("<or>\n" +
+ "<comparisonFilter id=\"name2id\" path=\"Dimension|folder\\Test|folder\\name2|dimension\" operator=\"IsNull\"/>\n\n" +
+ "<and>\n" +
+ "<comparisonFilter path=\"Measure|folder\\name5|measure\" operator=\"InList\" id=\"name5id\">\n" +
+ "<constantOperand>\n" + "<value>\n" + "<caption type=\"Numeric\">1</caption>\n" +
+ "</value>\n" +
+ "<value>\n" +
+ "<caption type=\"Numeric\">2</caption>\n" +
+ "</value>\n" +
+ "</constantOperand>\n" +
+ "</comparisonFilter>\n\n" +
+ "<comparisonFilter id=\"name5id\" path=\"Measure|folder\\name5|measure\" operator=\"IsNotNull\"/>\n\n" +
+ "</and>\n\n" +
+ "</or>\n",
+ universeQuery.getWhere());
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java
index 20cceda..fc5f380 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/completer/CompletionType.java
@@ -24,5 +24,6 @@ public enum CompletionType {
setting,
command,
keyword,
- path
+ path,
+ universe
}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index 81f9341..bae0c38 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -692,7 +692,8 @@ public class ZeppelinConfiguration extends XMLConfiguration {
+ "org.apache.zeppelin.beam.BeamInterpreter,"
+ "org.apache.zeppelin.scio.ScioInterpreter,"
+ "org.apache.zeppelin.groovy.GroovyInterpreter,"
- + "org.apache.zeppelin.neo4j.Neo4jCypherInterpreter"
+ + "org.apache.zeppelin.neo4j.Neo4jCypherInterpreter,"
+ + "org.apache.zeppelin.sap.UniverseInterpreter"
),
ZEPPELIN_INTERPRETER_JSON("zeppelin.interpreter.setting", "interpreter-setting.json"),
ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"),
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index 971257c..ee17130 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -800,7 +800,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
name: v.name,
value: v.value,
meta: v.meta,
- caption: computeCaption(v.value, v.meta),
+ caption: computeCaption(v.name, v.meta),
score: 300,
});
}
[2/2] zeppelin git commit: [ZEPPELIN-3194][NEW-INTERPRETER] SAP
Universe interpreter
Posted by mo...@apache.org.
[ZEPPELIN-3194][NEW-INTERPRETER] SAP Universe interpreter
### What is this PR for?
New interpreter, SAP BusinessObjects (Universes). See [documentation](https://github.com/masyan/zeppelin/blob/9178d1cc80253b9ae58b988443b619127f42d945/docs/interpreter/sap.md)
### What type of PR is it?
[Feature]
### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-3194
### How should this be tested?
see screenshots
### Screenshots (if appropriate)
Autocomplete + get results
![comletion_and_results](https://user-images.githubusercontent.com/705100/35770106-1fb3e9e6-0937-11e8-9ee4-df0c06a7b6c3.gif)
Errors
![errors](https://user-images.githubusercontent.com/705100/35770108-23de9674-0937-11e8-89e1-a0d3380e88bb.gif)
### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no
Author: Maksim Reshetov <jm...@gmail.com>
Closes #2763 from masyan/ZEPPELIN-3194 and squashes the following commits:
696b7dd54 [Maksim Reshetov] Merge remote-tracking branch 'upstream/master' into ZEPPELIN-3194
1e25fd1ee [Maksim Reshetov] [ZEPPELIN-3194] refactor parser os query. add tests
24336a855 [Maksim Reshetov] [ZEPPELIN-3194] fix parser for result objects and conditions
9178d1cc8 [Maksim Reshetov] [ZEPPELIN-3194] docs
ce97551b9 [Maksim Reshetov] Merge remote-tracking branch 'upstream/master' into ZEPPELIN-3194
0784ba030 [Maksim Reshetov] [ZEPPELIN-3194] docs
e00ace2e3 [Maksim Reshetov] [ZEPPELIN-3194] completer tests
1aeb7ad0f [Maksim Reshetov] [ZEPPELIN-3194] pom license
c1c09af5c [Maksim Reshetov] [ZEPPELIN-3194] docs fix + unit tests for util
f6b71f9c2 [Maksim Reshetov] [ZEPPELIN-3194] is null fix
Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/63c53fcc
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/63c53fcc
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/63c53fcc
Branch: refs/heads/master
Commit: 63c53fcc5979b8c2cbd8bae13de2561078780637
Parents: bfc93dc
Author: Maksim Reshetov <jm...@gmail.com>
Authored: Thu Mar 1 23:34:20 2018 +0500
Committer: Lee moon soo <mo...@apache.org>
Committed: Sun Mar 4 10:00:22 2018 -0800
----------------------------------------------------------------------
docs/index.md | 1 +
docs/interpreter/sap.md | 123 ++++
pom.xml | 1 +
sap/pom.xml | 86 +++
.../zeppelin/sap/UniverseInterpreter.java | 215 ++++++
.../zeppelin/sap/universe/UniverseClient.java | 733 +++++++++++++++++++
.../sap/universe/UniverseCompleter.java | 344 +++++++++
.../sap/universe/UniverseException.java | 38 +
.../zeppelin/sap/universe/UniverseInfo.java | 60 ++
.../zeppelin/sap/universe/UniverseNodeInfo.java | 85 +++
.../sap/universe/UniverseNodeInfoCompleter.java | 183 +++++
.../zeppelin/sap/universe/UniverseQuery.java | 53 ++
.../sap/universe/UniverseQueryPrompt.java | 110 +++
.../zeppelin/sap/universe/UniverseUtil.java | 643 ++++++++++++++++
sap/src/main/resources/interpreter-setting.json | 42 ++
sap/src/main/resources/universe.keywords | 1 +
.../sap/universe/UniverseCompleterTest.java | 134 ++++
.../zeppelin/sap/universe/UniverseUtilTest.java | 371 ++++++++++
.../zeppelin/completer/CompletionType.java | 3 +-
.../zeppelin/conf/ZeppelinConfiguration.java | 3 +-
.../notebook/paragraph/paragraph.controller.js | 2 +-
21 files changed, 3228 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/docs/index.md
----------------------------------------------------------------------
diff --git a/docs/index.md b/docs/index.md
index 3d42735..f00571e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -150,6 +150,7 @@ limitations under the License.
* [Postgresql, HAWQ](./interpreter/postgresql.html)
* [Python](./interpreter/python.html)
* [R](./interpreter/r.html)
+ * [SAP](./interpreter/sap.html)
* [Scalding](./interpreter/scalding.html)
* [Scio](./interpreter/scio.html)
* [Shell](./interpreter/Shell.html)
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/docs/interpreter/sap.md
----------------------------------------------------------------------
diff --git a/docs/interpreter/sap.md b/docs/interpreter/sap.md
new file mode 100644
index 0000000..be05aee
--- /dev/null
+++ b/docs/interpreter/sap.md
@@ -0,0 +1,123 @@
+---
+
+layout: page
+
+title: "SAP BusinessObjects Interpreter for Apache Zeppelin"
+
+description: "SAP BusinessObjects BI platform can simplify the lives of business users and IT staff. SAP BusinessObjects is based on universes. The universe contains dual-semantic layer model. The users make queries upon universes. This interpreter is new interface for universes."
+
+group: interpreter
+
+---
+
+<!--
+
+Licensed 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.
+
+-->
+
+{% include JB/setup %}
+
+# SAP BusinessObjects (Universe) Interpreter for Apache Zeppelin
+
+<div id="toc"></div>
+
+## Overview
+
+[SAP BusinessObjects BI platform (universes)](https://help.sap.com/viewer/p/SAP_BUSINESSOBJECTS_BUSINESS_INTELLIGENCE_PLATFORM) can simplify the lives of business users and IT staff. SAP BusinessObjects is based on universes. The universe contains dual-semantic layer model. The users make queries upon universes. This interpreter is new interface for universes.
+
+*Disclaimer* SAP interpreter is not official interpreter for SAP BusinessObjects BI platform. It uses [BI Semantic Layer REST API](https://help.sap.com/viewer/5431204882b44fc98d56bd752e69f132/4.2.5/en-US/ec54808e6fdb101497906a7cb0e91070.html)
+
+This interpreter is not directly supported by SAP AG.
+
+Tested with versions 4.2SP3 (14.2.3.2220) and 4.2SP5. There is no support for filters in UNX-universes converted from old UNV format.
+
+The universe name must be unique.
+
+## Configuring SAP Universe Interpreter
+
+At the "Interpreters" menu, you can edit SAP interpreter or create new one. Zeppelin provides these properties for SAP.
+
+<table class="table-configuration">
+ <tr>
+ <th>Property Name</th>
+ <th>Value</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>universe.api.url</td>
+ <td>http://localhost:6405/biprws</td>
+ <td>The base url for the SAP BusinessObjects BI platform. You have to edit "localhost" that you may use (ex. http://0.0.0.0:6405/biprws)</td>
+ </tr>
+ <tr>
+ <td>universe.authType</td>
+ <td>secEnterprise</td>
+ <td>The type of authentication for API of Universe. Available values: secEnterprise, secLDAP, secWinAD, secSAPR3</td>
+ </tr>
+ <tr>
+ <td>universe.password</td>
+ <td></td>
+ <td>The BI platform user password</td>
+ </tr>
+ <tr>
+ <td>universe.user</td>
+ <td>Administrator</td>
+ <td>The BI platform user login</td>
+ </tr>
+</table>
+
+![SAP Interpreter Setting]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/sap-interpreter-setting.png)
+
+### How to use
+
+<li> Choose the universe
+<li> Choose dimensions and measures in `select` statement
+<li> Define conditions in `where` statement
+You can compare two dimensions/measures or use Filter (without value).
+Dimesions/Measures can be compared with static values, may be `is null` or `is not null`, contains or not in list.
+Available the nested conditions (using braces "()"). "and" operator have more priority than "or".
+
+
+If generated query contains promtps, then promtps will appear as dynamic form after paragraph submitting.
+
+Example query
+
+```
+%sap
+
+universe [Universe Name];
+
+select
+
+ [Folder1].[Dimension2],
+
+ [Folder2].[Dimension3],
+
+ [Measure1]
+
+where
+
+ [Filter1]
+
+ and [Date] > '2018-01-01 00:00:00'
+
+ and [Folder1].[Dimension4] is not null
+
+ and [Folder1].[Dimension5] in ('Value1', 'Value2');
+```
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 6ce20aa..dc6d64b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,6 +81,7 @@
<module>zeppelin-server</module>
<module>zeppelin-jupyter</module>
<module>zeppelin-distribution</module>
+ <module>sap</module>
</modules>
<properties>
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/pom.xml
----------------------------------------------------------------------
diff --git a/sap/pom.xml b/sap/pom.xml
new file mode 100644
index 0000000..f0bf317
--- /dev/null
+++ b/sap/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>interpreter-parent</artifactId>
+ <groupId>org.apache.zeppelin</groupId>
+ <version>0.9.0-SNAPSHOT</version>
+ <relativePath>../interpreter-parent</relativePath>
+ </parent>
+
+ <groupId>org.apache.zeppelin</groupId>
+ <artifactId>sap</artifactId>
+ <packaging>jar</packaging>
+ <version>0.9.0-SNAPSHOT</version>
+ <name>Zeppelin: Sap</name>
+ <description>Zeppelin SAP support</description>
+
+ <properties>
+ <interpreter.name>sap</interpreter.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>zeppelin-interpreter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/UniverseInterpreter.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/UniverseInterpreter.java b/sap/src/main/java/org/apache/zeppelin/sap/UniverseInterpreter.java
new file mode 100644
index 0000000..1fdb6d6
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/UniverseInterpreter.java
@@ -0,0 +1,215 @@
+/*
+ * 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.sap;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterException;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
+import org.apache.zeppelin.sap.universe.*;
+
+
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * SAP Universe interpreter for Zeppelin.
+ */
+public class UniverseInterpreter extends Interpreter {
+
+ public UniverseInterpreter(Properties properties) {
+ super(properties);
+ }
+
+ private UniverseClient client;
+ private UniverseUtil universeUtil;
+ private UniverseCompleter universeCompleter;
+
+ private static final String EMPTY_COLUMN_VALUE = StringUtils.EMPTY;
+ private static final char WHITESPACE = ' ';
+ private static final char NEWLINE = '\n';
+ private static final char TAB = '\t';
+ private static final String TABLE_MAGIC_TAG = "%table ";
+
+ @Override
+ public void open() throws InterpreterException {
+ String user = getProperty("universe.user");
+ String password = getProperty("universe.password");
+ String apiUrl = getProperty("universe.api.url");
+ String authType = getProperty("universe.authType");
+ this.client = new UniverseClient(user, password, apiUrl, authType);
+ this.universeUtil = new UniverseUtil();
+ }
+
+ @Override
+ public void close() throws InterpreterException {
+ try {
+ client.close();
+ } catch (Exception e) {
+ throw new InterpreterException(e.getCause());
+ }
+ }
+
+ @Override
+ public InterpreterResult interpret(String st, InterpreterContext context)
+ throws InterpreterException {
+ try {
+ InterpreterResult interpreterResult = new InterpreterResult(InterpreterResult.Code.SUCCESS);
+ String paragraphId = context.getParagraphId();
+ String token = client.getToken(paragraphId);
+ client.loadUniverses(token);
+ UniverseQuery universeQuery = universeUtil.convertQuery(st, client, token);
+ String queryId = client.createQuery(token, universeQuery);
+ // process parameters
+ List<UniverseQueryPrompt> parameters = client.getParameters(token, queryId);
+
+ for (UniverseQueryPrompt parameter : parameters) {
+ Object value = context.getGui().getParams().get(parameter.getName());
+ if (value != null) {
+ parameter.setValue(value.toString());
+ }
+ context.getGui().textbox(parameter.getName(), StringUtils.EMPTY);
+ }
+
+ if (!parameters.isEmpty() && parameters.size() != context.getGui().getParams().size()) {
+ client.deleteQuery(token, queryId);
+ interpreterResult.add("Set parameters");
+ return interpreterResult;
+ }
+
+ if (!parameters.isEmpty()) {
+ client.setParametersValues(token, queryId, parameters);
+ }
+
+ // get results
+ List<List<String>> results = client.getResults(token, queryId);
+ String table = formatResults(results);
+ // remove query
+ client.deleteQuery(token, queryId);
+ interpreterResult.add(table);
+ return interpreterResult;
+ } catch (Exception e) {
+ throw new InterpreterException(e.getMessage(), e);
+ } finally {
+ try {
+ client.closeSession(context.getParagraphId());
+ } catch (Exception e) {
+ logger.error("Error close SAP session", e );
+ }
+ }
+ }
+
+ @Override
+ public void cancel(InterpreterContext context) throws InterpreterException {
+ try {
+ client.closeSession(context.getParagraphId());
+ } catch (Exception e) {
+ logger.error("Error close SAP session", e );
+ }
+ }
+
+ @Override
+ public FormType getFormType() throws InterpreterException {
+ return FormType.NATIVE;
+ }
+
+ @Override
+ public int getProgress(InterpreterContext context) throws InterpreterException {
+ return 0;
+ }
+
+ @Override
+ public List<InterpreterCompletion> completion(String buf, int cursor,
+ InterpreterContext interpreterContext)
+ throws InterpreterException {
+ List<InterpreterCompletion> candidates = new ArrayList<>();
+
+ try {
+ universeCompleter = createOrUpdateUniverseCompleter(interpreterContext, buf, cursor);
+ universeCompleter.complete(buf, cursor, candidates);
+ } catch (UniverseException e) {
+ logger.error("Error update completer", e );
+ }
+
+ return candidates;
+ }
+
+ private String formatResults(List<List<String>> results) {
+ StringBuilder msg = new StringBuilder();
+ if (results != null) {
+ msg.append(TABLE_MAGIC_TAG);
+ for (int i = 0; i < results.size(); i++) {
+ List<String> items = results.get(i);
+ for (int j = 0; j < items.size(); j++) {
+ if (j > 0) {
+ msg.append(TAB);
+ }
+ msg.append(replaceReservedChars(items.get(j)));
+ }
+ msg.append(NEWLINE);
+ }
+ }
+
+ return msg.toString();
+ }
+
+ private String replaceReservedChars(String str) {
+ if (str == null) {
+ return EMPTY_COLUMN_VALUE;
+ }
+ return str.replace(TAB, WHITESPACE).replace(NEWLINE, WHITESPACE);
+ }
+
+ private UniverseCompleter createOrUpdateUniverseCompleter(InterpreterContext interpreterContext,
+ final String buf, final int cursor)
+ throws UniverseException {
+ final UniverseCompleter completer;
+ if (universeCompleter == null) {
+ completer = new UniverseCompleter(3600);
+ } else {
+ completer = universeCompleter;
+ }
+ try {
+ final String token = client.getToken(interpreterContext.getParagraphId());
+ ExecutorService executorService = Executors.newFixedThreadPool(1);
+ executorService.execute(new Runnable() {
+ @Override
+ public void run() {
+ completer.createOrUpdate(client, token, buf, cursor);
+ }
+ });
+
+ executorService.shutdown();
+
+ executorService.awaitTermination(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ logger.warn("Completion timeout", e);
+ } finally {
+ try {
+ client.closeSession(interpreterContext.getParagraphId());
+ } catch (Exception e) {
+ logger.error("Error close SAP session", e );
+ }
+ }
+ return completer;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseClient.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseClient.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseClient.java
new file mode 100644
index 0000000..b60eac4
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseClient.java
@@ -0,0 +1,733 @@
+/*
+ * 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.sap.universe;
+
+import com.sun.org.apache.xpath.internal.NodeSet;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Client for API SAP Universe
+ */
+public class UniverseClient {
+
+ private static Logger logger = LoggerFactory.getLogger(UniverseClient.class);
+ private static final String TOKEN_HEADER = "X-SAP-LogonToken";
+ private static final String EL_FOLDER = "folder";
+ private static final String EL_ITEM = "item";
+ private static final String EL_NAME = "name";
+ private static final String EL_PATH = "path";
+ private static final String EL_ID = "id";
+ private static final String EL_TECH_NAME = "technicalName";
+ private static final String EL_ANSWER = "answer";
+ private static final String EL_INFO = "info";
+ private Map<String, String> tokens = new HashMap();
+ private static final long DAY = 1000 * 60 * 60 * 24;
+ private CloseableHttpClient httpClient;
+ private String user;
+ private String password;
+ private String apiUrl;
+ private String authType;
+ private Header[] commonHeaders = {
+ new BasicHeader("Accept", "application/xml"),
+ new BasicHeader("Content-Type", "application/xml")
+ };
+ // <name, id>
+ private final Map<String, UniverseInfo> universesMap = new ConcurrentHashMap();
+ private final Map<String, Map<String, UniverseNodeInfo>> universeInfosMap =
+ new ConcurrentHashMap();
+ // for update the data (which was not updated a long time)
+ private long universesUpdated = 0;
+ private Map<String, Long> universesInfoUpdatedMap = new HashMap<>();
+
+ private final String loginRequestTemplate = "<attrs xmlns=\"http://www.sap.com/rws/bip\">\n"
+ + "<attr name=\"userName\" type=\"string\">%s</attr>\n"
+ + "<attr name=\"password\" type=\"string\">%s</attr>\n"
+ + "<attr name=\"auth\" type=\"string\" "
+ + "possibilities=\"secEnterprise,secLDAP,secWinAD,secSAPR3\">%s</attr>\n" + "</attrs>";
+ private final String createQueryRequestTemplate =
+ "<query xmlns=\"http://www.sap.com/rws/sl/universe\" dataSourceType=\"%s\" " +
+ "dataSourceId=\"%s\">\n" +
+ "<querySpecification version=\"1.0\">\n" +
+ " <queryData>\n%s\n" +
+ " %s\n" +
+ "</queryData>\n" +
+ "</querySpecification>\n" +
+ "</query>\n";
+ private final String filterPartTemplate = "<filterPart>%s\n</filterPart>";
+ private final String errorMessageTemplate = "%s\n\n%s";
+ private final String parameterTemplate = "<parameter type=\"prompt\">\n" +
+ "%s\n" +
+ "%s\n" +
+ "%s\n" +
+ "%s\n" +
+ "</parameter>\n";
+ private final String parameterAnswerTemplate = "<answer constrained=\"%s\" type=\"%s\">\n" +
+ " <info cardinality=\"%s\" keepLastValues=\"%s\"></info>\n" +
+ " <values>\n" + " " +
+ " <value>%s</value>\n" +
+ " </values>\n" +
+ " </answer>\n";
+
+ public UniverseClient(String user, String password, String apiUrl, String authType) {
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(20 * 60 * 1000)
+ .setSocketTimeout(20 * 60 * 1000)
+ .build();
+ PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
+ cm.setMaxTotal(100);
+ cm.setDefaultMaxPerRoute(100);
+ cm.closeIdleConnections(10, TimeUnit.MINUTES);
+ httpClient = HttpClientBuilder.create()
+ .setConnectionManager(cm)
+ .setDefaultRequestConfig(requestConfig)
+ .build();
+
+ this.user = user;
+ this.password = password;
+ this.authType = authType;
+ if (StringUtils.isNotBlank(apiUrl)) {
+ this.apiUrl = apiUrl.replaceAll("/$", "");
+ }
+ }
+
+ public void close() throws UniverseException {
+ for (String s : tokens.keySet()) {
+ closeSession(s);
+ }
+ try {
+ httpClient.close();
+ } catch (Exception e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient " +
+ "(close all): Error close HTTP client", ExceptionUtils.getStackTrace(e)));
+ }
+
+ }
+
+ public String createQuery(String token, UniverseQuery query) throws UniverseException {
+ try {
+ HttpPost httpPost = new HttpPost(String.format("%s%s", apiUrl, "/sl/v1/queries"));
+ setHeaders(httpPost, token);
+ String where = StringUtils.isNotBlank(query.getWhere()) ?
+ String.format(filterPartTemplate, query.getWhere()) : StringUtils.EMPTY;
+ httpPost.setEntity(new StringEntity(
+ String.format(createQueryRequestTemplate, query.getUniverseInfo().getType(),
+ query.getUniverseInfo().getId(), query.getSelect(), where), "UTF-8"));
+ HttpResponse response = httpClient.execute(httpPost);
+
+ if (response.getStatusLine().getStatusCode() == 200) {
+ return getValue(EntityUtils.toString(response.getEntity()), "//success/id");
+ }
+
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(create query): Request failed\n", EntityUtils.toString(response.getEntity())));
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(create query): Request failed", ExceptionUtils.getStackTrace(e)));
+ } catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(create query): Response processing failed", ExceptionUtils.getStackTrace(e)));
+ }
+ }
+
+ public void deleteQuery(String token, String queryId) throws UniverseException {
+ try {
+ if (StringUtils.isNotBlank(queryId)) {
+ HttpDelete httpDelete = new HttpDelete(String.format("%s%s%s", apiUrl, "/sl/v1/queries/",
+ queryId));
+ setHeaders(httpDelete, token);
+ httpClient.execute(httpDelete);
+ }
+ } catch (Exception e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient " +
+ "(delete query): Request failed", ExceptionUtils.getStackTrace(e)));
+ }
+ }
+
+ public List<List<String>> getResults(String token, String queryId) throws UniverseException {
+ HttpGet httpGet = new HttpGet(String.format("%s%s%s%s", apiUrl, "/sl/v1/queries/",
+ queryId, "/data.svc/Flows0"));
+ setHeaders(httpGet, token);
+ HttpResponse response = null;
+ try {
+ response = httpClient.execute(httpGet);
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get results): Request failed\n", EntityUtils.toString(response.getEntity())));
+ }
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient " +
+ "(get results): Request failed", ExceptionUtils.getStackTrace(e)));
+ }
+
+ try (InputStream xmlStream = response.getEntity().getContent()) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(xmlStream);
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr = xpath.compile("//feed/entry/content/properties");
+ NodeList resultsNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ if (resultsNodes != null) {
+ return parseResults(resultsNodes);
+ } else {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get results): Response processing failed"));
+ }
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get results): Request failed", ExceptionUtils.getStackTrace(e)));
+ } catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get results): Response processing failed", ExceptionUtils.getStackTrace(e)));
+ }
+ }
+
+ public String getToken(String paragraphId) throws UniverseException {
+ try {
+ if (tokens.containsKey(paragraphId)) {
+ return tokens.get(paragraphId);
+ }
+ HttpPost httpPost = new HttpPost(String.format("%s%s", apiUrl, "/logon/long"));
+ setHeaders(httpPost);
+
+ httpPost.setEntity(new StringEntity(
+ String.format(loginRequestTemplate, user, password, authType), "UTF-8"));
+ HttpResponse response = httpClient.execute(httpPost);
+ String result = null;
+ if (response.getStatusLine().getStatusCode() == 200) {
+ result = getValue(EntityUtils.toString(response.getEntity()),
+ "//content/attrs/attr[@name=\"logonToken\"]");
+ tokens.put(paragraphId, result);
+ }
+
+ return result;
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get token): Request failed", ExceptionUtils.getStackTrace(e)));
+ } catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get token): Response processing failed", ExceptionUtils.getStackTrace(e)));
+ }
+ }
+
+ public boolean closeSession(String paragraphId) throws UniverseException {
+ try {
+ if (tokens.containsKey(paragraphId)) {
+ HttpPost httpPost = new HttpPost(String.format("%s%s", apiUrl, "/logoff"));
+ setHeaders(httpPost, tokens.get(paragraphId));
+ HttpResponse response = httpClient.execute(httpPost);
+ if (response.getStatusLine().getStatusCode() == 200) {
+ return true;
+ }
+ }
+
+ return false;
+ } catch (Exception e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(close session): Request failed", ExceptionUtils.getStackTrace(e)));
+ } finally {
+ tokens.remove(paragraphId);
+ }
+ }
+
+ public UniverseInfo getUniverseInfo(String universeName) {
+ return universesMap.get(universeName);
+ }
+
+ public Map<String, UniverseNodeInfo> getUniverseNodesInfo(String token, String universeName)
+ throws UniverseException {
+ UniverseInfo universeInfo = universesMap.get(universeName);
+ if (universeInfo != null && StringUtils.isNotBlank(universeInfo.getId())) {
+ Map<String, UniverseNodeInfo> universeNodeInfoMap = universeInfosMap.get(universeName);
+ if (universeNodeInfoMap != null && universesInfoUpdatedMap.containsKey(universeName) &&
+ !isExpired(universesInfoUpdatedMap.get(universeName))) {
+ return universeNodeInfoMap;
+ } else {
+ universeNodeInfoMap = new HashMap<>();
+ }
+ try {
+ HttpGet httpGet =
+ new HttpGet(String.format("%s%s%s", apiUrl, "/sl/v1/universes/", universeInfo.getId()));
+ setHeaders(httpGet, token);
+ HttpResponse response = httpClient.execute(httpGet);
+
+ if (response.getStatusLine().getStatusCode() == 200) {
+ try (InputStream xmlStream = response.getEntity().getContent()) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(xmlStream);
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr = xpath.compile("//outline/folder");
+ XPathExpression exprRootItems = xpath.compile("//outline/item");
+ NodeList universeInfoNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ NodeList universeRootInfoNodes =
+ (NodeList) exprRootItems.evaluate(doc, XPathConstants.NODESET);
+ if (universeInfoNodes != null) {
+ parseUniverseInfo(universeInfoNodes, universeNodeInfoMap);
+ }
+ if (universeRootInfoNodes != null) {
+ parseUniverseInfo(universeRootInfoNodes, universeNodeInfoMap);
+ }
+ } catch (Exception e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get universe nodes info): Response processing failed",
+ ExceptionUtils.getStackTrace(e)));
+ }
+ }
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get universe nodes info): Request failed", ExceptionUtils.getStackTrace(e)));
+ }
+ universeInfosMap.put(universeName, universeNodeInfoMap);
+ universesInfoUpdatedMap.put(universeName, System.currentTimeMillis());
+
+ return universeNodeInfoMap;
+ }
+ return Collections.emptyMap();
+
+ }
+
+ public void loadUniverses(String token) throws UniverseException {
+ if (universesMap.isEmpty() || universesUpdated == 0 || isExpired(universesUpdated)) {
+ Map<String, UniverseInfo> universes = new ConcurrentHashMap();
+ loadUniverses(token, 0, universes);
+ universesMap.clear();
+ universesMap.putAll(universes);
+ universesUpdated = System.currentTimeMillis();
+ }
+ }
+
+ public void cleanUniverses() {
+ universesMap.clear();
+ }
+
+ public void removeUniverseInfo(String universe) {
+ universeInfosMap.remove(universe);
+ }
+
+ public Map<String, UniverseInfo> getUniversesMap() {
+ return universesMap;
+ }
+
+ public List<UniverseQueryPrompt> getParameters(String token, String queryId)
+ throws UniverseException {
+ HttpGet httpGet = new HttpGet(String.format("%s%s%s%s", apiUrl, "/sl/v1/queries/",
+ queryId, "/parameters"));
+ setHeaders(httpGet, token);
+ HttpResponse response = null;
+ try {
+ response = httpClient.execute(httpGet);
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get parameters): Request failed\n", EntityUtils.toString(response.getEntity())));
+ }
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient " +
+ "(get parameters): Request failed", ExceptionUtils.getStackTrace(e)));
+ }
+
+ try (InputStream xmlStream = response.getEntity().getContent()) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(xmlStream);
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr = xpath.compile("//parameters/parameter");
+ NodeList parametersNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ if (parametersNodes != null) {
+ return parseParameters(parametersNodes);
+ } else {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get parameters): Response processing failed"));
+ }
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get parameters): Response processing failed", ExceptionUtils.getStackTrace(e)));
+ } catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get parameters): Response processing failed", ExceptionUtils.getStackTrace(e)));
+ }
+ }
+
+ public void setParametersValues(String token, String queryId,
+ List<UniverseQueryPrompt> parameters) throws UniverseException {
+ HttpPut httpPut = new HttpPut(String.format("%s%s%s%s", apiUrl, "/sl/v1/queries/",
+ queryId, "/parameters"));
+ setHeaders(httpPut, token);
+ HttpResponse response = null;
+ try {
+ StringBuilder request = new StringBuilder();
+ request.append("<parameters>\n");
+ for (UniverseQueryPrompt parameter : parameters) {
+ String answer = String.format(parameterAnswerTemplate, parameter.getConstrained(),
+ parameter.getType(), parameter.getCardinality(), parameter.getKeepLastValues(),
+ parameter.getValue());
+ String id = parameter.getId() != null ? String.format("<id>%s</id>\n", parameter.getId()) :
+ StringUtils.EMPTY;
+ String technicalName = parameter.getTechnicalName() != null ?
+ String.format("<technicalName>%s</technicalName>\n", parameter.getTechnicalName()) :
+ StringUtils.EMPTY;
+ String name = parameter.getTechnicalName() != null ?
+ String.format("<name>%s</name>\n", parameter.getName()) :
+ StringUtils.EMPTY;
+ request.append(String.format(parameterTemplate, id, technicalName, name, answer));
+ }
+ request.append("</parameters>\n");
+
+ httpPut.setEntity(new StringEntity(request.toString(), "UTF-8"));
+
+ response = httpClient.execute(httpPut);
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(set parameters): Request failed\n", EntityUtils.toString(response.getEntity())));
+ }
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient " +
+ "(set parameters): Request failed", ExceptionUtils.getStackTrace(e)));
+ }
+ }
+
+ private void loadUniverses(String token, int offset, Map<String, UniverseInfo> universesMap)
+ throws UniverseException {
+ int limit = 50;
+ HttpGet httpGet = new HttpGet(String.format("%s%s?offset=%s&limit=%s", apiUrl,
+ "/sl/v1/universes",
+ offset, limit));
+ setHeaders(httpGet, token);
+ HttpResponse response = null;
+ try {
+ response = httpClient.execute(httpGet);
+ } catch (Exception e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get universes): Request failed", ExceptionUtils.getStackTrace(e)));
+ }
+ if (response != null && response.getStatusLine().getStatusCode() == 200) {
+ try (InputStream xmlStream = response.getEntity().getContent()) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(xmlStream);
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr = xpath.compile("//universe");
+ NodeList universesNodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
+ if (universesNodes != null) {
+ int count = universesNodes.getLength();
+ for (int i = 0; i < count; i++) {
+ Node universe = universesNodes.item(i);
+ if (universe.hasChildNodes()) {
+ NodeList universeParameters = universe.getChildNodes();
+ int parapetersCount = universeParameters.getLength();
+ String id = null;
+ String name = null;
+ String type = null;
+ for (int j = 0; j < parapetersCount; j++) {
+ Node parameterNode = universeParameters.item(j);
+ parameterNode.getNodeName();
+ if (parameterNode.getNodeType() == Node.ELEMENT_NODE) {
+ if (parameterNode.getNodeName().equalsIgnoreCase("id")) {
+ id = parameterNode.getTextContent();
+ continue;
+ }
+ if (parameterNode.getNodeName().equalsIgnoreCase("name")) {
+ name = parameterNode.getTextContent();
+ continue;
+ }
+ if (parameterNode.getNodeName().equalsIgnoreCase("type")) {
+ type = parameterNode.getTextContent();
+ continue;
+ }
+ }
+ }
+ if (StringUtils.isNotBlank(type)) {
+ name = name.replaceAll(String.format("\\.%s$", type), StringUtils.EMPTY);
+ }
+ universesMap.put(name, new UniverseInfo(id, name, type));
+ }
+ }
+ if (count == limit) {
+ offset += limit;
+ loadUniverses(token, offset, universesMap);
+ }
+ }
+ } catch (IOException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get universes): Response processing failed", ExceptionUtils.getStackTrace(e)));
+ } catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
+ throw new UniverseException(String.format(errorMessageTemplate, "UniverseClient "
+ + "(get universes): Response processing failed", ExceptionUtils.getStackTrace(e)));
+ }
+ }
+ }
+
+ private boolean isExpired(Long lastUpdated) {
+ if (lastUpdated == null || System.currentTimeMillis() - lastUpdated > DAY) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private void setHeaders(HttpRequestBase request) {
+ setHeaders(request, null);
+ }
+
+ private void setHeaders(HttpRequestBase request, String token) {
+ request.setHeaders(commonHeaders);
+ if (StringUtils.isNotBlank(token)) {
+ request.addHeader(TOKEN_HEADER, token);
+ }
+ }
+
+ private String getValue(String response, String xPathString) throws ParserConfigurationException,
+ IOException, SAXException, XPathExpressionException {
+ try (InputStream xmlStream = new ByteArrayInputStream(response.getBytes())) {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(xmlStream);
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+ XPathExpression expr = xpath.compile(xPathString);
+ Node tokenNode = (Node) expr.evaluate(doc, XPathConstants.NODE);
+ if (tokenNode != null) {
+ return tokenNode.getTextContent();
+ }
+ }
+ return null;
+ }
+
+ private List<UniverseQueryPrompt> parseParameters(NodeList parametersNodeList) {
+ List<UniverseQueryPrompt> parameters = new ArrayList<>();
+ if (parametersNodeList != null) {
+ int count = parametersNodeList.getLength();
+ for (int i = 0; i < count; i++) {
+ Node parameterNode = parametersNodeList.item(i);
+ Node type = parameterNode.getAttributes().getNamedItem("type");
+ if (type != null && type.getTextContent().equalsIgnoreCase("prompt") &&
+ parameterNode.hasChildNodes()) {
+ NodeList parameterInfoNodes = parameterNode.getChildNodes();
+ int childNodesCount = parameterInfoNodes.getLength();
+ String name = null;
+ Integer id = null;
+ String cardinality = null;
+ String constrained = null;
+ String valueType = null;
+ String technicalName = null;
+ String keepLastValues = null;
+ for (int j = 0; j < childNodesCount; j++) {
+ Node childNode = parameterInfoNodes.item(j);
+ String childNodeName = childNode.getNodeName();
+ if (childNodeName.equalsIgnoreCase(EL_NAME)) {
+ name = childNode.getTextContent();
+ continue;
+ }
+ if (childNodeName.equalsIgnoreCase(EL_ID)) {
+ id = Integer.parseInt(childNode.getTextContent());
+ continue;
+ }
+ if (childNodeName.equalsIgnoreCase(EL_TECH_NAME)) {
+ technicalName = childNode.getTextContent();
+ continue;
+ }
+ if (childNodeName.equalsIgnoreCase(EL_ANSWER)) {
+ NamedNodeMap answerAttributes = childNode.getAttributes();
+ if (answerAttributes.getNamedItem("constrained") != null) {
+ constrained = answerAttributes.getNamedItem("constrained").getTextContent();
+ }
+ if (answerAttributes.getNamedItem("type") != null) {
+ valueType = answerAttributes.getNamedItem("type").getTextContent();
+ }
+ NodeList answerNodes = childNode.getChildNodes();
+ int answerCount = answerNodes.getLength();
+ for (int k = 0; k < answerCount; k++) {
+ Node answerChildNode = answerNodes.item(k);
+ String answerChildNodeName = answerChildNode.getNodeName();
+ if (answerChildNodeName.equalsIgnoreCase(EL_INFO)) {
+ NamedNodeMap infoAttributes = answerChildNode.getAttributes();
+ if (infoAttributes.getNamedItem("cardinality") != null) {
+ cardinality = infoAttributes.getNamedItem("cardinality").getTextContent();
+ }
+ if (infoAttributes.getNamedItem("keepLastValues") != null) {
+ keepLastValues = infoAttributes.getNamedItem("keepLastValues").getTextContent();
+ }
+ break;
+ }
+ }
+ continue;
+ }
+ }
+ if (name != null && id != null && cardinality != null) {
+ parameters.add(new UniverseQueryPrompt(id, name, cardinality, constrained, valueType,
+ technicalName, keepLastValues));
+ break;
+ }
+ }
+ }
+ }
+
+ return parameters;
+ }
+
+ private List<List<String>> parseResults(NodeList resultsNodeList) {
+ List<List<String>> results = new ArrayList<>();
+ if (resultsNodeList != null) {
+ int count = resultsNodeList.getLength();
+ for (int i = 0; i < count; i++) {
+ Node node = resultsNodeList.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE && node.hasChildNodes()) {
+ NodeList properties = node.getChildNodes();
+ if (properties != null) {
+ int countProperties = properties.getLength();
+ List<String> headers = new ArrayList<>();
+ List<String> row = new ArrayList<>();
+ // first property is id
+ for (int j = 1; j < countProperties; j++) {
+ Node propertyNode = properties.item(j);
+ if (i == 0) {
+ headers.add(propertyNode.getNodeName().replaceAll("^\\w*:", StringUtils.EMPTY));
+ }
+ row.add(propertyNode.getTextContent());
+ }
+ if (i == 0) {
+ results.add(headers);
+ }
+ results.add(row);
+ }
+ }
+ }
+ }
+
+ return results;
+ }
+
+ private void parseUniverseInfo(NodeList universeInfoNodes, Map<String, UniverseNodeInfo> nodes) {
+ if (universeInfoNodes != null) {
+ int count = universeInfoNodes.getLength();
+ for (int i = 0; i < count; i++) {
+ Node node = universeInfoNodes.item(i);
+ if (node.getNodeType() == Node.ELEMENT_NODE && node.hasChildNodes()) {
+ String name = node.getNodeName();
+ NodeList childNodes = node.getChildNodes();
+ int childNodesCount = childNodes.getLength();
+ if (name.equalsIgnoreCase(EL_FOLDER)) {
+ NodeSet list = new NodeSet();
+ for (int j = 0; j < childNodesCount; j++) {
+ Node childNode = childNodes.item(j);
+ if (childNode.getNodeType() == Node.ELEMENT_NODE && childNode.hasChildNodes()) {
+ String childNodeName = childNode.getNodeName();
+ if (childNodeName.equalsIgnoreCase(EL_FOLDER)
+ || childNodeName.equalsIgnoreCase(EL_ITEM)) {
+ list.addNode(childNode);
+ }
+ }
+ }
+ if (list.getLength() > 0) {
+ parseUniverseInfo(list, nodes);
+ }
+ } else if (name.equalsIgnoreCase(EL_ITEM)) {
+ String nodeId = null;
+ String nodeName = null;
+ String nodePath = null;
+ for (int j = 0; j < childNodesCount; j++) {
+ Node childNode = childNodes.item(j);
+ if (childNode.getNodeType() == Node.ELEMENT_NODE) {
+ String childNodeName = childNode.getNodeName();
+ if (childNodeName.equalsIgnoreCase(EL_NAME)) {
+ nodeName = childNode.getTextContent();
+ continue;
+ }
+ if (childNodeName.equalsIgnoreCase(EL_ID)) {
+ nodeId = childNode.getTextContent();
+ continue;
+ }
+ if (childNodeName.equalsIgnoreCase(EL_PATH)) {
+ nodePath = childNode.getTextContent();
+ continue;
+ }
+ }
+ }
+ String folder = null;
+ StringBuilder key = new StringBuilder();
+ if (StringUtils.isNotBlank(nodeName)) {
+ String nodeType = null;
+ if (StringUtils.isNotBlank(nodePath)) {
+ String[] parts = nodePath.split("\\\\");
+ List<String> path = new ArrayList();
+ for (String part : parts) {
+ String[] p = part.split("\\|");
+ if (p.length == 2) {
+ if (p[1].equalsIgnoreCase("folder")) {
+ path.add(p[0]);
+ } else {
+ nodeName = p[0];
+ nodeType = p[1];
+ }
+ }
+ }
+ folder = StringUtils.join(path, "\\");
+ if (path.isEmpty()) {
+ key.append(String.format("[%s]", nodeName));
+ } else {
+ key.append("[");
+ key.append(StringUtils.join(path, "].["));
+ key.append(String.format("].[%s]", nodeName));
+ }
+ }
+ nodes.put(key.toString(),
+ new UniverseNodeInfo(nodeId, nodeName, nodeType, folder, nodePath));
+ }
+ }
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseCompleter.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseCompleter.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseCompleter.java
new file mode 100644
index 0000000..e67011b
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseCompleter.java
@@ -0,0 +1,344 @@
+/*
+ * 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.sap.universe;
+
+import jline.console.completer.ArgumentCompleter.ArgumentList;
+import jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter;
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.completer.CachedCompleter;
+import org.apache.zeppelin.completer.CompletionType;
+import org.apache.zeppelin.completer.StringsCompleter;
+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.*;
+import java.util.regex.Pattern;
+
+/**
+ * SAP Universe auto complete functionality.
+ */
+public class UniverseCompleter {
+
+ private static Logger logger = LoggerFactory.getLogger(UniverseCompleter.class);
+
+ private static final String KEYWORD_SPLITERATOR = ",";
+ public static final String CLEAN_NAME_REGEX = "\\[|\\]";
+ public static final Character START_NAME = '[';
+ public static final Character END_NAME = ']';
+ public static final String KW_UNIVERSE = "universe";
+ public static final String TYPE_FOLDER = "folder";
+
+ private static final Comparator nodeInfoComparator = new Comparator<UniverseNodeInfo>() {
+ @Override
+ public int compare(UniverseNodeInfo o1, UniverseNodeInfo o2) {
+ if (o1.getType().equalsIgnoreCase(TYPE_FOLDER)
+ && o2.getType().equalsIgnoreCase(TYPE_FOLDER)) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ if (o1.getType().equalsIgnoreCase(TYPE_FOLDER)) {
+ return -1;
+ }
+ if (o2.getType().equalsIgnoreCase(TYPE_FOLDER)) {
+ return 1;
+ }
+ if (!o1.getType().equalsIgnoreCase(o2.getType())) {
+ return o1.getType().compareToIgnoreCase(o2.getType());
+ } else {
+
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ }
+ };
+
+ /**
+ * Delimiter that can split keyword list
+ */
+ private WhitespaceArgumentDelimiter sqlDelimiter = new WhitespaceArgumentDelimiter() {
+
+ private Pattern pattern = Pattern.compile(",|;");
+
+ @Override
+ public boolean isDelimiterChar(CharSequence buffer, int pos) {
+ char c = buffer.charAt(pos);
+ boolean endName = false;
+ for (int i = pos; i > 0; i--) {
+ char ch = buffer.charAt(i);
+ if (ch == '\n') {
+ break;
+ }
+ if (ch == START_NAME && !endName) {
+ return false;
+ }
+ if (ch == END_NAME) {
+ break;
+ }
+ }
+ return pattern.matcher(StringUtils.EMPTY + buffer.charAt(pos)).matches()
+ || super.isDelimiterChar(buffer, pos);
+ }
+ };
+
+ /**
+ * Universe completer
+ */
+ private CachedCompleter universeCompleter;
+
+ /**
+ * Keywords completer
+ */
+ private CachedCompleter keywordCompleter;
+
+ /**
+ * UniverseInfo completers
+ */
+ private Map<String, CachedCompleter> universeInfoCompletersMap = new HashMap<>();
+
+ private int ttlInSeconds;
+
+ public UniverseCompleter(int ttlInSeconds) {
+ this.ttlInSeconds = ttlInSeconds;
+ }
+
+ public int complete(String buffer, int cursor, List<InterpreterCompletion> candidates) {
+ CursorArgument cursorArgument = parseCursorArgument(buffer, cursor);
+
+ String argument = cursorArgument.getCursorArgumentPartForComplete();
+ if (cursorArgument.isUniverseNamePosition()) {
+ List<CharSequence> universeCandidates = new ArrayList<>();
+ universeCompleter.getCompleter().complete(argument, argument.length(),
+ universeCandidates);
+ addCompletions(candidates, universeCandidates, CompletionType.universe.name());
+ return universeCandidates.size();
+ }
+
+ if (cursorArgument.isUniverseNodePosition()) {
+ List universeNodeCandidates = new ArrayList();
+ CachedCompleter completer = universeInfoCompletersMap.get(cursorArgument.getUniverse());
+ if (completer != null) {
+ completer.getCompleter().complete(argument, argument.length(), universeNodeCandidates);
+ }
+ Collections.sort(universeNodeCandidates, nodeInfoComparator);
+ addCompletions(candidates, universeNodeCandidates);
+ return universeNodeCandidates.size();
+ }
+
+ List<CharSequence> keywordCandidates = new ArrayList<>();
+ keywordCompleter.getCompleter().complete(argument,
+ argument.length() > 0 ? argument.length() : 0, keywordCandidates);
+ addCompletions(candidates, keywordCandidates, CompletionType.keyword.name());
+
+ return keywordCandidates.size();
+ }
+
+ public void createOrUpdate(UniverseClient client, String token, String buffer, int cursor) {
+ try {
+ CursorArgument cursorArgument = parseCursorArgument(buffer, cursor);
+ if (keywordCompleter == null || keywordCompleter.getCompleter() == null
+ || keywordCompleter.isExpired()) {
+ Set<String> keywords = getKeywordsCompletions();
+ if (keywords != null && !keywords.isEmpty()) {
+ keywordCompleter = new CachedCompleter(new StringsCompleter(keywords), 0);
+ }
+ }
+ if (cursorArgument.needLoadUniverses() || (universeCompleter == null
+ || universeCompleter.getCompleter() == null || universeCompleter.isExpired())) {
+ client.cleanUniverses();
+ client.loadUniverses(token);
+ if (client.getUniversesMap().size() > 0) {
+ universeCompleter = new CachedCompleter(
+ new StringsCompleter(client.getUniversesMap().keySet()), ttlInSeconds);
+ }
+ }
+ if (cursorArgument.needLoadUniverseInfo() &&
+ (!universeInfoCompletersMap.containsKey(cursorArgument.getUniverse()) ||
+ universeInfoCompletersMap.get(cursorArgument.getUniverse()).getCompleter() == null ||
+ universeInfoCompletersMap.get(cursorArgument.getUniverse()).isExpired())) {
+ if (StringUtils.isNotBlank(cursorArgument.getUniverse())) {
+ client.removeUniverseInfo(cursorArgument.getUniverse());
+ Map<String, UniverseNodeInfo> info = client.getUniverseNodesInfo(token, cursorArgument
+ .getUniverse());
+ CachedCompleter completer = new CachedCompleter(
+ new UniverseNodeInfoCompleter(info.values()), ttlInSeconds);
+ universeInfoCompletersMap.put(cursorArgument.getUniverse(), completer);
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Failed to update completions", e);
+ }
+ }
+
+ private Set<String> getKeywordsCompletions() throws IOException {
+ String keywords =
+ new BufferedReader(new InputStreamReader(
+ UniverseCompleter.class.getResourceAsStream("/universe.keywords"))).readLine();
+
+ Set<String> completions = new TreeSet<>();
+
+ if (StringUtils.isNotBlank(keywords)) {
+ String[] words = keywords.split(KEYWORD_SPLITERATOR);
+ for (String word : words) {
+ completions.add(word);
+ }
+ }
+
+ return completions;
+ }
+
+ private CursorArgument parseCursorArgument(String buffer, int cursor) {
+ CursorArgument result = new CursorArgument();
+ if (buffer != null && buffer.length() >= cursor) {
+ String buf = buffer.substring(0, cursor);
+ if (StringUtils.isNotBlank(buf)) {
+ ArgumentList argList = sqlDelimiter.delimit(buf, cursor);
+ int argIndex = argList.getCursorArgumentIndex();
+ if (argIndex == 0) {
+ result.setCursorArgumentPartForComplete(argList.getCursorArgument());
+ return result;
+ }
+
+ if (argIndex > 0 && argList.getArguments()[argIndex - 1].equalsIgnoreCase(KW_UNIVERSE)) {
+ result.setUniverseNamePosition(true);
+ result.setCursorArgumentPartForComplete(cleanName(argList.getCursorArgument()
+ .substring(0, argList.getArgumentPosition())));
+ return result;
+ }
+ if (argIndex > 1) {
+ for (int i = argIndex - 2; i >= 0; i--) {
+ if (argList.getArguments()[i].equalsIgnoreCase(KW_UNIVERSE)) {
+ result.setUniverse(cleanName(argList.getArguments()[i + 1]));
+ break;
+ }
+ }
+
+ if (StringUtils.isNotBlank(result.getUniverse())
+ && argList.getCursorArgument().startsWith(START_NAME.toString())) {
+ result.setCursorArgumentPartForComplete(
+ argList.getCursorArgument().substring(0, argList.getArgumentPosition()));
+ result.setUniverseNodePosition(true);
+ return result;
+ } else {
+ result.setCursorArgumentPartForComplete(argList.getCursorArgument()
+ .substring(0, argList.getArgumentPosition()));
+ }
+ }
+ }
+ }
+
+ if (result.getCursorArgumentPartForComplete() == null) {
+ result.setCursorArgumentPartForComplete(StringUtils.EMPTY);
+ }
+
+ return result;
+ }
+
+ private String cleanName(String name) {
+ return name.replaceAll(CLEAN_NAME_REGEX, StringUtils.EMPTY);
+ }
+
+ private void addCompletions(List<InterpreterCompletion> interpreterCompletions,
+ List<CharSequence> candidates, String meta) {
+ for (CharSequence candidate : candidates) {
+ String value;
+ if (meta.equalsIgnoreCase(CompletionType.universe.name())) {
+ value = String.format("%s%s;\n", candidate.toString(), END_NAME);
+ } else {
+ value = candidate.toString();
+ }
+ interpreterCompletions.add(new InterpreterCompletion(candidate.toString(), value, meta));
+ }
+ }
+
+ private void addCompletions(List<InterpreterCompletion> interpreterCompletions,
+ List<UniverseNodeInfo> candidates) {
+ for (UniverseNodeInfo candidate : candidates) {
+ String value;
+ if (candidate.getType().equalsIgnoreCase(TYPE_FOLDER)) {
+ value = String.format("%s%s.%s", candidate.getName(), END_NAME, START_NAME);
+ } else {
+ value = String.format("%s%s", candidate.getName(), END_NAME);
+ }
+ interpreterCompletions.add(new InterpreterCompletion(candidate.getName(), value,
+ candidate.getType()));
+ }
+ }
+
+ public CachedCompleter getUniverseCompleter() {
+ return universeCompleter;
+ }
+
+ public Map<String, CachedCompleter> getUniverseInfoCompletersMap() {
+ return universeInfoCompletersMap;
+ }
+
+ private class CursorArgument {
+ private boolean universeNamePosition = false;
+ private boolean universeNodePosition = false;
+ private String universe;
+ private String cursorArgumentPartForComplete;
+
+ public boolean isUniverseNamePosition() {
+ return universeNamePosition;
+ }
+
+ public void setUniverseNamePosition(boolean universeNamePosition) {
+ this.universeNamePosition = universeNamePosition;
+ }
+
+ public boolean isUniverseNodePosition() {
+ return universeNodePosition;
+ }
+
+ public void setUniverseNodePosition(boolean universeNodePosition) {
+ this.universeNodePosition = universeNodePosition;
+ }
+
+ public String getCursorArgumentPartForComplete() {
+ return cursorArgumentPartForComplete;
+ }
+
+ public void setCursorArgumentPartForComplete(String cursorArgumentPartForComplete) {
+ this.cursorArgumentPartForComplete = cursorArgumentPartForComplete;
+ }
+
+ public String getUniverse() {
+ return universe;
+ }
+
+ public void setUniverse(String universe) {
+ this.universe = universe;
+ }
+
+ public boolean needLoadUniverses() {
+ if (universe == null) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean needLoadUniverseInfo() {
+ if (universe != null && universeNodePosition) {
+ return true;
+ }
+ return false;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseException.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseException.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseException.java
new file mode 100644
index 0000000..8086f94
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sap.universe;
+
+
+/**
+ * Runtime Exception for SAP universe
+ */
+public class UniverseException extends Exception {
+
+ public UniverseException(Throwable e) {
+ super(e);
+ }
+
+ public UniverseException(String m) {
+ super(m);
+ }
+
+ public UniverseException(String msg, Throwable t) {
+ super(msg, t);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseInfo.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseInfo.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseInfo.java
new file mode 100644
index 0000000..4f40dce
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseInfo.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sap.universe;
+
+/**
+ * Info about of universe node
+ */
+public class UniverseInfo {
+ private String id;
+ private String name;
+ private String type;
+
+ public UniverseInfo() {
+ }
+
+ public UniverseInfo(String id, String name, String type) {
+ this.id = id;
+ this.name = name;
+ this.type = type;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfo.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfo.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfo.java
new file mode 100644
index 0000000..fe0c97e
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfo.java
@@ -0,0 +1,85 @@
+/*
+ * 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.sap.universe;
+
+/**
+ * Info about of universe item
+ */
+public class UniverseNodeInfo {
+ private String id;
+ private String name;
+ private String type;
+ private String folder;
+ private String nodePath;
+
+ public UniverseNodeInfo() {
+ }
+
+ public UniverseNodeInfo(String id, String name, String type, String folder, String nodePath) {
+ this.id = id;
+ this.name = name;
+ this.type = type;
+ this.folder = folder;
+ this.nodePath = nodePath;
+ }
+
+ public UniverseNodeInfo(String name, String type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getFolder() {
+ return folder;
+ }
+
+ public void setFolder(String folder) {
+ this.folder = folder;
+ }
+
+ public String getNodePath() {
+ return nodePath;
+ }
+
+ public void setNodePath(String nodePath) {
+ this.nodePath = nodePath;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfoCompleter.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfoCompleter.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfoCompleter.java
new file mode 100644
index 0000000..0704e1b
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseNodeInfoCompleter.java
@@ -0,0 +1,183 @@
+/*
+ * 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.sap.universe;
+
+import jline.console.completer.Completer;
+import jline.internal.Preconditions;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.*;
+
+/**
+ * Case-insensitive completer.
+ */
+public class UniverseNodeInfoCompleter implements Completer {
+ private final UniverseInfoTreeNode tree = new UniverseInfoTreeNode();
+
+ public UniverseNodeInfoCompleter() {
+ }
+
+ public UniverseNodeInfoCompleter(final Collection<UniverseNodeInfo> nodes) {
+ Preconditions.checkNotNull(nodes);
+ for (UniverseNodeInfo node : nodes) {
+ String folder = node.getFolder();
+ if (StringUtils.isBlank(folder)) {
+ tree.putInfo(node);
+ } else {
+ String[] path = folder.split("\\\\");
+ UniverseInfoTreeNode universeInfoTreeNode = tree;
+ for (String s : path) {
+ if (!universeInfoTreeNode.contains(s)) {
+ universeInfoTreeNode = universeInfoTreeNode.putFolder(s);
+ } else {
+ universeInfoTreeNode = universeInfoTreeNode.getFolder(s);
+ }
+ }
+ universeInfoTreeNode.putInfo(node);
+ }
+ }
+ }
+
+ public int complete(final String buffer, final int cursor, final List candidates) {
+ return completeCollection(buffer, cursor, candidates);
+ }
+
+ private int completeCollection(final String buffer, final int cursor,
+ final Collection candidates) {
+ Preconditions.checkNotNull(candidates);
+ if (buffer == null) {
+ candidates.addAll(tree.getNodesInfo());
+ } else {
+ String part = buffer.substring(0, cursor);
+ List<String> path = new ArrayList<>();
+ path.addAll(Arrays.asList(part.split("\\]\\.\\[")));
+ if (part.endsWith(UniverseCompleter.START_NAME.toString())) {
+ path.add(StringUtils.EMPTY);
+ }
+
+ UniverseInfoTreeNode treeNode = tree;
+ for (int i = 0; i < path.size() - 1; i++) {
+ String folder = cleanName(path.get(i));
+ if (treeNode.contains(folder)) {
+ treeNode = treeNode.getFolder(folder);
+ if (treeNode == null) {
+ break;
+ }
+ }
+ }
+ String p = cleanName(path.get(path.size() - 1)).toUpperCase();
+ if (treeNode != null && treeNode.getChildren() != null) {
+ if (p.isEmpty()) {
+ candidates.addAll(treeNode.getNodesInfo());
+ } else {
+ for (UniverseNodeInfo universeNodeInfo : treeNode.getNodesInfo()) {
+ if (universeNodeInfo.getName().toUpperCase().startsWith(p)) {
+ candidates.add(universeNodeInfo);
+ }
+ }
+ }
+ }
+ }
+
+ return candidates.isEmpty() ? -1 : 0;
+ }
+
+ private String cleanName(String name) {
+ return name.replaceAll(UniverseCompleter.CLEAN_NAME_REGEX, StringUtils.EMPTY);
+ }
+
+ private class UniverseInfoTreeNode {
+ private String name;
+ private boolean isFolder;
+ private Map<String, Object> children;
+
+ public UniverseInfoTreeNode() {
+ this.name = "/";
+ this.isFolder = true;
+ this.children = new HashMap<>();
+ }
+
+ public UniverseInfoTreeNode(String name) {
+ this.name = name;
+ this.isFolder = true;
+ this.children = new HashMap<>();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isFolder() {
+ return isFolder;
+ }
+
+ public void setFolder(boolean folder) {
+ isFolder = folder;
+ }
+
+ public Map<String, Object> getChildren() {
+ return children;
+ }
+
+ public void setChildren(Map<String, Object> children) {
+ this.children = children;
+ }
+
+ public boolean contains(String name) {
+ return children.containsKey(name);
+ }
+
+ public UniverseInfoTreeNode getFolder(String name) {
+ Object child = children.get(name);
+ if (child instanceof UniverseInfoTreeNode) {
+ return (UniverseInfoTreeNode) children.get(name);
+ }
+
+ return null;
+ }
+
+ public UniverseInfoTreeNode putFolder(String name) {
+ UniverseInfoTreeNode newNode = new UniverseInfoTreeNode(name);
+ children.put(name, newNode);
+ return newNode;
+ }
+
+ public void putInfo(UniverseNodeInfo info) {
+ children.put(info.getName(), info);
+ }
+
+ public List<UniverseNodeInfo> getNodesInfo() {
+ List<UniverseNodeInfo> list = new ArrayList<>();
+ if (children != null) {
+ for (Object o : children.values()) {
+ if (o instanceof UniverseNodeInfo) {
+ list.add((UniverseNodeInfo) o);
+ } else {
+ UniverseInfoTreeNode treeNode = (UniverseInfoTreeNode) o;
+ list.add(new UniverseNodeInfo(treeNode.getName(), UniverseCompleter.TYPE_FOLDER));
+ }
+ }
+ }
+
+ return list;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQuery.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQuery.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQuery.java
new file mode 100644
index 0000000..824d1be
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQuery.java
@@ -0,0 +1,53 @@
+/*
+ * 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.sap.universe;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Data of universe query
+ */
+public class UniverseQuery {
+ private String select;
+ private String where;
+ private UniverseInfo universeInfo;
+
+ public UniverseQuery(String select, String where, UniverseInfo universeInfo) {
+ this.select = select;
+ this.where = where;
+ this.universeInfo = universeInfo;
+ }
+
+ public boolean isCorrect() {
+ return StringUtils.isNotBlank(select) && universeInfo != null &&
+ StringUtils.isNotBlank(universeInfo.getId())
+ && StringUtils.isNotBlank(universeInfo.getName());
+ }
+
+ public String getSelect() {
+ return select;
+ }
+
+ public String getWhere() {
+ return where;
+ }
+
+ public UniverseInfo getUniverseInfo() {
+ return universeInfo;
+ }
+}
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/63c53fcc/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQueryPrompt.java
----------------------------------------------------------------------
diff --git a/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQueryPrompt.java b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQueryPrompt.java
new file mode 100644
index 0000000..04b2b49
--- /dev/null
+++ b/sap/src/main/java/org/apache/zeppelin/sap/universe/UniverseQueryPrompt.java
@@ -0,0 +1,110 @@
+/*
+ * 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.sap.universe;
+
+/**
+ * Info about parameter of universe query
+ */
+public class UniverseQueryPrompt {
+ private Integer id;
+ private String name;
+ private String cardinality;
+ private String constrained;
+ private String type;
+ private String value;
+ private String technicalName;
+ private String keepLastValues;
+
+ public UniverseQueryPrompt() {
+ }
+
+ public UniverseQueryPrompt(Integer id, String name, String cardinality, String constrained,
+ String type, String technicalName, String keepLastValues) {
+ this.id = id;
+ this.name = name;
+ this.cardinality = cardinality;
+ this.constrained = constrained;
+ this.type = type;
+ this.technicalName = technicalName;
+ this.keepLastValues = keepLastValues;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getCardinality() {
+ return cardinality;
+ }
+
+ public void setCardinality(String cardinality) {
+ this.cardinality = cardinality;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getConstrained() {
+ return constrained;
+ }
+
+ public void setConstrained(String constrained) {
+ this.constrained = constrained;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getTechnicalName() {
+ return technicalName;
+ }
+
+ public void setTechnicalName(String technicalName) {
+ this.technicalName = technicalName;
+ }
+
+ public String getKeepLastValues() {
+ return keepLastValues;
+ }
+
+ public void setKeepLastValues(String keepLastValues) {
+ this.keepLastValues = keepLastValues;
+ }
+}