This is an automated email from the ASF dual-hosted git repository.

zjffdu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new f4ca1a2  [ZEPPELIN-4571] Add MongoDB interpreter for Zeppelin
f4ca1a2 is described below

commit f4ca1a27af0ada77d7bdd1f0d1b4e44a9539a172
Author: Tang Xiao <tangxc1...@126.com>
AuthorDate: Sun Jan 19 17:44:15 2020 +0800

    [ZEPPELIN-4571] Add MongoDB interpreter for Zeppelin
    
    ### What is this PR for?
    add interpreter for mongodb
    
    ### What type of PR is it?
    [Feature]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/browse/ZEPPELIN-4571
    
    ### How should this be tested?
    unit test supplied
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the licenses files need update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? Yes,Updated
    
    Author: Tang Xiao <tangxc1...@126.com>
    
    Closes #3612 from tokenian/mongo-interpreter and squashes the following 
commits:
    
    48b95efbb [Tang Xiao] [ZEPPELIN-4571]add mongodb interpreter for convience
---
 .travis.yml                                        |   2 +-
 docs/_includes/themes/zeppelin/_navigation.html    |   1 +
 .../zeppelin/img/docs-img/mongo-examples.png       | Bin 0 -> 310717 bytes
 .../img/docs-img/mongo-interpreter-install.png     | Bin 0 -> 173720 bytes
 .../img/docs-img/mongo-interpreter-monitor.png     | Bin 0 -> 311948 bytes
 docs/interpreter/mongodb.md                        | 102 ++++++++++
 mongodb/README.md                                  |   8 +
 mongodb/pom.xml                                    |  60 ++++++
 .../zeppelin/mongodb/MongoDbInterpreter.java       | 206 +++++++++++++++++++++
 .../src/main/resources/interpreter-setting.json    |  84 +++++++++
 mongodb/src/main/resources/shell_extension.js      | 121 ++++++++++++
 .../zeppelin/mongodb/MongoDbInterpreterTest.java   | 159 ++++++++++++++++
 pom.xml                                            |   1 +
 zeppelin-web/bower.json                            |   3 +-
 14 files changed, 745 insertions(+), 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 26295ba..ec5e027 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -51,7 +51,7 @@ services:
 env:
   global:
     # Interpreters does not required by zeppelin-server integration tests
-    - 
INTERPRETERS='!beam,!hbase,!pig,!jdbc,!file,!flink,!ignite,!kylin,!lens,!cassandra,!elasticsearch,!bigquery,!alluxio,!scio,!livy,!groovy,!sap,!java,!geode,!neo4j,!hazelcastjet,!submarine,!sparql'
+    - 
INTERPRETERS='!beam,!hbase,!pig,!jdbc,!file,!flink,!ignite,!kylin,!lens,!cassandra,!elasticsearch,!bigquery,!alluxio,!scio,!livy,!groovy,!sap,!java,!geode,!neo4j,!hazelcastjet,!submarine,!sparql,!mongodb'
 
 matrix:
   include:
diff --git a/docs/_includes/themes/zeppelin/_navigation.html 
b/docs/_includes/themes/zeppelin/_navigation.html
index f19dc30..0a2563f 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -149,6 +149,7 @@
                 <li><a href="{{BASE_PATH}}/interpreter/lens.html">Lens</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/livy.html">Livy</a></li>
                 <li><a 
href="{{BASE_PATH}}/interpreter/markdown.html">Markdown</a></li>
+                <li><a 
href="{{BASE_PATH}}/interpreter/mongodb.html">MongoDB</a></li>
                 <li><a 
href="{{BASE_PATH}}/interpreter/neo4j.html">Neo4j</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/pig.html">Pig</a></li>
                 <li><a 
