diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html
index ecdccbd7ffd..215c94426a3 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -137,6 +137,7 @@
Lens
Livy
Markdown
+ Neo4j
Pig
Postgresql, HAWQ
R
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-config.png b/docs/assets/themes/zeppelin/img/docs-img/neo4j-config.png
new file mode 100644
index 00000000000..2de3699e8a5
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-config.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png b/docs/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png
new file mode 100644
index 00000000000..177e0a5e764
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-dynamic-forms.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-graph.png b/docs/assets/themes/zeppelin/img/docs-img/neo4j-graph.png
new file mode 100644
index 00000000000..396b960db6c
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-graph.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif b/docs/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif
new file mode 100644
index 00000000000..28c191516fd
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/neo4j-interpreter-video.gif differ
diff --git a/docs/index.md b/docs/index.md
index 5e991f1903f..dbec0402d97 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -142,6 +142,7 @@ limitations under the License.
* [Lens](./interpreter/lens.html)
* [Livy](./interpreter/livy.html)
* [markdown](./interpreter/markdown.html)
+ * [Neo4j](./interpreter/neo4j.html)
* [Pig](./interpreter/pig.html)
* [Postgresql, HAWQ](./interpreter/postgresql.html)
* [Python](./interpreter/python.html)
diff --git a/docs/interpreter/neo4j.md b/docs/interpreter/neo4j.md
new file mode 100644
index 00000000000..37f1f8c935d
--- /dev/null
+++ b/docs/interpreter/neo4j.md
@@ -0,0 +1,117 @@
+---
+layout: page
+title: "Neo4j Interpreter for Apache Zeppelin"
+description: "Neo4j is a native graph database, designed to store and process graphs from bottom to top."
+group: interpreter
+---
+
+{% include JB/setup %}
+
+# Neo4j Interpreter for Apache Zeppelin
+
+
+
+## Overview
+[Neo4j](https://neo4j.com/product/) is a native graph database, designed to store and process graphs from bottom to top.
+
+
+
+
+## Configuration
+
+
+ | Property |
+ Default |
+ Description |
+
+
+ | neo4j.url |
+ bolt://localhost:7687 |
+ The Neo4j's BOLT url. |
+
+
+ | neo4j.auth.type |
+ BASIC |
+ The Neo4j's authentication type (NONE, BASIC). |
+
+
+ | neo4j.auth.user |
+ neo4j |
+ The Neo4j user name. |
+
+
+ | neo4j.auth.password |
+ neo4j |
+ The Neo4j user password. |
+
+
+ | neo4j.max.concurrency |
+ 50 |
+ Max concurrency call from Zeppelin to Neo4j server. |
+
+
+
+
+ 
+
+
+
+## Enabling the Neo4j Interpreter
+In a notebook, to enable the **Neo4j** interpreter, click the **Gear** icon and select **Neo4j**.
+
+## Using the Neo4j Interpreter
+In a paragraph, use `%neo4j` to select the Neo4j interpreter and then input the Cypher commands.
+For list of Cypher commands please refer to the official [Cyper Refcard](http://neo4j.com/docs/cypher-refcard/current/)
+
+```bash
+%neo4j
+//Sample the TrumpWorld dataset
+WITH
+'https://docs.google.com/spreadsheets/u/1/d/1Z5Vo5pbvxKJ5XpfALZXvCzW26Cl4we3OaN73K9Ae5Ss/export?format=csv&gid=1996904412' AS url
+LOAD CSV WITH HEADERS FROM url AS row
+RETURN row.`Entity A`, row.`Entity A Type`, row.`Entity B`, row.`Entity B Type`, row.Connection, row.`Source(s)`
+LIMIT 10
+```
+
+The Neo4j interpreter leverages the [Network display system](../usage/display_system/basic.html#network) allowing to visualize the them directly from the paragraph.
+
+
+### Write your Cypher queries and navigate your graph
+
+This query:
+
+```bash
+%neo4j
+MATCH (vp:Person {name:"VLADIMIR PUTIN"}), (dt:Person {name:"DONALD J. TRUMP"})
+MATCH path = allShortestPaths( (vp)-[*]-(dt) )
+RETURN path
+```
+produces the following result_
+
+
+### Apply Zeppelin Dynamic Forms
+You can leverage [Zeppelin Dynamic Form](../usage/dynamic_form/intro.html) inside your queries. This query:
+
+```bash
+%neo4j
+MATCH (o:Organization)-[r]-()
+RETURN o.name, count(*), collect(distinct type(r)) AS types
+ORDER BY count(*) DESC
+LIMIT ${Show top=10}
+```
+
+produces the following result:
+
+
diff --git a/neo4j/pom.xml b/neo4j/pom.xml
new file mode 100644
index 00000000000..298726fb82b
--- /dev/null
+++ b/neo4j/pom.xml
@@ -0,0 +1,144 @@
+
+
+
+
+ 4.0.0
+
+
+ zeppelin
+ org.apache.zeppelin
+ 0.8.0-SNAPSHOT
+ ..
+
+
+ org.apache.zeppelin
+ zeppelin-neo4j
+ jar
+ 0.8.0-SNAPSHOT
+ Zeppelin: Neo4j interpreter
+
+
+ 1.4.3
+ 3.2.3
+ 3.2.3
+ 2.8.9
+
+
+
+
+ ${project.groupId}
+ zeppelin-interpreter
+ ${project.version}
+ provided
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+
+ org.neo4j.driver
+ neo4j-java-driver
+ ${neo4j.driver.version}
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+ junit
+ junit
+ test
+
+
+
+ org.neo4j.test
+ neo4j-harness
+ ${neo4j.version}
+ test
+
+
+
+
+
+
+ maven-enforcer-plugin
+ 1.3.1
+
+
+ enforce
+ none
+
+
+
+
+
+ maven-dependency-plugin
+ 2.8
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/../../interpreter/neo4j
+ false
+ false
+ true
+ runtime
+
+
+
+ copy-artifact
+ package
+
+ copy
+
+
+ ${project.build.directory}/../../interpreter/neo4j
+ false
+ false
+ true
+ runtime
+
+
+ ${project.groupId}
+ ${project.artifactId}
+ ${project.version}
+ ${project.packaging}
+
+
+
+
+
+
+
+
+
+
diff --git a/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jConnectionManager.java b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jConnectionManager.java
new file mode 100644
index 00000000000..7cd504ef200
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jConnectionManager.java
@@ -0,0 +1,151 @@
+/*
+ * 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.graph.neo4j;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.resource.Resource;
+import org.apache.zeppelin.resource.ResourcePool;
+import org.neo4j.driver.v1.AuthToken;
+import org.neo4j.driver.v1.AuthTokens;
+import org.neo4j.driver.v1.Config;
+import org.neo4j.driver.v1.Driver;
+import org.neo4j.driver.v1.GraphDatabase;
+import org.neo4j.driver.v1.Session;
+import org.neo4j.driver.v1.StatementResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Neo4j connection manager for Zeppelin.
+ */
+public class Neo4jConnectionManager {
+ static final Logger LOGGER = LoggerFactory.getLogger(Neo4jConnectionManager.class);
+
+ public static final String NEO4J_SERVER_URL = "neo4j.url";
+ public static final String NEO4J_AUTH_TYPE = "neo4j.auth.type";
+ public static final String NEO4J_AUTH_USER = "neo4j.auth.user";
+ public static final String NEO4J_AUTH_PASSWORD = "neo4j.auth.password";
+ public static final String NEO4J_MAX_CONCURRENCY = "neo4j.max.concurrency";
+
+ private static final Pattern PROPERTY_PATTERN = Pattern.compile("\\{\\w+\\}");
+ private static final String REPLACE_CURLY_BRACKETS = "\\{|\\}";
+
+ private static final Pattern $_PATTERN = Pattern.compile("\\$\\w+\\}");
+ private static final String REPLACE_$ = "\\$";
+
+ private Driver driver = null;
+
+ private final String neo4jUrl;
+
+ private final Config config;
+
+ private final AuthToken authToken;
+
+ /**
+ *
+ * Enum type for the AuthToken
+ *
+ */
+ public enum Neo4jAuthType {NONE, BASIC}
+
+ public Neo4jConnectionManager(Properties properties) {
+ this.neo4jUrl = properties.getProperty(NEO4J_SERVER_URL);
+ this.config = Config.build()
+ .withMaxIdleSessions(Integer.parseInt(properties.getProperty(NEO4J_MAX_CONCURRENCY)))
+ .toConfig();
+ String authType = properties.getProperty(NEO4J_AUTH_TYPE);
+ switch (Neo4jAuthType.valueOf(authType.toUpperCase())) {
+ case BASIC:
+ String username = properties.getProperty(NEO4J_AUTH_USER);
+ String password = properties.getProperty(NEO4J_AUTH_PASSWORD);
+ this.authToken = AuthTokens.basic(username, password);
+ break;
+ case NONE:
+ LOGGER.debug("Creating NONE authentication");
+ this.authToken = AuthTokens.none();
+ break;
+ default:
+ throw new RuntimeException("Neo4j authentication type not supported");
+ }
+ }
+
+ private Driver getDriver() {
+ if (driver == null) {
+ driver = GraphDatabase.driver(this.neo4jUrl, this.authToken, this.config);
+ }
+ return driver;
+ }
+
+ public void open() {
+ getDriver();
+ }
+
+ public void close() {
+ getDriver().close();
+ }
+
+ private Session getSession() {
+ return getDriver().session();
+ }
+
+ public StatementResult execute(String cypherQuery,
+ InterpreterContext interpreterContext) {
+ Map params = new HashMap<>();
+ if (interpreterContext != null) {
+ ResourcePool resourcePool = interpreterContext.getResourcePool();
+ Set keys = extractParams(cypherQuery, PROPERTY_PATTERN, REPLACE_CURLY_BRACKETS);
+ keys.addAll(extractParams(cypherQuery, $_PATTERN, REPLACE_$));
+ for (String key : keys) {
+ Resource resource = resourcePool.get(key);
+ if (resource != null) {
+ params.put(key, resource.get());
+ }
+ }
+ }
+ LOGGER.debug("Executing cypher query {} with params {}", cypherQuery, params);
+ StatementResult result;
+ try (Session session = getSession()) {
+ result = params.isEmpty()
+ ? getSession().run(cypherQuery) : getSession().run(cypherQuery, params);
+ }
+ return result;
+ }
+
+ public StatementResult execute(String cypherQuery) {
+ return execute(cypherQuery, null);
+ }
+
+ private Set extractParams(String cypherQuery, Pattern pattern, String replaceChar) {
+ Matcher matcher = pattern.matcher(cypherQuery);
+ Set keys = new HashSet<>();
+ while (matcher.find()) {
+ keys.add(matcher.group().replaceAll(replaceChar, StringUtils.EMPTY));
+ }
+ return keys;
+ }
+
+}
diff --git a/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java
new file mode 100644
index 00000000000..a6255225587
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreter.java
@@ -0,0 +1,274 @@
+/*
+ * 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.graph.neo4j;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.zeppelin.graph.neo4j.utils.Neo4jConversionUtils;
+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.interpreter.graph.GraphResult;
+import org.apache.zeppelin.scheduler.Scheduler;
+import org.apache.zeppelin.scheduler.SchedulerFactory;
+import org.neo4j.driver.internal.types.InternalTypeSystem;
+import org.neo4j.driver.internal.util.Iterables;
+import org.neo4j.driver.v1.Record;
+import org.neo4j.driver.v1.StatementResult;
+import org.neo4j.driver.v1.Value;
+import org.neo4j.driver.v1.types.Node;
+import org.neo4j.driver.v1.types.Relationship;
+import org.neo4j.driver.v1.types.TypeSystem;
+import org.neo4j.driver.v1.util.Pair;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Neo4j interpreter for Zeppelin.
+ */
+public class Neo4jCypherInterpreter extends Interpreter {
+ private static final String TABLE = "%table";
+ public static final String NEW_LINE = "\n";
+ public static final String TAB = "\t";
+
+ private static final String MAP_KEY_TEMPLATE = "%s.%s";
+
+ private Map labels;
+
+ private Set types;
+
+ private final Neo4jConnectionManager neo4jConnectionManager;
+
+ private final ObjectMapper jsonMapper = new ObjectMapper();
+
+ public Neo4jCypherInterpreter(Properties properties) {
+ super(properties);
+ this.neo4jConnectionManager = new Neo4jConnectionManager(properties);
+ }
+
+ @Override
+ public void open() {
+ this.neo4jConnectionManager.open();
+ }
+
+ @Override
+ public void close() {
+ this.neo4jConnectionManager.close();
+ }
+
+ public Map getLabels(boolean refresh) {
+ if (labels == null || refresh) {
+ Map old = labels == null ?
+ new LinkedHashMap() : new LinkedHashMap<>(labels);
+ labels = new LinkedHashMap<>();
+ StatementResult result = this.neo4jConnectionManager.execute("CALL db.labels()");
+ Set colors = new HashSet<>();
+ while (result.hasNext()) {
+ Record record = result.next();
+ String label = record.get("label").asString();
+ String color = old.get(label);
+ while (color == null || colors.contains(color)) {
+ color = Neo4jConversionUtils.getRandomLabelColor();
+ }
+ colors.add(color);
+ labels.put(label, color);
+ }
+ }
+ return labels;
+ }
+
+ private Set getTypes(boolean refresh) {
+ if (types == null || refresh) {
+ types = new HashSet<>();
+ StatementResult result = this.neo4jConnectionManager.execute("CALL db.relationshipTypes()");
+ while (result.hasNext()) {
+ Record record = result.next();
+ types.add(record.get("relationshipType").asString());
+ }
+ }
+ return types;
+ }
+
+ @Override
+ public InterpreterResult interpret(String cypherQuery, InterpreterContext interpreterContext) {
+ logger.info("Opening session");
+ if (StringUtils.isBlank(cypherQuery)) {
+ return new InterpreterResult(Code.SUCCESS);
+ }
+ try {
+ StatementResult result = this.neo4jConnectionManager.execute(cypherQuery,
+ interpreterContext);
+ Set nodes = new HashSet<>();
+ Set relationships = new HashSet<>();
+ List columns = new ArrayList<>();
+ List> lines = new ArrayList>();
+ while (result.hasNext()) {
+ Record record = result.next();
+ List> fields = record.fields();
+ List line = new ArrayList<>();
+ for (Pair field : fields) {
+ if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.NODE())) {
+ nodes.add(field.value().asNode());
+ } else if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.RELATIONSHIP())) {
+ relationships.add(field.value().asRelationship());
+ } else if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.PATH())) {
+ nodes.addAll(Iterables.asList(field.value().asPath().nodes()));
+ relationships.addAll(Iterables.asList(field.value().asPath().relationships()));
+ } else {
+ setTabularResult(field.key(), field.value(), columns, line,
+ InternalTypeSystem.TYPE_SYSTEM);
+ }
+ }
+ if (!line.isEmpty()) {
+ lines.add(line);
+ }
+ }
+ if (!nodes.isEmpty()) {
+ return renderGraph(nodes, relationships);
+ } else {
+ return renderTable(columns, lines);
+ }
+ } catch (Exception e) {
+ logger.error("Exception while interpreting cypher query", e);
+ return new InterpreterResult(Code.ERROR, e.getMessage());
+ }
+ }
+
+ private void setTabularResult(String key, Object obj, List columns, List line,
+ TypeSystem typeSystem) {
+ if (obj instanceof Value) {
+ Value value = (Value) obj;
+ if (value.hasType(typeSystem.MAP())) {
+ Map map = value.asMap();
+ for (Entry entry : map.entrySet()) {
+ setTabularResult(String.format(MAP_KEY_TEMPLATE, key, entry.getKey()), entry.getValue(),
+ columns, line, typeSystem);
+ }
+ } else {
+ addValueToLine(key, columns, line, value);
+ }
+ } else if (obj instanceof Map) {
+ Map map = (Map) obj;
+ for (Entry entry : map.entrySet()) {
+ setTabularResult(String.format(MAP_KEY_TEMPLATE, key, entry.getKey()), entry.getValue(),
+ columns, line, typeSystem);
+ }
+ } else {
+ addValueToLine(key, columns, line, obj);
+ }
+ }
+
+ private void addValueToLine(String key, List columns, List line, Object value) {
+ if (!columns.contains(key)) {
+ columns.add(key);
+ }
+ int position = columns.indexOf(key);
+ if (line.size() < columns.size()) {
+ for (int i = line.size(); i < columns.size(); i++) {
+ line.add(null);
+ }
+ }
+ if (value != null) {
+ if (value instanceof Value) {
+ Value val = (Value) value;
+ if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.LIST())) {
+ value = val.asList();
+ } else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.MAP())) {
+ value = val.asMap();
+ }
+ }
+ if (value instanceof Collection) {
+ try {
+ value = jsonMapper.writer().writeValueAsString(value);
+ } catch (Exception ignored) {}
+ }
+ }
+ line.set(position, value == null ? null : value.toString());
+ }
+
+ private InterpreterResult renderTable(List cols, List> lines) {
+ logger.info("Executing renderTable method");
+ StringBuilder msg = null;
+ if (cols.isEmpty()) {
+ msg = new StringBuilder();
+ } else {
+ msg = new StringBuilder(TABLE);
+ msg.append(NEW_LINE);
+ msg.append(StringUtils.join(cols, TAB));
+ msg.append(NEW_LINE);
+ for (List line : lines) {
+ if (line.size() < cols.size()) {
+ for (int i = line.size(); i < cols.size(); i++) {
+ line.add(null);
+ }
+ }
+ msg.append(StringUtils.join(line, TAB));
+ msg.append(NEW_LINE);
+ }
+ }
+ return new InterpreterResult(Code.SUCCESS, msg.toString());
+ }
+
+ private InterpreterResult renderGraph(Set nodes,
+ Set relationships) {
+ logger.info("Executing renderGraph method");
+ List nodesList = new ArrayList<>();
+ List relsList = new ArrayList<>();
+ for (Relationship rel : relationships) {
+ relsList.add(Neo4jConversionUtils.toZeppelinRelationship(rel));
+ }
+ Map labels = getLabels(true);
+ for (Node node : nodes) {
+ nodesList.add(Neo4jConversionUtils.toZeppelinNode(node, labels));
+ }
+ return new GraphResult(Code.SUCCESS,
+ new GraphResult.Graph(nodesList, relsList, labels, getTypes(true), true));
+ }
+
+ @Override
+ public Scheduler getScheduler() {
+ return SchedulerFactory.singleton()
+ .createOrGetParallelScheduler(Neo4jCypherInterpreter.class.getName() + this.hashCode(),
+ Integer.parseInt(getProperty(Neo4jConnectionManager.NEO4J_MAX_CONCURRENCY)));
+ }
+
+ @Override
+ public int getProgress(InterpreterContext context) {
+ return 0;
+ }
+
+ @Override
+ public FormType getFormType() {
+ return FormType.SIMPLE;
+ }
+
+ @Override
+ public void cancel(InterpreterContext context) {
+ }
+
+}
diff --git a/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/utils/Neo4jConversionUtils.java b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/utils/Neo4jConversionUtils.java
new file mode 100644
index 00000000000..484940198a7
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/zeppelin/graph/neo4j/utils/Neo4jConversionUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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.graph.neo4j.utils;
+
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.neo4j.driver.v1.types.Node;
+import org.neo4j.driver.v1.types.Relationship;
+
+/**
+ * Neo4jConversionUtils
+ */
+public class Neo4jConversionUtils {
+ private Neo4jConversionUtils() {}
+
+ private static final String[] LETTERS = "0123456789ABCDEF".split("");
+
+ public static final String COLOR_GREY = "#D3D3D3";
+
+ public static org.apache.zeppelin.tabledata.Node toZeppelinNode(Node n,
+ Map graphLabels) {
+ Set labels = new LinkedHashSet<>();
+ String firstLabel = null;
+ for (String label : n.labels()) {
+ if (firstLabel == null) {
+ firstLabel = label;
+ }
+ labels.add(label);
+ }
+ return new org.apache.zeppelin.tabledata.Node(n.id(), n.asMap(),
+ labels);
+ }
+
+ public static org.apache.zeppelin.tabledata.Relationship
+ toZeppelinRelationship(Relationship r) {
+ return new org.apache.zeppelin.tabledata.Relationship(r.id(), r.asMap(),
+ r.startNodeId(), r.endNodeId(), r.type());
+ }
+
+ public static String getRandomLabelColor() {
+ char[] color = new char[7];
+ color[0] = '#';
+ for (int i = 1; i < color.length; i++) {
+ color[i] = LETTERS[(int) Math.floor(Math.random() * 16)].charAt(0);
+ }
+ return new String(color);
+ }
+
+}
diff --git a/neo4j/src/main/resources/interpreter-setting.json b/neo4j/src/main/resources/interpreter-setting.json
new file mode 100644
index 00000000000..8db4367cc3e
--- /dev/null
+++ b/neo4j/src/main/resources/interpreter-setting.json
@@ -0,0 +1,42 @@
+[
+ {
+ "group": "neo4j",
+ "name": "neo4j",
+ "className": "org.apache.zeppelin.graph.neo4j.Neo4jCypherInterpreter",
+ "properties": {
+ "neo4j.url": {
+ "envName": null,
+ "propertyName": "neo4j.url",
+ "defaultValue": "bolt://localhost:7687",
+ "description": "The Neo4j's BOLT url."
+ },
+ "neo4j.auth.type": {
+ "envName": null,
+ "propertyName": "neo4j.auth.type",
+ "defaultValue": "BASIC",
+ "description": "The Neo4j's authentication type (NONE, BASIC)."
+ },
+ "neo4j.auth.user": {
+ "envName": null,
+ "propertyName": "neo4j.auth.user",
+ "defaultValue": "",
+ "description": "The Neo4j user name."
+ },
+ "neo4j.auth.password": {
+ "envName": null,
+ "propertyName": "neo4j.auth.password",
+ "defaultValue": "",
+ "description": "The Neo4j user password."
+ },
+ "neo4j.max.concurrency": {
+ "envName": null,
+ "propertyName": "neo4j.max.concurrency",
+ "defaultValue": "50",
+ "description": "Max concurrency call from Zeppelin to Neo4j server."
+ }
+ },
+ "editor": {
+ "editOnDblClick": false
+ }
+ }
+]
diff --git a/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java b/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java
new file mode 100644
index 00000000000..1bb14b71e1f
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/zeppelin/graph/neo4j/Neo4jCypherInterpreterTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.graph.neo4j;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.zeppelin.display.AngularObjectRegistry;
+import org.apache.zeppelin.display.GUI;
+import org.apache.zeppelin.graph.neo4j.Neo4jConnectionManager.Neo4jAuthType;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterContextRunner;
+import org.apache.zeppelin.interpreter.InterpreterGroup;
+import org.apache.zeppelin.interpreter.InterpreterOutput;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+import org.apache.zeppelin.interpreter.graph.GraphResult;
+import org.apache.zeppelin.resource.LocalResourcePool;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.neo4j.harness.ServerControls;
+import org.neo4j.harness.TestServerBuilders;
+
+import com.google.gson.Gson;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class Neo4jCypherInterpreterTest {
+
+ private Neo4jCypherInterpreter interpreter;
+
+ private InterpreterContext context;
+
+ private static ServerControls server;
+
+ private static final Gson gson = new Gson();
+
+ private static final String LABEL_PERSON = "Person";
+ private static final String REL_KNOWS = "KNOWS";
+
+ private static final String CYPHER_FOREACH = "FOREACH (x in range(1,1000) | CREATE (:%s{name: \"name\" + x, age: %s}))";
+ private static final String CHPHER_UNWIND = "UNWIND range(1,1000) as x "
+ + "MATCH (n), (m) WHERE id(n) = x AND id(m) = toInt(rand() * 1000) "
+ + "CREATE (n)-[:%s]->(m)";
+
+ @BeforeClass
+ public static void setUpNeo4jServer() throws Exception {
+ server = TestServerBuilders.newInProcessBuilder()
+ .withConfig("dbms.security.auth_enabled","false")
+ .withFixture(String.format(CYPHER_FOREACH, LABEL_PERSON, "x % 10"))
+ .withFixture(String.format(CHPHER_UNWIND, REL_KNOWS))
+ .newServer();
+ }
+
+ @AfterClass
+ public static void tearDownNeo4jServer() throws Exception {
+ server.close();
+ }
+
+ @Before
+ public void setUpZeppelin() {
+ Properties p = new Properties();
+ p.setProperty(Neo4jConnectionManager.NEO4J_SERVER_URL, server.boltURI().toString());
+ p.setProperty(Neo4jConnectionManager.NEO4J_AUTH_TYPE, Neo4jAuthType.NONE.toString());
+ p.setProperty(Neo4jConnectionManager.NEO4J_MAX_CONCURRENCY, "50");
+ interpreter = new Neo4jCypherInterpreter(p);
+ context = new InterpreterContext("note", "id", null, "title", "text",
+ new AuthenticationInfo(),
+ new HashMap(),
+ new GUI(),
+ new AngularObjectRegistry(new InterpreterGroup().getId(), null),
+ new LocalResourcePool("id"),
+ new LinkedList(),
+ new InterpreterOutput(null));
+ }
+
+ @After
+ public void tearDownZeppelin() throws Exception {
+ interpreter.close();
+ }
+
+ @Test
+ public void testTableWithArray() {
+ interpreter.open();
+ InterpreterResult result = interpreter.interpret("return 'a' as colA, 'b' as colB, [1, 2, 3] as colC", context);
+ assertEquals(Code.SUCCESS, result.code());
+ final String tableResult = "colA\tcolB\tcolC\n\"a\"\t\"b\"\t[1,2,3]\n";
+ assertEquals(tableResult, result.toString().replace("%table ", StringUtils.EMPTY));
+
+ result = interpreter.interpret("return 'a' as colA, 'b' as colB, [{key: \"value\"}, {key: 1}] as colC", context);
+ assertEquals(Code.SUCCESS, result.code());
+ final String tableResultWithMap = "colA\tcolB\tcolC\n\"a\"\t\"b\"\t[{\"key\":\"value\"},{\"key\":1}]\n";
+ assertEquals(tableResultWithMap, result.toString().replace("%table ", StringUtils.EMPTY));
+ }
+
+ @Test
+ public void testCreateIndex() {
+ interpreter.open();
+ InterpreterResult result = interpreter.interpret("CREATE INDEX ON :Person(name)", context);
+ assertEquals(Code.SUCCESS, result.code());
+ assertEquals(StringUtils.EMPTY, result.toString());
+ }
+
+ @Test
+ public void testRenderTable() {
+ interpreter.open();
+ InterpreterResult result = interpreter.interpret("MATCH (n:Person) "
+ + "WHERE n.name IN ['name1', 'name2', 'name3'] "
+ + "RETURN n.name AS name, n.age AS age", context);
+ assertEquals(Code.SUCCESS, result.code());
+ final String tableResult = "name\tage\n\"name1\"\t1\n\"name2\"\t2\n\"name3\"\t3\n";
+ assertEquals(tableResult, result.toString().replace("%table ", StringUtils.EMPTY));
+ }
+
+ @Test
+ public void testRenderMap() {
+ interpreter.open();
+ final String jsonQuery = "RETURN {key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]} as object";
+ final String objectKey = "object.key";
+ final String objectListKey = "object.listKey";
+ InterpreterResult result = interpreter.interpret(jsonQuery, context);
+ assertEquals(Code.SUCCESS, result.code());
+ String[] rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
+ assertEquals(rows.length, 2);
+ List header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB));
+ assertEquals(header.contains(objectKey), true);
+ assertEquals(header.contains(objectListKey), true);
+ List row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB));
+ assertEquals(row.size(), header.size());
+ assertEquals(row.get(header.indexOf(objectKey)), "value");
+ assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
+
+ final String query = "WITH [{key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]},"
+ + "{key: \"value2\", listKey: [{inner: \"Map12\"}, {inner: \"Map22\"}]}] "
+ + "AS array UNWIND array AS object RETURN object";
+ result = interpreter.interpret(query, context);
+ assertEquals(Code.SUCCESS, result.code());
+ rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
+ assertEquals(rows.length, 3);
+ header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB));
+ assertEquals(header.contains(objectKey), true);
+ assertEquals(header.contains(objectListKey), true);
+ row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB));
+ assertEquals(row.size(), header.size());
+ assertEquals(row.get(header.indexOf(objectKey)), "value");
+ assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
+ row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB));
+ assertEquals(row.size(), header.size());
+ assertEquals(row.get(header.indexOf(objectKey)), "value2");
+ assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map12\"},{\"inner\":\"Map22\"}]");
+
+ final String jsonListWithNullQuery = "WITH [{key: \"value\", listKey: null},"
+ + "{key: \"value2\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]}] "
+ + "AS array UNWIND array AS object RETURN object";
+ result = interpreter.interpret(jsonListWithNullQuery, context);
+ assertEquals(Code.SUCCESS, result.code());
+ rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
+ assertEquals(rows.length, 3);
+ header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1));
+ assertEquals(header.contains(objectKey), true);
+ assertEquals(header.contains(objectListKey), true);
+ row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB, -1));
+ assertEquals(row.size(), header.size());
+ assertEquals(row.get(header.indexOf(objectKey)), "value");
+ assertEquals(row.get(header.indexOf(objectListKey)), StringUtils.EMPTY);
+ assertEquals(row.get(header.indexOf(objectListKey)), "");
+ row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB, -1));
+ assertEquals(row.size(), header.size());
+ assertEquals(row.get(header.indexOf(objectKey)), "value2");
+ assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
+
+ final String jsonListWithoutListKeyQuery = "WITH [{key: \"value\"},"
+ + "{key: \"value2\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]}] "
+ + "AS array UNWIND array AS object RETURN object";
+ result = interpreter.interpret(jsonListWithoutListKeyQuery, context);
+ assertEquals(Code.SUCCESS, result.code());
+ rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
+ assertEquals(rows.length, 3);
+ header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1));
+ assertEquals(header.contains(objectKey), true);
+ assertEquals(header.contains(objectListKey), true);
+ row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB, -1));
+ assertEquals(row.size(), header.size());
+ assertEquals(row.get(header.indexOf(objectKey)), "value");
+ assertEquals(row.get(header.indexOf(objectListKey)), StringUtils.EMPTY);
+ row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB, -1));
+ assertEquals(row.size(), header.size());
+ assertEquals(row.get(header.indexOf(objectKey)), "value2");
+ assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
+ }
+
+ @Test
+ public void testRenderNetwork() {
+ interpreter.open();
+ InterpreterResult result = interpreter.interpret("MATCH (n)-[r:KNOWS]-(m) RETURN n, r, m LIMIT 1", context);
+ GraphResult.Graph graph = gson.fromJson(result.toString().replace("%network ", StringUtils.EMPTY), GraphResult.Graph.class);
+ assertEquals(2, graph.getNodes().size());
+ assertEquals(true, graph.getNodes().iterator().next().getLabel().equals(LABEL_PERSON));
+ assertEquals(1, graph.getEdges().size());
+ assertEquals(true, graph.getEdges().iterator().next().getLabel().equals(REL_KNOWS));
+ assertEquals(1, graph.getLabels().size());
+ assertEquals(1, graph.getTypes().size());
+ assertEquals(true, graph.getLabels().containsKey(LABEL_PERSON));
+ assertEquals(REL_KNOWS, graph.getTypes().iterator().next());
+ assertEquals(Code.SUCCESS, result.code());
+ }
+
+ @Test
+ public void testFallingQuery() {
+ interpreter.open();
+ final String ERROR_MSG_EMPTY = "";
+ InterpreterResult result = interpreter.interpret(StringUtils.EMPTY, context);
+ assertEquals(Code.SUCCESS, result.code());
+ assertEquals(ERROR_MSG_EMPTY, result.toString());
+
+ result = interpreter.interpret(null, context);
+ assertEquals(Code.SUCCESS, result.code());
+ assertEquals(ERROR_MSG_EMPTY, result.toString());
+
+ result = interpreter.interpret("MATCH (n:Person{name: }) RETURN n.name AS name, n.age AS age", context);
+ assertEquals(Code.ERROR, result.code());
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index acfcd0572c0..7ff9acd7ae0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,7 @@
bigquery
alluxio
scio
+ neo4j
zeppelin-web
zeppelin-server
zeppelin-jupyter
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index 3a6d0aac881..42302169d23 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -217,6 +217,7 @@ The following components are provided under Apache License.
(Apache 2.0) frontend-maven-plugin 1.3 (com.github.eirslett:frontend-maven-plugin:1.3 - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE
(Apache 2.0) frontend-plugin-core 1.3 (com.github.eirslett:frontend-plugin-core) - https://github.com/eirslett/frontend-maven-plugin/blob/frontend-plugins-1.3/LICENSE
(Apache 2.0) mongo-java-driver 3.4.1 (org.mongodb:mongo-java-driver:3.4.1) - https://github.com/mongodb/mongo-java-driver/blob/master/LICENSE.txt
+ (Apache 2.0) Neo4j Java Driver (https://github.com/neo4j/neo4j-java-driver) - https://github.com/neo4j/neo4j-java-driver/blob/1.4.3/LICENSE.txt
========================================================================
MIT licenses
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/graph/GraphResult.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/graph/GraphResult.java
new file mode 100644
index 00000000000..df1b9a3ae6e
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/graph/GraphResult.java
@@ -0,0 +1,122 @@
+/*
+ * 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.interpreter.graph;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.tabledata.Node;
+import org.apache.zeppelin.tabledata.Relationship;
+
+import com.google.gson.Gson;
+
+/**
+ * The intepreter result template for Networks
+ *
+ */
+public class GraphResult extends InterpreterResult {
+
+ /**
+ * The Graph structure parsed from the front-end
+ *
+ */
+ public static class Graph {
+ private Collection nodes;
+
+ private Collection edges;
+
+ /**
+ * The node types in the whole graph, and the related colors
+ *
+ */
+ private Map labels;
+
+ /**
+ * The relationship types in the whole graph
+ *
+ */
+ private Set types;
+
+ /**
+ * Is a directed graph
+ */
+ private boolean directed;
+
+ public Graph() {}
+
+ public Graph(Collection nodes, Collection edges,
+ Map labels, Set types, boolean directed) {
+ super();
+ this.setNodes(nodes);
+ this.setEdges(edges);
+ this.setLabels(labels);
+ this.setTypes(types);
+ this.setDirected(directed);
+ }
+
+ public Collection getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(Collection nodes) {
+ this.nodes = nodes;
+ }
+
+ public Collection getEdges() {
+ return edges;
+ }
+
+ public void setEdges(Collection edges) {
+ this.edges = edges;
+ }
+
+ public Map getLabels() {
+ return labels;
+ }
+
+ public void setLabels(Map labels) {
+ this.labels = labels;
+ }
+
+ public Set getTypes() {
+ return types;
+ }
+
+ public void setTypes(Set types) {
+ this.types = types;
+ }
+
+ public boolean isDirected() {
+ return directed;
+ }
+
+ public void setDirected(boolean directed) {
+ this.directed = directed;
+ }
+
+ }
+
+ private static final Gson gson = new Gson();
+
+ public GraphResult(Code code, Graph graphObject) {
+ super(code, Type.NETWORK, gson.toJson(graphObject));
+ }
+
+}
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/GraphEntity.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/GraphEntity.java
new file mode 100644
index 00000000000..320b1447269
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/GraphEntity.java
@@ -0,0 +1,74 @@
+/*
+ * 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.tabledata;
+
+import java.util.Map;
+
+/**
+ * The base network entity
+ *
+ */
+public abstract class GraphEntity {
+
+ private long id;
+
+ /**
+ * The data of the entity
+ *
+ */
+ private Map data;
+
+ /**
+ * The primary type of the entity
+ */
+ private String label;
+
+ public GraphEntity() {}
+
+ public GraphEntity(long id, Map data, String label) {
+ super();
+ this.setId(id);
+ this.setData(data);
+ this.setLabel(label);
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public Map getData() {
+ return data;
+ }
+
+ public void setData(Map data) {
+ this.data = data;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+}
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Node.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Node.java
new file mode 100644
index 00000000000..2efabc40894
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Node.java
@@ -0,0 +1,49 @@
+/*
+ * 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.tabledata;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The Zeppelin Node Entity
+ *
+ */
+public class Node extends GraphEntity {
+
+ /**
+ * The labels (types) attached to a node
+ */
+ private Set labels;
+
+ public Node() {}
+
+
+ public Node(long id, Map data, Set labels) {
+ super(id, data, labels.iterator().next());
+ }
+
+ public Set getLabels() {
+ return labels;
+ }
+
+ public void setLabels(Set labels) {
+ this.labels = labels;
+ }
+
+}
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Relationship.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Relationship.java
new file mode 100644
index 00000000000..aa8ddb7854a
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/tabledata/Relationship.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.tabledata;
+
+import java.util.Map;
+
+/**
+ * The Zeppelin Relationship entity
+ *
+ */
+public class Relationship extends GraphEntity {
+
+ /**
+ * Source node ID
+ */
+ private long source;
+
+ /**
+ * End node ID
+ */
+ private long target;
+
+ public Relationship() {}
+
+ public Relationship(long id, Map data, long source,
+ long target, String label) {
+ super(id, data, label);
+ this.setSource(source);
+ this.setTarget(target);
+ }
+
+ public long getSource() {
+ return source;
+ }
+
+ public void setSource(long startNodeId) {
+ this.source = startNodeId;
+ }
+
+ public long getTarget() {
+ return target;
+ }
+
+ public void setTarget(long endNodeId) {
+ this.target = endNodeId;
+ }
+
+}
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 2dec19cbf56..6ccd272c910 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -623,7 +623,8 @@ public static enum ConfVars {
+ "org.apache.zeppelin.bigquery.BigQueryInterpreter,"
+ "org.apache.zeppelin.beam.BeamInterpreter,"
+ "org.apache.zeppelin.scio.ScioInterpreter,"
- + "org.apache.zeppelin.groovy.GroovyInterpreter"
+ + "org.apache.zeppelin.groovy.GroovyInterpreter,"
+ + "org.apache.zeppelin.neo4j.Neo4jCypherInterpreter"
),
ZEPPELIN_INTERPRETER_JSON("zeppelin.interpreter.setting", "interpreter-setting.json"),
ZEPPELIN_INTERPRETER_DIR("zeppelin.interpreter.dir", "interpreter"),
@@ -634,7 +635,7 @@ public static enum ConfVars {
ZEPPELIN_INTERPRETER_MAX_POOL_SIZE("zeppelin.interpreter.max.poolsize", 10),
ZEPPELIN_INTERPRETER_GROUP_ORDER("zeppelin.interpreter.group.order", "spark,md,angular,sh,"
+ "livy,alluxio,file,psql,flink,python,ignite,lens,cassandra,geode,kylin,elasticsearch,"
- + "scalding,jdbc,hbase,bigquery,beam,pig,scio,groovy"),
+ + "scalding,jdbc,hbase,bigquery,beam,pig,scio,groovy,neo4j"),
ZEPPELIN_INTERPRETER_OUTPUT_LIMIT("zeppelin.interpreter.output.limit", 1024 * 100),
ZEPPELIN_ENCODING("zeppelin.encoding", "UTF-8"),
ZEPPELIN_NOTEBOOK_DIR("zeppelin.notebook.dir", "notebook"),