/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.zeppelin.kylin;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContext;
import org.apache.zeppelin.interpreter.InterpreterPropertyBuilder;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Kylin interpreter for Zeppelin. (http://kylin.io)
*/
public class KylinInterpreter extends Interpreter {
Logger logger = LoggerFactory.getLogger(KylinInterpreter.class);
static final String KYLIN_QUERY_API_URL = "kylin.api.url";
static final String KYLIN_USERNAME = "kylin.api.user";
static final String KYLIN_PASSWORD = "kylin.api.password";
static final String KYLIN_QUERY_PROJECT = "kylin.query.project";
static final String KYLIN_QUERY_OFFSET = "kylin.query.offset";
static final String KYLIN_QUERY_LIMIT = "kylin.query.limit";
static final String KYLIN_QUERY_ACCEPT_PARTIAL = "kylin.query.ispartial";
static final Pattern KYLIN_TABLE_FORMAT_REGEX_LABEL = Pattern.compile("\"label\":\"(.*?)\"");
static final Pattern KYLIN_TABLE_FORMAT_REGEX = Pattern.compile("\"results\":\\[\\[\"(.*?)\"]]");
static {
Interpreter.register(
"kylin",
"kylin",
KylinInterpreter.class.getName(),
new InterpreterPropertyBuilder()
.add(KYLIN_USERNAME, "ADMIN", "username for kylin user")
.add(KYLIN_PASSWORD, "KYLIN", "password for kylin user")
.add(KYLIN_QUERY_API_URL, "http://<host>:<port>/kylin/api/query", "Kylin API.")
.add(KYLIN_QUERY_PROJECT, "default", "kylin project name")
.add(KYLIN_QUERY_OFFSET, "0", "kylin query offset")
.add(KYLIN_QUERY_LIMIT, "5000", "kylin query limit")
.add(KYLIN_QUERY_ACCEPT_PARTIAL, "true", "The kylin query partial flag").build());
}
public KylinInterpreter(Properties property) {
super(property);
}
@Override
public void open() {
}
@Override
public void close() {
}
@Override
public InterpreterResult interpret(String st, InterpreterContext context) {
try {
return executeQuery(st);
} catch (IOException e) {
logger.error("failed to query data in kylin ", e);
return new InterpreterResult(InterpreterResult.Code.ERROR, e.getMessage());
}
}
@Override
public void cancel(InterpreterContext context) {
}
@Override
public FormType getFormType() {
return FormType.SIMPLE;
}
@Override
public int getProgress(InterpreterContext context) {
return 0;
}
@Override
public List<String> completion(String buf, int cursor) {
return null;
}
public HttpResponse prepareRequest(String sql) throws IOException {
String KYLIN_PROJECT = getProperty(KYLIN_QUERY_PROJECT);
logger.info("project:" + KYLIN_PROJECT);
logger.info("sql:" + sql);
logger.info("acceptPartial:" + getProperty(KYLIN_QUERY_ACCEPT_PARTIAL));
logger.info("limit:" + getProperty(KYLIN_QUERY_LIMIT));
logger.info("offset:" + getProperty(KYLIN_QUERY_OFFSET));
byte[] encodeBytes = Base64.encodeBase64(new String(getProperty(KYLIN_USERNAME)
+ ":" + getProperty(KYLIN_PASSWORD)).getBytes("UTF-8"));
String postContent = new String("{\"project\":" + "\"" + KYLIN_PROJECT + "\""
+ "," + "\"sql\":" + "\"" + sql + "\""
+ "," + "\"acceptPartial\":" + "\"" + getProperty(KYLIN_QUERY_ACCEPT_PARTIAL) + "\""
+ "," + "\"offset\":" + "\"" + getProperty(KYLIN_QUERY_OFFSET) + "\""
+ "," + "\"limit\":" + "\"" + getProperty(KYLIN_QUERY_LIMIT) + "\"" + "}");
logger.info("post:" + postContent);
postContent = postContent.replaceAll("[\u0000-\u001f]", " ");
StringEntity entity = new StringEntity(postContent, "UTF-8");
entity.setContentType("application/json; charset=UTF-8");
logger.info("post url:" + getProperty(KYLIN_QUERY_API_URL));
HttpPost postRequest = new HttpPost(getProperty(KYLIN_QUERY_API_URL));
postRequest.setEntity(entity);
postRequest.addHeader("Authorization", "Basic " + new String(encodeBytes));
postRequest.addHeader("Accept-Encoding", "UTF-8");
HttpClient httpClient = HttpClientBuilder.create().build();
return httpClient.execute(postRequest);
}
private InterpreterResult executeQuery(String sql) throws IOException {
HttpResponse response = prepareRequest(sql);
if (response.getStatusLine().getStatusCode() != 200) {
logger.error("failed to execute query: " + response.getEntity().getContent().toString());
return new InterpreterResult(InterpreterResult.Code.ERROR,
"Failed : HTTP error code " + response.getStatusLine().getStatusCode());
}
BufferedReader br = new BufferedReader(
new InputStreamReader((response.getEntity().getContent())));
StringBuilder sb = new StringBuilder();
String output;
logger.info("Output from Server .... \n");
while ((output = br.readLine()) != null) {
logger.info(output);
sb.append(output).append('\n');
}
InterpreterResult rett = new InterpreterResult(InterpreterResult.Code.SUCCESS,
formatResult(sb.toString()));
return rett;
}
private String formatResult(String msg) {
StringBuilder res = new StringBuilder("%table ");
Matcher ml = KYLIN_TABLE_FORMAT_REGEX_LABEL.matcher(msg);
while (!ml.hitEnd() && ml.find()) {
res.append(ml.group(1) + " \t");
}
res.append(" \n");
Matcher mr = KYLIN_TABLE_FORMAT_REGEX.matcher(msg);
String table = null;
while (!mr.hitEnd() && mr.find()) {
table = mr.group(1);
}
String[] row = table.split("\"],\\[\"");
for (int i = 0; i < row.length; i++) {
String[] col = row[i].split("\",\"");
for (int j = 0; j < col.length; j++) {
res.append(col[j] + " \t");
}
res.append(" \n");
}
return res.toString();
}
}