You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by bz...@apache.org on 2015/12/22 11:37:23 UTC
incubator-zeppelin git commit: Add an Elasticsearch interpreter
Repository: incubator-zeppelin
Updated Branches:
refs/heads/master 4c269e6d8 -> 7e9028329
Add an Elasticsearch interpreter
### Elasticsearch Interpreter
Interpreter for querying ElasticSearch .
Supported requests are "get document by id" , "search documents" , "delete by id" , "count documents" and "index / update a document".
Supported versions of Elasticsearch : >= 2.1
Author: Bruno Bonnin <bb...@gmail.com>
Author: Bruno Bonnin <br...@myscript.com>
Closes #520 from bbonnin/master and squashes the following commits:
e40c06d [Bruno Bonnin] Remove duplicate dependency license (same groupid/artifactid)
98822df [Bruno Bonnin] Merge branch 'master' of https://github.com/apache/incubator-zeppelin
32ea103 [Bruno Bonnin] Update elasticsearch.md
e8a9ff2 [Bruno Bonnin] Merge branch 'master' of https://github.com/bbonnin/incubator-zeppelin
34a39a9 [Bruno Bonnin] Update tests with new search format
a3cf78c [Bruno Bonnin] Update elasticsearch.md
af319e6 [Bruno Bonnin] Update snapshots
ce7c15f [Bruno Bonnin] Update search command (use of query_string) + size config
6b6886b [Bruno Bonnin] Update doc with query DSL
2dfd129 [Bruno Bonnin] Check client before starting process (to get a better error msg)
7bf4232 [Bruno Bonnin] Doc : count command with a query
93253df [Bruno Bonnin] Count command with a query
d3b599c [Bruno Bonnin] Fix typo
25383db [Bruno Bonnin] Update doc (config, shield, completion)
3e1655d [Bruno Bonnin] Update elasticsearch.md
ee1547a [Bruno Bonnin] Ugly table for flattened data
46899d6 [Bruno Bonnin] Doc: flattened json and security
4044169 [Bruno Bonnin] Add completion
31c73b5 [Bruno Bonnin] Update tests
c68b3df [Bruno Bonnin] Update of LICENSE
455a072 [Bruno Bonnin] Fix pb from remarks of the PR
a10a5ec [Bruno Bonnin] Elasticsearch Interpreter
fdc413f [Bruno Bonnin] Elasticsearch Interpreter
Project: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/commit/7e902832
Tree: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/tree/7e902832
Diff: http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/diff/7e902832
Branch: refs/heads/master
Commit: 7e9028329e61ce3401ba39643bd1ca7ac3021c89
Parents: 4c269e6
Author: Bruno Bonnin <bb...@gmail.com>
Authored: Mon Dec 21 10:19:01 2015 +0100
Committer: Alexander Bezzubov <bz...@apache.org>
Committed: Tue Dec 22 19:36:55 2015 +0900
----------------------------------------------------------------------
conf/zeppelin-site.xml.template | 2 +-
.../img/docs-img/elasticsearch-config.png | Bin 0 -> 68876 bytes
.../docs-img/elasticsearch-count-with-query.png | Bin 0 -> 55824 bytes
.../img/docs-img/elasticsearch-count.png | Bin 0 -> 59770 bytes
.../zeppelin/img/docs-img/elasticsearch-get.png | Bin 0 -> 76122 bytes
.../img/docs-img/elasticsearch-query-string.png | Bin 0 -> 116111 bytes
.../elasticsearch-search-json-query-table.png | Bin 0 -> 57479 bytes
.../img/docs-img/elasticsearch-search-pie.png | Bin 0 -> 48215 bytes
.../img/docs-img/elasticsearch-search-table.png | Bin 0 -> 263452 bytes
docs/interpreter/elasticsearch.md | 228 +++++++++
elasticsearch/pom.xml | 147 ++++++
.../elasticsearch/ElasticsearchInterpreter.java | 465 +++++++++++++++++++
.../ElasticsearchInterpreterTest.java | 171 +++++++
pom.xml | 1 +
zeppelin-distribution/src/bin_license/LICENSE | 33 ++
.../zeppelin/conf/ZeppelinConfiguration.java | 3 +-
16 files changed, 1048 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/conf/zeppelin-site.xml.template
----------------------------------------------------------------------
diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index 78d7f1e..b6aca75 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -105,7 +105,7 @@
<property>
<name>zeppelin.interpreters</name>
- <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.phoenix.PhoenixInterpreter,org.apache.zeppelin.kylin.KylinInterpreter</value>
+ <value>org.apache.zeppelin.spark.SparkInterpreter,org.apache.zeppelin.spark.PySparkInterpreter,org.apache.zeppelin.spark.SparkSqlInterpreter,org.apache.zeppelin.spark.DepInterpreter,org.apache.zeppelin.markdown.Markdown,org.apache.zeppelin.angular.AngularInterpreter,org.apache.zeppelin.shell.ShellInterpreter,org.apache.zeppelin.hive.HiveInterpreter,org.apache.zeppelin.tajo.TajoInterpreter,org.apache.zeppelin.flink.FlinkInterpreter,org.apache.zeppelin.lens.LensInterpreter,org.apache.zeppelin.ignite.IgniteInterpreter,org.apache.zeppelin.ignite.IgniteSqlInterpreter,org.apache.zeppelin.cassandra.CassandraInterpreter,org.apache.zeppelin.geode.GeodeOqlInterpreter,org.apache.zeppelin.postgresql.PostgreSqlInterpreter,org.apache.zeppelin.phoenix.PhoenixInterpreter,org.apache.zeppelin.kylin.KylinInterpreter,org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter</value>
<description>Comma separated interpreter configurations. First interpreter become a default</description>
</property>
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-config.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-config.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-config.png
new file mode 100644
index 0000000..ce68836
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-config.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count-with-query.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count-with-query.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count-with-query.png
new file mode 100755
index 0000000..ca2f940
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count-with-query.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count.png
new file mode 100644
index 0000000..7514e63
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-count.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-get.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-get.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-get.png
new file mode 100644
index 0000000..a3b4107
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-get.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-query-string.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-query-string.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-query-string.png
new file mode 100755
index 0000000..c351e8e
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-query-string.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-json-query-table.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-json-query-table.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-json-query-table.png
new file mode 100755
index 0000000..bcec2e5
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-json-query-table.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-pie.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-pie.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-pie.png
new file mode 100644
index 0000000..81b711b
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-pie.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-table.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-table.png b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-table.png
new file mode 100644
index 0000000..fba9966
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/elasticsearch-search-table.png differ
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/docs/interpreter/elasticsearch.md
----------------------------------------------------------------------
diff --git a/docs/interpreter/elasticsearch.md b/docs/interpreter/elasticsearch.md
new file mode 100644
index 0000000..fb83799
--- /dev/null
+++ b/docs/interpreter/elasticsearch.md
@@ -0,0 +1,228 @@
+---
+layout: page
+title: "Elasticsearch Interpreter"
+description: ""
+group: manual
+---
+{% include JB/setup %}
+
+
+## Elasticsearch Interpreter for Apache Zeppelin
+
+### 1. Configuration
+
+<br/>
+<table class="table-configuration">
+ <tr>
+ <th>Property</th>
+ <th>Default</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>elasticsearch.cluster.name</td>
+ <td>elasticsearch</td>
+ <td>Cluster name</td>
+ </tr>
+ <tr>
+ <td>elasticsearch.host</td>
+ <td>localhost</td>
+ <td>Host of a node in the cluster</td>
+ </tr>
+ <tr>
+ <td>elasticsearch.port</td>
+ <td>9300</td>
+ <td>Connection port <b>(important: this is not the HTTP port, but the transport port)</b></td>
+ </tr>
+ <tr>
+ <td>elasticsearch.result.size</td>
+ <td>10</td>
+ <td>The size of the result set of a search query</td>
+ </tr>
+</table>
+
+<center>
+ ![Interpreter configuration](../assets/themes/zeppelin/img/docs-img/elasticsearch-config.png)
+</center>
+
+
+> Note #1: you can add more properties to configure the Elasticsearch client.
+
+> Note #2: if you use Shield, you can add a property named `shield.user` with a value containing the name and the password (format: `username:password`). For more details about Shield configuration, consult the [Shield reference guide](https://www.elastic.co/guide/en/shield/current/_using_elasticsearch_java_clients_with_shield.html). Do not forget, to copy the shield client jar in the interpreter directory (`ZEPPELIN_HOME/interpreters/elasticsearch`).
+
+
+<hr/>
+
+### 2. Enabling the Elasticsearch Interpreter
+
+In a notebook, to enable the **Elasticsearch** interpreter, click the **Gear** icon and select **Elasticsearch**.
+
+
+<hr/>
+
+
+### 3. Using the Elasticsearch Interpreter
+
+In a paragraph, use `%elasticsearch` to select the Elasticsearch interpreter and then input all commands. To get the list of available commands, use `help`.
+
+```bash
+| %elasticsearch
+| help
+Elasticsearch interpreter:
+General format: <command> /<indices>/<types>/<id> <option> <JSON>
+ - indices: list of indices separated by commas (depends on the command)
+ - types: list of document types separated by commas (depends on the command)
+Commands:
+ - search /indices/types <query>
+ . indices and types can be omitted (at least, you have to provide '/')
+ . a query is either a JSON-formatted query, nor a lucene query
+ - size <value>
+ . defines the size of the result set (default value is in the config)
+ . if used, this command must be declared before a search command
+ - count /indices/types <query>
+ . same comments as for the search
+ - get /index/type/id
+ - delete /index/type/id
+ - index /ndex/type/id <json-formatted document>
+ . the id can be omitted, elasticsearch will generate one
+```
+
+> Tip: use (CTRL + .) for completion
+
+
+#### get
+With the `get` command, you can find a document by id. The result is a JSON document.
+
+```bash
+| %elasticsearch
+| get /index/type/id
+```
+
+Example:
+![Elasticsearch - Get](../assets/themes/zeppelin/img/docs-img/elasticsearch-get.png)
+
+
+#### search
+With the `search` command, you can send a search query to Elasticsearch. There are two formats of query:
+* You can provide a JSON-formatted query, that is exactly what you provide when you use the REST API of Elasticsearch.
+ * See [Elasticsearch search API reference document](https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html) for more details about the content of the search queries.
+* You can also provide the content of a `query_string`
+ * This is a shortcut to a query like that: `{ "query": { "query_string": { "query": "__HERE YOUR QUERY__", "analyze_wildcard": true } } }`
+ * See [Elasticsearch query string syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax) for more details about the content of such a query.
+
+```bash
+| %elasticsearch
+| search /index1,index2,.../type1,type2,... <JSON document containing the query or query_string elements>
+```
+
+If you want to modify the size of the result set, you can add a line that is setting the size, before your search command.
+
+```bash
+| %elasticsearch
+| size 50
+| search /index1,index2,.../type1,type2,... <JSON document containing the query or query_string elements>
+```
+
+
+Examples:
+* With a JSON query:
+```bash
+| %elasticsearch
+| search / { "query": { "match_all": {} } }
+
+| %elasticsearch
+| search /logs { "query": { "query_string": { "query": "request.method:GET AND status:200" } } }
+```
+
+* With query_string elements:
+```bash
+| %elasticsearch
+| search /logs request.method:GET AND status:200
+
+| %elasticsearch
+| search /logs (404 AND (POST OR DELETE))
+```
+
+> **Important**: a document in Elasticsearch is a JSON document, so it is hierarchical, not flat as a row in a SQL table.
+For the Elastic interpreter, the result of a search query is flattened.
+
+Suppose we have a JSON document:
+```json
+{
+ "date": "2015-12-08T21:03:13.588Z",
+ "request": {
+ "method": "GET",
+ "url": "/zeppelin/4cd001cd-c517-4fa9-b8e5-a06b8f4056c4",
+ "headers": [ "Accept: *.*", "Host: apache.org"]
+ },
+ "status": "403"
+}
+```
+
+The data will be flattened like this:
+
+date | request.headers[0] | request.headers[1] | request.method | request.url | status
+-----|--------------------|--------------------|----------------|-------------|-------
+2015-12-08T21:03:13.588Z | Accept: \*.\* | Host: apache.org | GET | /zeppelin/4cd001cd-c517-4fa9-b8e5-a06b8f4056c4 | 403
+
+
+Examples:
+* With a table containing the results:
+![Elasticsearch - Search - table](../assets/themes/zeppelin/img/docs-img/elasticsearch-search-table.png)
+
+
+* You can also use a predefined diagram:
+![Elasticsearch - Search - diagram](../assets/themes/zeppelin/img/docs-img/elasticsearch-search-pie.png)
+
+* With a JSON query:
+![Elasticsearch - Search with query](../assets/themes/zeppelin/img/docs-img/elasticsearch-search-json-query-table.png)
+
+* With a query string:
+![Elasticsearch - Search with query string](../assets/themes/zeppelin/img/docs-img/elasticsearch-query-string.png)
+
+
+#### count
+With the `count` command, you can count documents available in some indices and types. You can also provide a query.
+
+```bash
+| %elasticsearch
+| count /index1,index2,.../type1,type2,... <JSON document containing the query OR a query string>
+```
+
+Examples:
+* Without query:
+![Elasticsearch - Count](../assets/themes/zeppelin/img/docs-img/elasticsearch-count.png)
+
+* With a query:
+![Elasticsearch - Count with query](../assets/themes/zeppelin/img/docs-img/elasticsearch-count-with-query.png)
+
+
+#### index
+With the `index` command, you can insert/update a document in Elasticsearch.
+```bash
+| %elasticsearch
+| index /index/type/id <JSON document>
+
+| %elasticsearch
+| index /index/type <JSON document>
+```
+
+#### delete
+With the `delete` command, you can delete a document.
+
+```bash
+| %elasticsearch
+| delete /index/type/id
+```
+
+
+
+#### Apply Zeppelin Dynamic Forms
+
+You can leverage [Zeppelin Dynamic Form]({{BASE_PATH}}/manual/dynamicform.html) inside your queries. You can use both the `text input` and `select form` parameterization features
+
+```bash
+%elasticsearch
+size ${limit=10}
+search /index/type { "query": { "match_all": {} } }
+```
+
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/elasticsearch/pom.xml
----------------------------------------------------------------------
diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml
new file mode 100644
index 0000000..3da4441
--- /dev/null
+++ b/elasticsearch/pom.xml
@@ -0,0 +1,147 @@
+<?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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>zeppelin</artifactId>
+ <groupId>org.apache.zeppelin</groupId>
+ <version>0.6.0-incubating-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <groupId>org.apache.zeppelin</groupId>
+ <artifactId>zeppelin-elasticsearch</artifactId>
+ <packaging>jar</packaging>
+ <version>0.6.0-incubating-SNAPSHOT</version>
+ <name>Zeppelin: Elasticsearch interpreter</name>
+ <url>http://www.apache.org</url>
+
+ <properties>
+ <elasticsearch.version>2.1.0</elasticsearch.version>
+ <guava.version>18.0</guava.version>
+ <json-flattener.version>0.1.1</json-flattener.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.zeppelin</groupId>
+ <artifactId>zeppelin-interpreter</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.elasticsearch</groupId>
+ <artifactId>elasticsearch</artifactId>
+ <version>${elasticsearch.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>${guava.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.wnameless</groupId>
+ <artifactId>json-flattener</artifactId>
+ <version>${json-flattener.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>2.7</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <version>1.3.1</version>
+ <executions>
+ <execution>
+ <id>enforce</id>
+ <phase>none</phase>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.8</version>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/../../interpreter/elasticsearch</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>false</overWriteSnapshots>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ <includeScope>runtime</includeScope>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-artifact</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/../../interpreter/elasticsearch</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>false</overWriteSnapshots>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ <includeScope>runtime</includeScope>
+ <artifactItems>
+ <artifactItem>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>${project.artifactId}</artifactId>
+ <version>${project.version}</version>
+ <type>${project.packaging}</type>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/elasticsearch/src/main/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreter.java
----------------------------------------------------------------------
diff --git a/elasticsearch/src/main/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreter.java b/elasticsearch/src/main/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreter.java
new file mode 100644
index 0000000..dba5b73
--- /dev/null
+++ b/elasticsearch/src/main/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreter.java
@@ -0,0 +1,465 @@
+/*
+ * 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.elasticsearch;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.lang.StringUtils;
+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.elasticsearch.action.delete.DeleteResponse;
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.action.search.SearchAction;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.transport.InetSocketTransportAddress;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.wnameless.json.flattener.JsonFlattener;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+
+
+/**
+ * Elasticsearch Interpreter for Zeppelin.
+ */
+public class ElasticsearchInterpreter extends Interpreter {
+
+ private static Logger logger = LoggerFactory.getLogger(ElasticsearchInterpreter.class);
+
+ private static final String HELP = "Elasticsearch interpreter:\n"
+ + "General format: <command> /<indices>/<types>/<id> <option> <JSON>\n"
+ + " - indices: list of indices separated by commas (depends on the command)\n"
+ + " - types: list of document types separated by commas (depends on the command)\n"
+ + "Commands:\n"
+ + " - search /indices/types <query>\n"
+ + " . indices and types can be omitted (at least, you have to provide '/')\n"
+ + " . a query is either a JSON-formatted query, nor a lucene query\n"
+ + " - size <value>\n"
+ + " . defines the size of the result set (default value is in the config)\n"
+ + " . if used, this command must be declared before a search command\n"
+ + " - count /indices/types <query>\n"
+ + " . same comments as for the search\n"
+ + " - get /index/type/id\n"
+ + " - delete /index/type/id\n"
+ + " - index /ndex/type/id <json-formatted document>\n"
+ + " . the id can be omitted, elasticsearch will generate one";
+
+ private static final List<String> COMMANDS = Arrays.asList(
+ "count", "delete", "get", "help", "index", "search");
+
+
+ public static final String ELASTICSEARCH_HOST = "elasticsearch.host";
+ public static final String ELASTICSEARCH_PORT = "elasticsearch.port";
+ public static final String ELASTICSEARCH_CLUSTER_NAME = "elasticsearch.cluster.name";
+ public static final String ELASTICSEARCH_RESULT_SIZE = "elasticsearch.result.size";
+
+ static {
+ Interpreter.register(
+ "elasticsearch",
+ "elasticsearch",
+ ElasticsearchInterpreter.class.getName(),
+ new InterpreterPropertyBuilder()
+ .add(ELASTICSEARCH_HOST, "localhost", "The host for Elasticsearch")
+ .add(ELASTICSEARCH_PORT, "9300", "The port for Elasticsearch")
+ .add(ELASTICSEARCH_CLUSTER_NAME, "elasticsearch", "The cluster name for Elasticsearch")
+ .add(ELASTICSEARCH_RESULT_SIZE, "10", "The size of the result set of a search query")
+ .build());
+ }
+
+ private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ private Client client;
+ private String host = "localhost";
+ private int port = 9300;
+ private String clusterName = "elasticsearch";
+ private int resultSize = 10;
+
+ public ElasticsearchInterpreter(Properties property) {
+ super(property);
+ this.host = getProperty(ELASTICSEARCH_HOST);
+ this.port = Integer.parseInt(getProperty(ELASTICSEARCH_PORT));
+ this.clusterName = getProperty(ELASTICSEARCH_CLUSTER_NAME);
+ this.resultSize = Integer.parseInt(getProperty(ELASTICSEARCH_RESULT_SIZE));
+ }
+
+ @Override
+ public void open() {
+ try {
+ logger.info("prop={}", getProperty());
+ final Settings settings = Settings.settingsBuilder()
+ .put("cluster.name", clusterName)
+ .put(getProperty())
+ .build();
+ client = TransportClient.builder().settings(settings).build()
+ .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port));
+ }
+ catch (IOException e) {
+ logger.error("Open connection with Elasticsearch", e);
+ }
+ }
+
+ @Override
+ public void close() {
+ if (client != null) {
+ client.close();
+ }
+ }
+
+ @Override
+ public InterpreterResult interpret(String cmd, InterpreterContext interpreterContext) {
+ logger.info("Run Elasticsearch command '" + cmd + "'");
+
+ int currentResultSize = resultSize;
+
+ if (client == null) {
+ return new InterpreterResult(InterpreterResult.Code.ERROR,
+ "Problem with the Elasticsearch client, please check your configuration (host, port,...)");
+ }
+
+ String[] items = StringUtils.split(cmd.trim(), " ", 3);
+
+ // Process some specific commands (help, size, ...)
+ if ("help".equalsIgnoreCase(items[0])) {
+ return processHelp(InterpreterResult.Code.SUCCESS, null);
+ }
+
+ if ("size".equalsIgnoreCase(items[0])) {
+ // In this case, the line with size must be followed by a search,
+ // so we will continue with the next lines
+ final String[] lines = StringUtils.split(cmd.trim(), "\n", 2);
+
+ if (lines.length < 2) {
+ return processHelp(InterpreterResult.Code.ERROR,
+ "Size cmd must be followed by a search");
+ }
+
+ final String[] sizeLine = StringUtils.split(lines[0], " ", 2);
+ if (sizeLine.length != 2) {
+ return processHelp(InterpreterResult.Code.ERROR, "Right format is : size <value>");
+ }
+ currentResultSize = Integer.parseInt(sizeLine[1]);
+
+ items = StringUtils.split(lines[1].trim(), " ", 3);
+ }
+
+ if (items.length < 2) {
+ return processHelp(InterpreterResult.Code.ERROR, "Arguments missing");
+ }
+
+ final String method = items[0];
+ final String url = items[1];
+ final String data = items.length > 2 ? items[2].trim() : null;
+
+ final String[] urlItems = StringUtils.split(url.trim(), "/");
+
+ try {
+ if ("get".equalsIgnoreCase(method)) {
+ return processGet(urlItems);
+ }
+ else if ("count".equalsIgnoreCase(method)) {
+ return processCount(urlItems, data);
+ }
+ else if ("search".equalsIgnoreCase(method)) {
+ return processSearch(urlItems, data, currentResultSize);
+ }
+ else if ("index".equalsIgnoreCase(method)) {
+ return processIndex(urlItems, data);
+ }
+ else if ("delete".equalsIgnoreCase(method)) {
+ return processDelete(urlItems);
+ }
+
+ return processHelp(InterpreterResult.Code.ERROR, "Unknown command");
+ }
+ catch (Exception e) {
+ return new InterpreterResult(InterpreterResult.Code.ERROR, "Error : " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void cancel(InterpreterContext interpreterContext) {
+ // Nothing to do
+ }
+
+ @Override
+ public FormType getFormType() {
+ return FormType.SIMPLE;
+ }
+
+ @Override
+ public int getProgress(InterpreterContext interpreterContext) {
+ return 0;
+ }
+
+ @Override
+ public List<String> completion(String s, int i) {
+ final List<String> suggestions = new ArrayList<>();
+
+ if (StringUtils.isEmpty(s)) {
+ suggestions.addAll(COMMANDS);
+ }
+ else {
+ for (String cmd : COMMANDS) {
+ if (cmd.toLowerCase().contains(s)) {
+ suggestions.add(cmd);
+ }
+ }
+ }
+
+ return suggestions;
+ }
+
+ private InterpreterResult processHelp(InterpreterResult.Code code, String additionalMessage) {
+ final StringBuffer buffer = new StringBuffer();
+
+ if (additionalMessage != null) {
+ buffer.append(additionalMessage).append("\n");
+ }
+
+ buffer.append(HELP).append("\n");
+
+ return new InterpreterResult(code, InterpreterResult.Type.TEXT, buffer.toString());
+ }
+
+ /**
+ * Processes a "get" request.
+ *
+ * @param urlItems Items of the URL
+ * @return Result of the get request, it contains a JSON-formatted string
+ */
+ private InterpreterResult processGet(String[] urlItems) {
+
+ if (urlItems.length != 3
+ || StringUtils.isEmpty(urlItems[0])
+ || StringUtils.isEmpty(urlItems[1])
+ || StringUtils.isEmpty(urlItems[2])) {
+ return new InterpreterResult(InterpreterResult.Code.ERROR,
+ "Bad URL (it should be /index/type/id)");
+ }
+
+ final GetResponse response = client
+ .prepareGet(urlItems[0], urlItems[1], urlItems[2])
+ .get();
+ if (response.isExists()) {
+ final String json = gson.toJson(response.getSource());
+
+ return new InterpreterResult(
+ InterpreterResult.Code.SUCCESS,
+ InterpreterResult.Type.TEXT,
+ json);
+ }
+
+ return new InterpreterResult(InterpreterResult.Code.ERROR, "Document not found");
+ }
+
+ /**
+ * Processes a "count" request.
+ *
+ * @param urlItems Items of the URL
+ * @param data May contains the JSON of the request
+ * @return Result of the count request, it contains the total hits
+ */
+ private InterpreterResult processCount(String[] urlItems, String data) {
+
+ if (urlItems.length > 2) {
+ return new InterpreterResult(InterpreterResult.Code.ERROR,
+ "Bad URL (it should be /index1,index2,.../type1,type2,...)");
+ }
+
+ final SearchResponse response = searchData(urlItems, data, 0);
+
+ return new InterpreterResult(
+ InterpreterResult.Code.SUCCESS,
+ InterpreterResult.Type.TEXT,
+ "" + response.getHits().getTotalHits());
+ }
+
+ /**
+ * Processes a "search" request.
+ *
+ * @param urlItems Items of the URL
+ * @param data May contains the limit and the JSON of the request
+ * @return Result of the search request, it contains a tab-formatted string of the matching hits
+ */
+ private InterpreterResult processSearch(String[] urlItems, String data, int size) {
+
+ if (urlItems.length > 2) {
+ return new InterpreterResult(InterpreterResult.Code.ERROR,
+ "Bad URL (it should be /index1,index2,.../type1,type2,...)");
+ }
+
+ final SearchResponse response = searchData(urlItems, data, size);
+
+ return new InterpreterResult(
+ InterpreterResult.Code.SUCCESS,
+ InterpreterResult.Type.TABLE,
+ buildResponseMessage(response.getHits().getHits()));
+ }
+
+ /**
+ * Processes a "index" request.
+ *
+ * @param urlItems Items of the URL
+ * @param data JSON to be indexed
+ * @return Result of the index request, it contains the id of the document
+ */
+ private InterpreterResult processIndex(String[] urlItems, String data) {
+
+ if (urlItems.length < 2 || urlItems.length > 3) {
+ return new InterpreterResult(InterpreterResult.Code.ERROR,
+ "Bad URL (it should be /index/type or /index/type/id)");
+ }
+
+ final IndexResponse response = client
+ .prepareIndex(urlItems[0], urlItems[1], urlItems.length == 2 ? null : urlItems[2])
+ .setSource(data)
+ .get();
+
+ return new InterpreterResult(
+ InterpreterResult.Code.SUCCESS,
+ InterpreterResult.Type.TEXT,
+ response.getId());
+ }
+
+ /**
+ * Processes a "delete" request.
+ *
+ * @param urlItems Items of the URL
+ * @return Result of the delete request, it contains the id of the deleted document
+ */
+ private InterpreterResult processDelete(String[] urlItems) {
+
+ if (urlItems.length != 3
+ || StringUtils.isEmpty(urlItems[0])
+ || StringUtils.isEmpty(urlItems[1])
+ || StringUtils.isEmpty(urlItems[2])) {
+ return new InterpreterResult(InterpreterResult.Code.ERROR,
+ "Bad URL (it should be /index/type/id)");
+ }
+
+ final DeleteResponse response = client
+ .prepareDelete(urlItems[0], urlItems[1], urlItems[2])
+ .get();
+
+ if (response.isFound()) {
+ return new InterpreterResult(
+ InterpreterResult.Code.SUCCESS,
+ InterpreterResult.Type.TEXT,
+ response.getId());
+ }
+
+ return new InterpreterResult(InterpreterResult.Code.ERROR, "Document not found");
+ }
+
+ private SearchResponse searchData(String[] urlItems, String query, int size) {
+
+ final SearchRequestBuilder reqBuilder = new SearchRequestBuilder(
+ client, SearchAction.INSTANCE);
+ reqBuilder.setIndices();
+
+ if (urlItems.length >= 1) {
+ reqBuilder.setIndices(StringUtils.split(urlItems[0], ","));
+ }
+ if (urlItems.length > 1) {
+ reqBuilder.setTypes(StringUtils.split(urlItems[1], ","));
+ }
+
+ if (!StringUtils.isEmpty(query)) {
+ // The query can be either JSON-formatted, nor a Lucene query
+ // So, try to parse as a JSON => if there is an error, consider the query a Lucene one
+ try {
+ final Map source = gson.fromJson(query, Map.class);
+ reqBuilder.setExtraSource(source);
+ }
+ catch (JsonParseException e) {
+ // This is not a JSON (or maybe not well formatted...)
+ reqBuilder.setQuery(QueryBuilders.queryStringQuery(query).analyzeWildcard(true));
+ }
+ }
+
+ reqBuilder.setSize(size);
+
+ final SearchResponse response = reqBuilder.get();
+
+ return response;
+ }
+
+ private String buildResponseMessage(SearchHit[] hits) {
+
+ if (hits == null || hits.length == 0) {
+ return "";
+ }
+
+ //First : get all the keys in order to build an ordered list of the values for each hit
+ //
+ final List<Map<String, Object>> flattenHits = new LinkedList<>();
+ final Set<String> keys = new TreeSet<>();
+ for (SearchHit hit : hits) {
+ final String json = hit.getSourceAsString();
+ final Map<String, Object> flattenMap = JsonFlattener.flattenAsMap(json);
+ flattenHits.add(flattenMap);
+
+ for (String key : flattenMap.keySet()) {
+ keys.add(key);
+ }
+ }
+
+ // Next : build the header of the table
+ //
+ final StringBuffer buffer = new StringBuffer();
+ for (String key : keys) {
+ buffer.append(key).append('\t');
+ }
+ buffer.replace(buffer.lastIndexOf("\t"), buffer.lastIndexOf("\t") + 1, "\n");
+
+ // Finally : build the result by using the key set
+ //
+ for (Map<String, Object> hit : flattenHits) {
+ for (String key : keys) {
+ final Object val = hit.get(key);
+ if (val != null) {
+ buffer.append(val);
+ }
+ buffer.append('\t');
+ }
+ buffer.replace(buffer.lastIndexOf("\t"), buffer.lastIndexOf("\t") + 1, "\n");
+ }
+
+ return buffer.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/elasticsearch/src/test/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreterTest.java
----------------------------------------------------------------------
diff --git a/elasticsearch/src/test/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreterTest.java b/elasticsearch/src/test/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreterTest.java
new file mode 100644
index 0000000..839be66
--- /dev/null
+++ b/elasticsearch/src/test/java/org/apache/zeppelin/elasticsearch/ElasticsearchInterpreterTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.elasticsearch;
+
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Properties;
+import java.util.UUID;
+
+import org.apache.commons.lang.math.RandomUtils;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.transport.InetSocketTransportAddress;
+import org.elasticsearch.node.Node;
+import org.elasticsearch.node.NodeBuilder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ElasticsearchInterpreterTest {
+
+ private static Client elsClient;
+ private static Node elsNode;
+ private static ElasticsearchInterpreter interpreter;
+
+ private static final String[] METHODS = { "GET", "PUT", "DELETE", "POST" };
+ private static final String[] STATUS = { "200", "404", "500", "403" };
+
+ private static final String ELS_CLUSTER_NAME = "zeppelin-elasticsearch-interpreter-test";
+ private static final String ELS_HOST = "localhost";
+ private static final String ELS_TRANSPORT_PORT = "10300";
+ private static final String ELS_HTTP_PORT = "10200";
+ private static final String ELS_PATH = "/tmp/els";
+
+
+ @BeforeClass
+ public static void populate() throws IOException {
+
+ final Settings settings = Settings.settingsBuilder()
+ .put("cluster.name", ELS_CLUSTER_NAME)
+ .put("network.host", ELS_HOST)
+ .put("http.port", ELS_HTTP_PORT)
+ .put("transport.tcp.port", ELS_TRANSPORT_PORT)
+ .put("path.home", ELS_PATH)
+ .build();
+
+ elsNode = NodeBuilder.nodeBuilder().settings(settings).node();
+ elsClient = elsNode.client();
+
+ for (int i = 0; i < 50; i++) {
+ elsClient.prepareIndex("logs", "http", "" + i)
+ .setRefresh(true)
+ .setSource(jsonBuilder()
+ .startObject()
+ .field("date", new Date())
+ .startObject("request")
+ .field("method", METHODS[RandomUtils.nextInt(METHODS.length)])
+ .field("url", "/zeppelin/" + UUID.randomUUID().toString())
+ .field("headers", Arrays.asList("Accept: *.*", "Host: apache.org"))
+ .endObject()
+ .field("status", STATUS[RandomUtils.nextInt(STATUS.length)])
+ )
+ .get();
+ }
+
+ final Properties props = new Properties();
+ props.put(ElasticsearchInterpreter.ELASTICSEARCH_HOST, ELS_HOST);
+ props.put(ElasticsearchInterpreter.ELASTICSEARCH_PORT, ELS_TRANSPORT_PORT);
+ props.put(ElasticsearchInterpreter.ELASTICSEARCH_CLUSTER_NAME, ELS_CLUSTER_NAME);
+ interpreter = new ElasticsearchInterpreter(props);
+ interpreter.open();
+ }
+
+ @AfterClass
+ public static void clean() {
+ if (interpreter != null) {
+ interpreter.close();
+ }
+
+ if (elsClient != null) {
+ elsClient.admin().indices().delete(new DeleteIndexRequest("logs")).actionGet();
+ elsClient.close();
+ }
+
+ if (elsNode != null) {
+ elsNode.close();
+ }
+ }
+
+ @Test
+ public void testCount() {
+
+ InterpreterResult res = interpreter.interpret("count /unknown", null);
+ assertEquals(Code.ERROR, res.code());
+
+ res = interpreter.interpret("count /logs", null);
+ assertEquals("50", res.message());
+ }
+
+ @Test
+ public void testGet() {
+
+ InterpreterResult res = interpreter.interpret("get /logs/http/unknown", null);
+ assertEquals(Code.ERROR, res.code());
+
+ res = interpreter.interpret("get /logs/http/10", null);
+ assertEquals(Code.SUCCESS, res.code());
+ }
+
+ @Test
+ public void testSearch() {
+
+ InterpreterResult res = interpreter.interpret("size 10\nsearch /logs *", null);
+ assertEquals(Code.SUCCESS, res.code());
+
+ res = interpreter.interpret("search /logs {{{hello}}}", null);
+ assertEquals(Code.ERROR, res.code());
+
+ res = interpreter.interpret("search /logs { \"query\": { \"match\": { \"status\": 500 } } }", null);
+ assertEquals(Code.SUCCESS, res.code());
+
+ res = interpreter.interpret("search /logs status:404", null);
+ assertEquals(Code.SUCCESS, res.code());
+ }
+
+ @Test
+ public void testIndex() {
+
+ InterpreterResult res = interpreter.interpret("index /logs { \"date\": \"" + new Date() + "\", \"method\": \"PUT\", \"status\": \"500\" }", null);
+ assertEquals(Code.ERROR, res.code());
+
+ res = interpreter.interpret("index /logs/http { \"date\": \"2015-12-06T14:54:23.368Z\", \"method\": \"PUT\", \"status\": \"500\" }", null);
+ assertEquals(Code.SUCCESS, res.code());
+ }
+
+ @Test
+ public void testDelete() {
+
+ InterpreterResult res = interpreter.interpret("delete /logs/http/unknown", null);
+ assertEquals(Code.ERROR, res.code());
+
+ res = interpreter.interpret("delete /logs/http/11", null);
+ assertEquals("11", res.message());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 1984cca..5e492fa 100755
--- a/pom.xml
+++ b/pom.xml
@@ -100,6 +100,7 @@
<module>kylin</module>
<module>lens</module>
<module>cassandra</module>
+ <module>elasticsearch</module>
<module>zeppelin-web</module>
<module>zeppelin-server</module>
<module>zeppelin-distribution</module>
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/zeppelin-distribution/src/bin_license/LICENSE
----------------------------------------------------------------------
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index 76e5b23..1cce5dc 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -66,6 +66,31 @@ The following components are provided under Apache License.
(Apache 2.0) lz4-java (net.jpountz.lz4:lz4:jar:1.3.0 - https://github.com/jpountz/lz4-java)
(Apache 2.0) RoaringBitmap (org.roaringbitmap:RoaringBitmap:jar:0.4.5 - https://github.com/lemire/RoaringBitmap)
(Apache 2.0) json4s (org.json4s:json4s-ast_2.10:jar:3.2.10 - https://github.com/json4s/json4s)
+ (Apache 2.0) HPPC Collections (com.carrotsearch:hppc:0.7.1 - http://labs.carrotsearch.com/hppc.html/hppc)
+ (Apache 2.0) Jackson-dataformat-CBOR (com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.6.2 - http://wiki.fasterxml.com/JacksonForCbor)
+ (Apache 2.0) Jackson-dataformat-Smile (com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.6.2 - http://wiki.fasterxml.com/JacksonForSmile)
+ (Apache 2.0) Jackson-dataformat-YAML (com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.6.2 - https://github.com/FasterXML/jackson)
+ (Apache 2.0) json-flattener (com.github.wnameless:json-flattener:0.1.1 - https://github.com/wnameless/json-flattener)
+ (Apache 2.0) Spatial4J (com.spatial4j:spatial4j:0.4.1 - https://github.com/spatial4j/spatial4j)
+ (Apache 2.0) T-Digest (com.tdunning:t-digest:3.0 - https://github.com/tdunning/t-digest)
+ (Apache 2.0) Netty (io.netty:netty:3.10.5.Final - http://netty.io/)
+ (Apache 2.0) Lucene Common Analyzers (org.apache.lucene:lucene-analyzers-common:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-analyzers-common)
+ (Apache 2.0) Lucene Memory (org.apache.lucene:lucene-backward-codecs:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-backward-codecs)
+ (Apache 2.0) Lucene Core (org.apache.lucene:lucene-core:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-core)
+ (Apache 2.0) Lucene Grouping (org.apache.lucene:lucene-grouping:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-grouping)
+ (Apache 2.0) Lucene Highlighter (org.apache.lucene:lucene-highlighter:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-highlighter)
+ (Apache 2.0) Lucene Join (org.apache.lucene:lucene-join:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-join)
+ (Apache 2.0) Lucene Memory (org.apache.lucene:lucene-memory:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-memory)
+ (Apache 2.0) Lucene Miscellaneous (org.apache.lucene:lucene-misc:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-misc)
+ (Apache 2.0) Lucene Queries (org.apache.lucene:lucene-queries:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-queries)
+ (Apache 2.0) Lucene QueryParsers (org.apache.lucene:lucene-queryparser:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-queryparser)
+ (Apache 2.0) Lucene Sandbox (org.apache.lucene:lucene-sandbox:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-sandbox)
+ (Apache 2.0) Lucene Spatial (org.apache.lucene:lucene-spatial:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-spatial)
+ (Apache 2.0) Lucene Spatial 3D (org.apache.lucene:lucene-spatial3d:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-spatial3d)
+ (Apache 2.0) Lucene Suggest (org.apache.lucene:lucene-suggest:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-suggest)
+ (Apache 2.0) Elasticsearch: Core (org.elasticsearch:elasticsearch:2.1.0 - http://nexus.sonatype.org/oss-repository-hosting.html/parent/elasticsearch)
+ (Apache 2.0) Joda convert (org.joda:joda-convert:1.2 - http://joda-convert.sourceforge.net)
+ (Apache 2.0) SnakeYAML (org.yaml:snakeyaml:1.15 - http://www.snakeyaml.org)
@@ -104,6 +129,7 @@ The following components are provided under the MIT License.
(The MIT License) Objenesis (org.objenesis:objenesis:2.1 - https://github.com/easymock/objenesis) - Copyright (c) 2006-2015 the original author and authors
(The MIT License) JCL 1.1.1 implemented over SLF4J (org.slf4j:jcl-over-slf4j:1.7.5 - http://www.slf4j.org)
(The MIT License) JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.5 - http://www.slf4j.org)
+ (The MIT License) minimal-json (com.eclipsesource.minimal-json:minimal-json:0.9.4 - https://github.com/ralfstx/minimal-json)
@@ -178,3 +204,10 @@ See licenses/LICENSE-postgresql
+========================================================================
+Creative Commons CC0 (http://creativecommons.org/publicdomain/zero/1.0/)
+========================================================================
+
+ (CC0 1.0 Universal) JSR166e (com.twitter:jsr166e:1.1.0 - http://github.com/twitter/jsr166e)
+ (Public Domain, per Creative Commons CC0) HdrHistogram (org.hdrhistogram:HdrHistogram:2.1.6 - http://hdrhistogram.github.io/HdrHistogram/)
+
http://git-wip-us.apache.org/repos/asf/incubator-zeppelin/blob/7e902832/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index 72b6a3c..a4d23e9 100755
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -413,7 +413,8 @@ public class ZeppelinConfiguration extends XMLConfiguration {
+ "org.apache.zeppelin.cassandra.CassandraInterpreter,"
+ "org.apache.zeppelin.geode.GeodeOqlInterpreter,"
+ "org.apache.zeppelin.postgresql.PostgreSqlInterpreter,"
- + "org.apache.zeppelin.kylin.KylinInterpreter"),
+ + "org.apache.zeppelin.kylin.KylinInterpreter,"
+ + "org.apache.zeppelin.elasticsearch.ElasticsearchInterpreter"),
ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"),
ZEPPELIN_INTERPRETER_CONNECT_TIMEOUT("zeppelin.interpreter.connect.timeout", 30000),
ZEPPELIN_ENCODING("zeppelin.encoding", "UTF-8"),