href="{{BASE_PATH}}/interpreter/postgresql.html">Postgresql, HAWQ</a></li>
diff --git a/docs/assets/themes/zeppelin/img/docs-img/mongo-examples.png 
b/docs/assets/themes/zeppelin/img/docs-img/mongo-examples.png
new file mode 100644
index 0000000..0e111c9
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/mongo-examples.png differ
diff --git 
a/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-install.png 
b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-install.png
new file mode 100644
index 0000000..c24834d
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-install.png differ
diff --git 
a/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-monitor.png 
b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-monitor.png
new file mode 100644
index 0000000..68cd0db
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/mongo-interpreter-monitor.png differ
diff --git a/docs/interpreter/mongodb.md b/docs/interpreter/mongodb.md
new file mode 100644
index 0000000..5561ee6
--- /dev/null
+++ b/docs/interpreter/mongodb.md
@@ -0,0 +1,102 @@
+---
+layout: page
+title: "MongoDB Interpreter for Apache Zeppelin"
+description: "MongoDB is a general purpose, document-based, distributed 
database built for modern application developers and for the cloud era."
+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 %}
+
+# MongoDB interpreter for Apache Zeppelin
+
+<div id="toc"></div>
+
+## Overview
+[MongoDB](https://www.mongodb.com/) is a general purpose, document-based, 
distributed database built for modern application developers and for the cloud 
era.
+This interpreter use mongo shell to execute 
[scripts](https://docs.mongodb.com/manual/tutorial/write-scripts-for-the-mongo-shell/)
+Use mongo-shell `JavaScript` to analyze data as you need.
+## Installing AND Configuration
+First, you need to install mongo shell with Zeppelin in the same machine.
+If you use mac with brew, follow this instructions.
+```
+brew tap mongodb/brew
+brew install mongodb/brew/mongodb-community-shell
+```
+Or you can follow this [mongo shell](https://docs.mongodb.com/manual/mongo/)
+Second, create mongodb interpreter in Zeppelin.
+![MongoDB interpreter 
install]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/mongo-interpreter-install.png)
+<table class="table-configuration">
+  <tr>
+    <th>Name</th>
+    <th>Default Value</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>mongo.shell.path</td>
+    <td>mongo</td>
+    <td>MongoDB shell local path. <br/> Use `which mongo` to get local path in 
linux or mac.</td>
+  </tr>
+  <tr>
+     <td>mongo.shell.command.table.limit</td>
+     <td>1000</td>
+     <td>Limit of documents displayed in a table. <br/> Use table function 
when get data from mongodb</td>
+  </tr>
+  <tr>
+     <td>mongo.shell.command.timeout</td>
+     <td>60000</td>
+     <td>MongoDB shell command timeout in millisecond</td>
+  </tr>
+  <tr>
+     <td>mongo.server.host</td>
+     <td>localhost</td>
+     <td>MongoDB server host to connect to</td>
+  </tr>
+  <tr>
+    <td>mongo.server.port</td>
+    <td>27017</td>
+    <td>MongoDB server port to connect to</td>
+  </tr>
+  <tr>
+    <td>mongo.server.database</td>
+    <td>test</td>
+    <td>MongoDB database name</td>
+  </tr>
+  <tr>
+     <td>mongo.server.authentdatabase</td>
+     <td></td>
+     <td>MongoDB database name for authentication</td>
+  </tr>
+  <tr>
+    <td>mongo.server.username</td>
+    <td></td>
+    <td>Username for authentication</td>
+  </tr>
+  <tr>
+    <td>mongo.server.password</td>
+    <td></td>
+    <td>Password for authentication</td>
+  </tr>
+  <tr>
+    <td>mongo.interpreter.concurrency.max</td>
+    <td>10</td>
+    <td>Max count of scheduler concurrency</td>
+  </tr>
+</table>
+## Examples
+The following example demonstrates the basic usage of MongoDB in a Zeppelin 
notebook.
+![MongoDB interpreter 
examples]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/mongo-examples.png)
+Or you can monitor stats of mongodb collections.
+![MongoDB interpreter 
examples]({{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/mongo-interpreter-monitor.png)
+
diff --git a/mongodb/README.md b/mongodb/README.md
new file mode 100644
index 0000000..7679864
--- /dev/null
+++ b/mongodb/README.md
@@ -0,0 +1,8 @@
+# Overview
+MongoDB interpreter for Apache Zeppelin. Thanksgiving to 
[bbonnin/zeppelin-mongodb-interpreter](https://github.com/bbonnin/zeppelin-mongodb-interpreter).
+I found bbonnin's mongodb interpreter was not working with newest zeppelin 
version, it has not been maintained for a long time.
+so I forked this for those people who want to use mongodb in zeppelin.
+
+### Technical overview
+it use mongo shell to execute scripts.All you need to do is to configure 
mongodb interpreter,
+and then study mongo aggregate functions.
diff --git a/mongodb/pom.xml b/mongodb/pom.xml
new file mode 100644
index 0000000..b3b4c1a
--- /dev/null
+++ b/mongodb/pom.xml
@@ -0,0 +1,60 @@
+<?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 
https://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>zeppelin-interpreter-parent</artifactId>
+        <groupId>org.apache.zeppelin</groupId>
+        <version>0.9.0-SNAPSHOT</version>
+        <relativePath>../zeppelin-interpreter-parent/pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.zeppelin</groupId>
+    <artifactId>zeppelin-mongodb</artifactId>
+    <packaging>jar</packaging>
+    <version>0.9.0-SNAPSHOT</version>
+    <name>Zeppelin: MongoDB interpreter</name>
+
+    <properties>
+        <interpreter.name>mongodb</interpreter.name>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-enforcer-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-shade-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git 
a/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java 
b/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java
new file mode 100644
index 0000000..97ff2da
--- /dev/null
+++ b/mongodb/src/main/java/org/apache/zeppelin/mongodb/MongoDbInterpreter.java
@@ -0,0 +1,206 @@
+/*
+ * 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.mongodb;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Scanner;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteException;
+import org.apache.commons.exec.ExecuteWatchdog;
+import org.apache.commons.exec.Executor;
+import org.apache.commons.exec.PumpStreamHandler;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.scheduler.Scheduler;
+import org.apache.zeppelin.scheduler.SchedulerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * MongoDB interpreter. It uses the mongo shell to interpret the commands.
+ */
+public class MongoDbInterpreter extends Interpreter {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(MongoDbInterpreter.class);
+
+  private String shellExtension = StringUtils.EMPTY;
+
+  private static final int SIGTERM_CODE = 143;
+
+  private long commandTimeout = 60000;
+
+  private String dbAddress;
+
+  private int maxConcurrency = 10;
+
+  private Map<String, Executor> runningProcesses =  new HashMap<>();
+
+  public MongoDbInterpreter(Properties property) {
+    super(property);
+  }
+
+  @Override
+  public void open() {
+    shellExtension = new 
Scanner(MongoDbInterpreter.class.getResourceAsStream("/shell_extension.js"), 
"UTF-8")
+            .useDelimiter("\\A").next();
+
+    commandTimeout = 
Long.parseLong(getProperty("mongo.shell.command.timeout"));
+    maxConcurrency = 
Integer.parseInt(getProperty("mongo.interpreter.concurrency.max"));
+
+    dbAddress = getProperty("mongo.server.host") + ":" + 
getProperty("mongo.server.port");
+
+    prepareShellExtension();
+  }
+
+  @Override
+  public void close() {
+    runningProcesses.clear();
+    runningProcesses = null;
+  }
+
+  @Override
+  public FormType getFormType() {
+    return FormType.SIMPLE;
+  }
+
+  @Override
+  public InterpreterResult interpret(String script, InterpreterContext 
context) {
+    LOGGER.debug("Run MongoDB script: {}", script);
+
+    if (StringUtils.isEmpty(script)) {
+      return new InterpreterResult(Code.SUCCESS);
+    }
+
+    String paragraphId = context.getParagraphId();
+    // Write script in a temporary file
+    // The script is enriched with extensions
+    final File scriptFile = new File(getScriptFileName(paragraphId));
+    try {
+      FileUtils.write(scriptFile, shellExtension + script);
+    }
+    catch (IOException e) {
+      LOGGER.error("Can not write script in temp file", e);
+      return new InterpreterResult(Code.ERROR, e.getMessage());
+    }
+
+    InterpreterResult result = new 
InterpreterResult(InterpreterResult.Code.SUCCESS);
+
+    final DefaultExecutor executor = new DefaultExecutor();
+    final ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
+
+    executor.setStreamHandler(new PumpStreamHandler(context.out, errorStream));
+    executor.setWatchdog(new ExecuteWatchdog(commandTimeout));
+
+    final CommandLine cmdLine = 
CommandLine.parse(getProperty("mongo.shell.path"));
+    cmdLine.addArgument("--quiet", false);
+    cmdLine.addArgument(dbAddress, false);
+    cmdLine.addArgument(scriptFile.getAbsolutePath(), false);
+
+    try {
+      executor.execute(cmdLine);
+      runningProcesses.put(paragraphId, executor);
+    }
+    catch (ExecuteException e) {
+      LOGGER.error("Can not run script in paragraph {}", paragraphId, e);
+
+      final int exitValue = e.getExitValue();
+      Code code = Code.ERROR;
+      String msg = errorStream.toString();
+
+      if (exitValue == SIGTERM_CODE) {
+        code = Code.INCOMPLETE;
+        msg = msg + "Paragraph received a SIGTERM.\n";
+        LOGGER.info("The paragraph {} stopped executing: {}", paragraphId, 
msg);
+      }
+
+      msg += "ExitValue: " + exitValue;
+      result = new InterpreterResult(code, msg);
+    }
+    catch (IOException e) {
+      LOGGER.error("Can not run script in paragraph {}", paragraphId, e);
+      result = new InterpreterResult(Code.ERROR, e.getMessage());
+    }
+    finally {
+      FileUtils.deleteQuietly(scriptFile);
+      stopProcess(paragraphId);
+    }
+
+    return result;
+  }
+
+  @Override
+  public int getProgress(InterpreterContext context) {
+    return 0;
+  }
+
+  @Override
+  public void cancel(InterpreterContext context) {
+    stopProcess(context.getParagraphId());
+    FileUtils.deleteQuietly(new 
File(getScriptFileName(context.getParagraphId())));
+  }
+
+  @Override
+  public Scheduler getScheduler() {
+    LOGGER.info("maxConcurrency is {}", maxConcurrency);
+    return 
SchedulerFactory.singleton().createOrGetParallelScheduler(MongoDbInterpreter.class.getName()
 + this.hashCode(), maxConcurrency);
+  }
+
+  private String getScriptFileName(String paragraphId) {
+    return String.format("%s%s.js",getScriptDir(),paragraphId);
+  }
+
+  private String getScriptDir(){
+    String tmpProperty = System.getProperty("java.io.tmpdir");
+    if (!tmpProperty.endsWith(File.separator)){
+      tmpProperty += File.separator;
+    }
+
+    return tmpProperty + "zeppelin-mongo-scripts" + File.separator;
+  }
+
+  private void stopProcess(String paragraphId) {
+    if (runningProcesses.containsKey(paragraphId)) {
+      final Executor executor = runningProcesses.get(paragraphId);
+      final ExecuteWatchdog watchdog = executor.getWatchdog();
+      watchdog.destroyProcess();
+      runningProcesses.remove(paragraphId);
+    }
+  }
+
+  /**
+   * use placeholders to replace properties
+   */
+  private void prepareShellExtension(){
+    shellExtension = shellExtension.replace("TABLE_LIMIT_PLACEHOLDER", 
getProperty("mongo.shell.command.table.limit"))
+            .replace("TARGET_DB_PLACEHOLDER", 
getProperty("mongo.server.database"))
+            .replace("USER_NAME_PLACEHOLDER", 
getProperty("mongo.server.username"))
+            .replace("PASSWORD_PLACEHOLDER", 
getProperty("mongo.server.password"))
+            .replace("AUTH_DB_PLACEHOLDER", 
getProperty("mongo.server.authenticationDatabase"));
+  }
+}
\ No newline at end of file
diff --git a/mongodb/src/main/resources/interpreter-setting.json 
b/mongodb/src/main/resources/interpreter-setting.json
new file mode 100644
index 0000000..706ba12
--- /dev/null
+++ b/mongodb/src/main/resources/interpreter-setting.json
@@ -0,0 +1,84 @@
+[
+  {
+    "group": "mongodb",
+    "name": "mongodb",
+    "className": "org.apache.zeppelin.mongodb.MongoDbInterpreter",
+    "properties": {
+      "mongo.shell.path": {
+        "envName": null,
+        "propertyName": "mongo.shell.path",
+        "defaultValue": "mongo",
+        "description": "MongoDB shell local path",
+        "type": "string"
+      },
+      "mongo.shell.command.table.limit": {
+        "envName": null,
+        "propertyName": "mongo.shell.command.table.limit",
+        "defaultValue": "1000",
+        "description": "Limit of documents displayed in a table",
+        "type": "number"
+      },
+      "mongo.shell.command.timeout": {
+        "envName": null,
+        "propertyName": "mongo.shell.command.timeout",
+        "defaultValue": "60000",
+        "description": "MongoDB shell command timeout",
+        "type": "number"
+      },
+      "mongo.server.host": {
+        "envName": null,
+        "propertyName": "mongo.server.host",
+        "defaultValue": "localhost",
+        "description": "MongoDB server host to connect to",
+        "type": "string"
+      },
+      "mongo.server.port": {
+        "envName": null,
+        "propertyName": "mongo.server.port",
+        "defaultValue": "27017",
+        "description": "MongoDB server port to connect to",
+        "type": "number"
+      },
+      "mongo.server.database": {
+        "envName": null,
+        "propertyName": "mongo.server.database",
+        "defaultValue": "test",
+        "description": "MongoDB database name",
+        "type": "string"
+      },
+      "mongo.server.authenticationDatabase": {
+        "envName": null,
+        "propertyName": "mongo.server.authenticationDatabase",
+        "defaultValue": "",
+        "description": "MongoDB database name for authentication",
+        "type": "string"
+      },
+      "mongo.server.username": {
+        "envName": null,
+        "propertyName": "mongo.server.username",
+        "defaultValue": "",
+        "description": "Username for authentication",
+        "type": "string"
+      },
+      "mongo.server.password": {
+        "envName": null,
+        "propertyName": "mongo.server.password",
+        "defaultValue": "",
+        "description": "Password for authentication",
+        "type": "password"
+      },
+      "mongo.interpreter.concurrency.max": {
+        "envName": null,
+        "propertyName": "mongo.interpreter.concurrency.max",
+        "defaultValue": "10",
+        "description": "Max count of scheduler concurrency",
+        "type": "number"
+      }
+    },
+    "editor": {
+      "language": "javascript",
+      "editOnDblClick": false,
+      "completionKey": "TAB"
+    }
+  }
+]
diff --git a/mongodb/src/main/resources/shell_extension.js 
b/mongodb/src/main/resources/shell_extension.js
new file mode 100644
index 0000000..b50f500
--- /dev/null
+++ b/mongodb/src/main/resources/shell_extension.js
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+var tableLimit = TABLE_LIMIT_PLACEHOLDER;
+
+function flattenObject(obj, flattenArray) {
+    var toReturn = {};
+    
+    for (var i in obj) {
+        if (!obj.hasOwnProperty(i)) continue;
+        
+        //if ((typeof obj[i]) == 'object') {
+        if (toString.call( obj[i] ) === '[object Object]' ||
+            toString.call( obj[i] ) === '[object BSON]' ||
+          (flattenArray && toString.call( obj[i] ) === '[object Array]')) {
+            var flatObject = flattenObject(obj[i]);
+            for (var x in flatObject) {
+                if (!flatObject.hasOwnProperty(x)) continue;
+                
+                toReturn[i + '.' + x] = flatObject[x];
+            }
+        } else if (toString.call( obj[i] ) === '[object Array]') {
+            toReturn[i] = tojson(obj[i], null, true);
+        } else {
+            toReturn[i] = obj[i];
+        }
+    }
+    return toReturn;
+}
+
+function printTable(dbquery, fields, flattenArray) {
+    
+    var iterator = dbquery;
+    
+    if (toString.call( dbquery ) === '[object Array]') {
+        iterator = (function() {
+            var index = 0,
+                data = dbquery,
+                length = data.length;
+
+            return {
+                next: function() {
+                    if (!this.hasNext()) {
+                        return null;
+                    }
+                    return data[index++];
+                },
+                hasNext: function() {
+                    return index < length;
+                }
+            }
+        }());
+    }
+
+    // Flatten all the documents and get all the fields to build a table with 
all fields
+    var docs = [];
+    var createFieldSet = fields == null || fields.length == 0;
+    var fieldSet = fields ? [].concat(fields) : []; //new Set(fields);
+    
+    while (iterator.hasNext()) {
+        var doc = iterator.next();
+        doc = flattenObject(doc, flattenArray);
+        docs.push(doc);
+        if (createFieldSet) {
+            for (var i in doc) {
+                if (doc.hasOwnProperty(i) && fieldSet.indexOf(i) === -1) {
+                    fieldSet.push(i);
+                }
+            }
+        }
+    }
+    
+    fields = fieldSet;
+
+    var header = "%table ";
+    fields.forEach(function (field) { header += field + "\t" })
+    print(header.substring(0, header.length - 1));
+    
+    docs.forEach(function (doc) {
+        var row = "";
+        fields.forEach(function (field) { row += doc[field] + "\t" })
+        print(row.substring(0, row.length - 1));
+    });
+}
+
+DBQuery.prototype.table = function (fields, flattenArray) {
+    if (this._limit > tableLimit) {
+        this.limit(tableLimit);
+    }
+    printTable(this, fields, flattenArray);
+};
+
+DBCommandCursor.prototype.table = DBQuery.prototype.table;
+
+var userName = "USER_NAME_PLACEHOLDER";
+var password = "PASSWORD_PLACEHOLDER";
+var authDB = "AUTH_DB_PLACEHOLDER";
+var targetDB = "TARGET_DB_PLACEHOLDER";
+
+if (userName){
+    authDB = authDB || "admin";
+    db = db.getSiblingDB(authDB);
+    db.auth(userName,password);
+}
+
+db = db.getSiblingDB(targetDB);
+
diff --git 
a/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java 
b/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java
new file mode 100644
index 0000000..7a39f7a
--- /dev/null
+++ 
b/mongodb/src/test/java/org/apache/zeppelin/mongodb/MongoDbInterpreterTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.mongodb;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Properties;
+import java.util.Scanner;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterOutput;
+import org.apache.zeppelin.interpreter.InterpreterOutputListener;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.interpreter.InterpreterResultMessageOutput;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertEquals;
+/**
+ * As there is no 'mongo' on the build platform, these tests simulates some 
basic behavior.
+ *
+ */
+public class MongoDbInterpreterTest implements InterpreterOutputListener {
+  
+  private static final String SHELL_EXTENSION =
+      new 
Scanner(MongoDbInterpreter.class.getResourceAsStream("/shell_extension.js"), 
"UTF-8")
+      .useDelimiter("\\A").next();
+  
+  private static final boolean IS_WINDOWS = System.getProperty("os.name")
+      .startsWith("Windows");
+  
+  private static final String MONGO_SHELL = 
System.getProperty("java.io.tmpdir") +
+          (System.getProperty("java.io.tmpdir").endsWith(File.separator) ? 
StringUtils.EMPTY : File.separator)
+          + "zeppelin-mongo-scripts"+ File.separator +"mongo-test." + 
(IS_WINDOWS ? "bat" : "sh");
+    
+  private final Properties props = new Properties();
+  private final MongoDbInterpreter interpreter = new MongoDbInterpreter(props);
+  private final InterpreterOutput out = new InterpreterOutput(this);
+
+  private final InterpreterContext context = 
InterpreterContext.builder().setNoteId("test")
+          
.setInterpreterOut(out).setNoteId("test").setParagraphId("test").build();
+
+  private ByteBuffer buffer;
+  
+  @BeforeClass
+  public static void setup() {
+    // Create a fake 'mongo'
+    final File mongoFile = new File(MONGO_SHELL);
+    try {
+      FileUtils.write(mongoFile, (IS_WINDOWS ? "@echo off\ntype \"%3%\"" : 
"cat \"$3\""));
+      FileUtils.forceDeleteOnExit(mongoFile);
+    }
+    catch (IOException ex) {
+      System.out.println(ex.getMessage());
+    }
+  }
+  
+  @Before
+  public void init() {
+    buffer = ByteBuffer.allocate(10000);
+    props.put("mongo.shell.path", (IS_WINDOWS ? "" : "sh ") + MONGO_SHELL);
+    props.put("mongo.shell.command.table.limit", "10000");
+    props.put("mongo.server.database", "test");
+    props.put("mongo.server.username", "");
+    props.put("mongo.server.password", "");
+    props.put("mongo.server.authenticationDatabase", "");
+    props.put("mongo.shell.command.timeout", "10000");
+    props.put("mongo.interpreter.concurrency.max", "10");
+    props.put("mongo.server.host", "localhost");
+    props.put("mongo.server.port", "27017");
+
+    interpreter.open();
+  }
+
+  @After
+  public void destroy(){
+    interpreter.close();
+  }
+
+  @Test
+  public void testSuccess() {
+    final String userScript = "print('hello');";
+    
+    final InterpreterResult res = interpreter.interpret(userScript, context);
+
+    assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code());
+    
+    try {
+      out.flush();
+    } catch (IOException ex) {
+      System.out.println(ex.getMessage());
+    }
+
+    final String resultScript = new String(getBufferBytes());
+    
+    final String expectedScript = SHELL_EXTENSION.replace(
+        "TABLE_LIMIT_PLACEHOLDER", 
interpreter.getProperty("mongo.shell.command.table.limit"))
+            .replace("TARGET_DB_PLACEHOLDER", 
interpreter.getProperty("mongo.server.database"))
+            .replace("USER_NAME_PLACEHOLDER", 
interpreter.getProperty("mongo.server.username"))
+            .replace("PASSWORD_PLACEHOLDER", 
interpreter.getProperty("mongo.server.password"))
+            .replace("AUTH_DB_PLACEHOLDER", interpreter. 
getProperty("mongo.server.authenticationDatabase"))+
+        userScript;
+    
+    // The script that is executed must contain the functions provided by this 
interpreter
+    assertEquals("Check SCRIPT", expectedScript, resultScript);
+  }
+  
+  @Test
+  public void testBadConf() {
+    props.setProperty("mongo.shell.path", "/bad/path/to/mongo");
+    final InterpreterResult res = interpreter.interpret("print('hello')", 
context);
+
+    assertSame(Code.ERROR, res.code());
+  }
+
+  @Override
+  public void onUpdateAll(InterpreterOutput interpreterOutput) {
+
+  }
+
+  @Override
+  public void onAppend(int i, InterpreterResultMessageOutput 
interpreterResultMessageOutput,
+                       byte[] bytes) {
+    buffer.put(bytes);
+  }
+
+  @Override
+  public void onUpdate(int i, InterpreterResultMessageOutput 
interpreterResultMessageOutput) {
+  }
+
+  private byte[] getBufferBytes() {
+    buffer.flip();
+    final byte[] bufferBytes = new byte[buffer.remaining()];
+    buffer.get(bufferBytes);
+    return bufferBytes;
+  }
+}
diff --git a/pom.xml b/pom.xml
index 58ecd09..de25b8b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,7 @@
     <module>zeppelin-jupyter</module>
     <module>zeppelin-plugins</module>
     <module>zeppelin-distribution</module>
+    <module>mongodb</module>
   </modules>
 
   <properties>
diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json
index dac35d4..90c212e 100644
--- a/zeppelin-web/bower.json
+++ b/zeppelin-web/bower.json
@@ -57,7 +57,8 @@
         "src-noconflict/mode-sparql.js",
         "src-noconflict/keybinding-emacs.js",
         "src-noconflict/ext-language_tools.js",
-        "src-noconflict/theme-chrome.js"
+        "src-noconflict/theme-chrome.js",
+        "src-noconflict/mode-javascript.js"
       ],
       "version": "1.3.2",
       "name": "ace-builds"

Reply via email to