You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metamodel.apache.org by to...@apache.org on 2015/11/28 11:32:44 UTC
metamodel git commit: Merging Neo4j module (query-only)
Repository: metamodel
Updated Branches:
refs/heads/master 137caf0d2 -> 0ddd0cdfe
Merging Neo4j module (query-only)
Project: http://git-wip-us.apache.org/repos/asf/metamodel/repo
Commit: http://git-wip-us.apache.org/repos/asf/metamodel/commit/0ddd0cdf
Tree: http://git-wip-us.apache.org/repos/asf/metamodel/tree/0ddd0cdf
Diff: http://git-wip-us.apache.org/repos/asf/metamodel/diff/0ddd0cdf
Branch: refs/heads/master
Commit: 0ddd0cdfe7b351b8a6589f265773268bb437c861
Parents: 137caf0
Author: Tomasz Guzialek <to...@guzialek.info>
Authored: Sat Nov 28 11:30:30 2015 +0100
Committer: Tomasz Guzialek <to...@guzialek.info>
Committed: Sat Nov 28 11:30:30 2015 +0100
----------------------------------------------------------------------
.travis.yml | 7 +
...del-integrationtest-configuration.properties | 12 +-
full/pom.xml | 5 +
hadoop/src/test/resources/log4j.xml | 40 +-
neo4j/.gitignore | 4 +
neo4j/pom.xml | 74 +++
.../neo4j/Neo4jCypherQueryBuilder.java | 162 ++++++
.../metamodel/neo4j/Neo4jDataContext.java | 356 ++++++++++++
.../apache/metamodel/neo4j/Neo4jDataSet.java | 77 +++
.../metamodel/neo4j/Neo4jRequestWrapper.java | 136 +++++
.../apache/metamodel/neo4j/package-info.java | 23 +
.../metamodel/neo4j/Neo4jDataContextTest.java | 545 +++++++++++++++++++
.../neo4j/Neo4jRequestWrapperTest.java | 172 ++++++
.../apache/metamodel/neo4j/Neo4jTestCase.java | 117 ++++
pom.xml | 34 +-
...del-integrationtest-configuration.properties | 7 +
16 files changed, 1743 insertions(+), 28 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index 1c9a38d..14f8734 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,13 @@ language: java
jdk:
- openjdk7
- oraclejdk8
+
+before_install:
+ # install Neo4j locally:
+ - wget dist.neo4j.org/neo4j-community-2.2.3-unix.tar.gz
+ - tar -xzf neo4j-community-2.2.3-unix.tar.gz
+ - sed -i.bak s/dbms.security.auth_enabled=true/dbms.security.auth_enabled=false/g neo4j-community-2.2.3/conf/neo4j-server.properties
+ - neo4j-community-2.2.3/bin/neo4j start
services:
- couchdb
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/example-metamodel-integrationtest-configuration.properties
----------------------------------------------------------------------
diff --git a/example-metamodel-integrationtest-configuration.properties b/example-metamodel-integrationtest-configuration.properties
index 8a2281d..83c5f4e 100644
--- a/example-metamodel-integrationtest-configuration.properties
+++ b/example-metamodel-integrationtest-configuration.properties
@@ -106,4 +106,14 @@
#cassandra.hostname=localhost
#cassandra.port=9042
-#cassandra.keyspace=my_keyspace
\ No newline at end of file
+#cassandra.keyspace=my_keyspace
+
+# ---------------------------
+# Neo4j module properties:
+# ---------------------------
+
+#neo4j.hostname=localhost
+#neo4j.port=7474
+#neo4j.username=neo4j
+#neo4j.password=neo4j
+#neo4j.serviceroot=/data/db
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/full/pom.xml
----------------------------------------------------------------------
diff --git a/full/pom.xml b/full/pom.xml
index 5835eef..867b61a 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -152,6 +152,11 @@ under the License.
</dependency>
<dependency>
<groupId>org.apache.metamodel</groupId>
+ <artifactId>MetaModel-neo4j</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.metamodel</groupId>
<artifactId>MetaModel-fixedwidth</artifactId>
<version>${project.version}</version>
</dependency>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/hadoop/src/test/resources/log4j.xml
----------------------------------------------------------------------
diff --git a/hadoop/src/test/resources/log4j.xml b/hadoop/src/test/resources/log4j.xml
index 29f497f..b1971c7 100644
--- a/hadoop/src/test/resources/log4j.xml
+++ b/hadoop/src/test/resources/log4j.xml
@@ -1,21 +1,21 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
-<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
-
- <appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
- <param name="Target" value="System.out" />
- <layout class="org.apache.log4j.PatternLayout">
- <param name="ConversionPattern" value="%-5p %d{HH:mm:ss} %c{1} - %m%n" />
- </layout>
- </appender>
-
- <logger name="org.apache.metamodel">
- <level value="info" />
- </logger>
-
- <root>
- <priority value="fatal" />
- <appender-ref ref="consoleAppender" />
- </root>
-
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out" />
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%-5p %d{HH:mm:ss} %c{1} - %m%n" />
+ </layout>
+ </appender>
+
+ <logger name="org.apache.metamodel">
+ <level value="info" />
+ </logger>
+
+ <root>
+ <priority value="fatal" />
+ <appender-ref ref="consoleAppender" />
+ </root>
+
</log4j:configuration>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/.gitignore
----------------------------------------------------------------------
diff --git a/neo4j/.gitignore b/neo4j/.gitignore
new file mode 100644
index 0000000..4e247ee
--- /dev/null
+++ b/neo4j/.gitignore
@@ -0,0 +1,4 @@
+/.settings
+/target
+/.classpath
+/.project
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/pom.xml
----------------------------------------------------------------------
diff --git a/neo4j/pom.xml b/neo4j/pom.xml
new file mode 100644
index 0000000..90ca1a9
--- /dev/null
+++ b/neo4j/pom.xml
@@ -0,0 +1,74 @@
+<?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">
+ <parent>
+ <artifactId>MetaModel</artifactId>
+ <groupId>org.apache.metamodel</groupId>
+ <version>4.4.2-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>MetaModel-neo4j</artifactId>
+ <name>MetaModel module for Neo4j databases</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.metamodel</groupId>
+ <artifactId>MetaModel-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-json-org</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-pool</groupId>
+ <artifactId>commons-pool</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-nop</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java
new file mode 100644
index 0000000..5d3ac6c
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jCypherQueryBuilder.java
@@ -0,0 +1,162 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.metamodel.query.FilterItem;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Table;
+
+public class Neo4jCypherQueryBuilder {
+
+ public static String buildSelectQuery(Table table, Column[] columns, int firstRow, int maxRows) {
+ String[] columnNames = new String[columns.length];
+ for (int i = 0; i < columns.length; i++) {
+ columnNames[i] = columns[i].getName();
+ }
+ return buildSelectQuery(table.getName(), columnNames, firstRow, maxRows);
+ }
+
+ public static String buildSelectQuery(String tableName, String[] columnNames, int firstRow, int maxRows) {
+ Map<String, String> returnClauseMap = new LinkedHashMap<>();
+ Map<String, Integer> relationshipIndexMap = new LinkedHashMap<>();
+ for (String columnName : columnNames) {
+ if (columnName.startsWith(Neo4jDataContext.RELATIONSHIP_PREFIX)) {
+ columnName = columnName.replace(Neo4jDataContext.RELATIONSHIP_PREFIX, "");
+
+ String relationshipName;
+ String relationshipPropertyName;
+
+ if (columnName.contains(Neo4jDataContext.RELATIONSHIP_COLUMN_SEPARATOR)) {
+ String[] parsedColumnNameArray = columnName.split(Neo4jDataContext.RELATIONSHIP_COLUMN_SEPARATOR);
+
+ relationshipName = parsedColumnNameArray[0];
+ relationshipPropertyName = parsedColumnNameArray[1];
+ } else {
+ relationshipName = columnName;
+ relationshipPropertyName = "metamodel_neo4j_relationship_marker";
+ }
+
+ String relationshipAlias;
+ if (relationshipIndexMap.containsKey(relationshipName)) {
+ relationshipAlias = "r" + relationshipIndexMap.get(relationshipName);
+ } else {
+ int nextIndex;
+ if (relationshipIndexMap.values().isEmpty()) {
+ nextIndex = 0;
+ } else {
+ nextIndex = Collections.max(relationshipIndexMap.values()) + 1;
+ }
+ relationshipIndexMap.put(relationshipName, nextIndex);
+ relationshipAlias = "r" + relationshipIndexMap.get(relationshipName);
+ }
+
+ if (relationshipPropertyName.equals("metamodel_neo4j_relationship_marker")) {
+ returnClauseMap.put(columnName, "id(" + relationshipAlias + "_relationshipEndNode)");
+ } else {
+ returnClauseMap.put(columnName, relationshipAlias + "." + relationshipPropertyName);
+ }
+ } else {
+ if (columnName.equals("_id")) {
+ returnClauseMap.put(columnName, "id(n)");
+ } else {
+ returnClauseMap.put(columnName, "n." + columnName);
+ }
+ }
+ }
+
+ StringBuilder cypherBuilder = new StringBuilder();
+ cypherBuilder.append("MATCH (n:");
+ cypherBuilder.append(tableName);
+ for (Map.Entry<String, Integer> relationshipAliasEntry : relationshipIndexMap.entrySet()) {
+ cypherBuilder.append(") OPTIONAL MATCH (n)-[r" + relationshipAliasEntry.getValue() + ":"
+ + relationshipAliasEntry.getKey() + "]->(r" + relationshipAliasEntry.getValue()
+ + "_relationshipEndNode");
+ }
+ cypherBuilder.append(") RETURN ");
+ boolean addComma = false;
+ for (Map.Entry<String, String> returnClauseEntry : returnClauseMap.entrySet()) {
+ if (addComma) {
+ cypherBuilder.append(",");
+ }
+ cypherBuilder.append(returnClauseEntry.getValue());
+ addComma = true;
+ }
+
+ if (firstRow > 1) {
+ cypherBuilder.append(" SKIP " + (firstRow - 1));
+ }
+ if (maxRows > -1) {
+ cypherBuilder.append(" LIMIT " + maxRows);
+ }
+ return cypherBuilder.toString();
+ }
+
+ public static String buildCountQuery(String tableName, List<FilterItem> whereItems) {
+ StringBuilder cypherBuilder = new StringBuilder();
+ cypherBuilder.append("MATCH (n:");
+ cypherBuilder.append(tableName);
+ cypherBuilder.append(") ");
+ cypherBuilder.append(buildWhereClause(whereItems, "n"));
+ cypherBuilder.append(" RETURN COUNT(*);");
+ return cypherBuilder.toString();
+ }
+
+ private static String buildWhereClause(List<FilterItem> whereItems, String queryObjectHandle) {
+ if ((whereItems != null) && (!whereItems.isEmpty())) {
+ StringBuilder whereClauseBuilder = new StringBuilder();
+ whereClauseBuilder.append("WHERE ");
+
+ FilterItem firstWhereItem = whereItems.get(0);
+ whereClauseBuilder.append(buildWhereClauseItem(firstWhereItem, queryObjectHandle));
+
+ for (int i = 1; i < whereItems.size(); i++) {
+ whereClauseBuilder.append(" AND ");
+ FilterItem whereItem = whereItems.get(i);
+ whereClauseBuilder.append(buildWhereClauseItem(whereItem, queryObjectHandle));
+ }
+
+ return whereClauseBuilder.toString();
+ } else {
+ return "";
+ }
+ }
+
+ private static String buildWhereClauseItem(FilterItem whereItem, String queryObjectHandle) {
+ StringBuilder whereClauseItemBuilder = new StringBuilder();
+ whereClauseItemBuilder.append(queryObjectHandle);
+ whereClauseItemBuilder.append(".");
+ whereClauseItemBuilder.append(whereItem.getSelectItem().getColumn().getName());
+ whereClauseItemBuilder.append(whereItem.getOperator().toSql());
+ final Object operand = whereItem.getOperand();
+ if (operand instanceof String) {
+ whereClauseItemBuilder.append("\"");
+ }
+ whereClauseItemBuilder.append(operand);
+ if (operand instanceof String) {
+ whereClauseItemBuilder.append("\"");
+ }
+ return whereClauseItemBuilder.toString();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java
new file mode 100644
index 0000000..568ad71
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataContext.java
@@ -0,0 +1,356 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.MetaModelException;
+import org.apache.metamodel.MetaModelHelper;
+import org.apache.metamodel.QueryPostprocessDataContext;
+import org.apache.metamodel.data.DataSet;
+import org.apache.metamodel.data.DocumentSource;
+import org.apache.metamodel.query.FilterItem;
+import org.apache.metamodel.query.SelectItem;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.MutableSchema;
+import org.apache.metamodel.schema.MutableTable;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+import org.apache.metamodel.schema.builder.DocumentSourceProvider;
+import org.apache.metamodel.util.SimpleTableDef;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DataContext implementation for Neo4j
+ */
+public class Neo4jDataContext extends QueryPostprocessDataContext implements DataContext, DocumentSourceProvider {
+
+ public static final Logger logger = LoggerFactory.getLogger(Neo4jDataContext.class);
+
+ public static final String SCHEMA_NAME = "neo4j";
+
+ public static final int DEFAULT_PORT = 7474;
+
+ public static final String RELATIONSHIP_PREFIX = "rel_";
+
+ public static final String RELATIONSHIP_COLUMN_SEPARATOR = "#";
+
+ private final SimpleTableDef[] _tableDefs;
+
+ private final Neo4jRequestWrapper _requestWrapper;
+
+ private final HttpHost _httpHost;
+
+ private String _serviceRoot = "/db/data";
+
+ public Neo4jDataContext(String hostname, int port, String username, String password, SimpleTableDef... tableDefs) {
+ _httpHost = new HttpHost(hostname, port);
+ final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+ _tableDefs = tableDefs;
+ }
+
+ public Neo4jDataContext(String hostname, int port, String username, String password, String serviceRoot,
+ SimpleTableDef... tableDefs) {
+ _httpHost = new HttpHost(hostname, port);
+ final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+ _tableDefs = tableDefs;
+ _serviceRoot = serviceRoot;
+ }
+
+ public Neo4jDataContext(String hostname, int port, String username, String password) {
+ _httpHost = new HttpHost(hostname, port);
+ final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+ _tableDefs = detectTableDefs();
+ }
+
+ public Neo4jDataContext(String hostname, int port, String username, String password, String serviceRoot) {
+ _httpHost = new HttpHost(hostname, port);
+ final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
+ _tableDefs = detectTableDefs();
+ _serviceRoot = serviceRoot;
+ }
+
+ public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient) {
+ _httpHost = new HttpHost(hostname, port);
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+ _tableDefs = detectTableDefs();
+ }
+
+ public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, String serviceRoot) {
+ _httpHost = new HttpHost(hostname, port);
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+ _tableDefs = detectTableDefs();
+ _serviceRoot = serviceRoot;
+ }
+
+ public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, SimpleTableDef... tableDefs) {
+ _httpHost = new HttpHost(hostname, port);
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+ _tableDefs = tableDefs;
+ }
+
+ public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, String serviceRoot,
+ SimpleTableDef... tableDefs) {
+ _httpHost = new HttpHost(hostname, port);
+ _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
+ _tableDefs = tableDefs;
+ _serviceRoot = serviceRoot;
+ }
+
+ @Override
+ protected String getDefaultSchemaName() throws MetaModelException {
+ return SCHEMA_NAME;
+ }
+
+ @Override
+ protected Schema getMainSchema() throws MetaModelException {
+ MutableSchema schema = new MutableSchema(getMainSchemaName());
+ for (SimpleTableDef tableDef : _tableDefs) {
+ MutableTable table = tableDef.toTable().setSchema(schema);
+ schema.addTable(table);
+ }
+ return schema;
+ }
+
+ @Override
+ protected String getMainSchemaName() throws MetaModelException {
+ return SCHEMA_NAME;
+ }
+
+ public SimpleTableDef[] detectTableDefs() {
+ List<SimpleTableDef> tableDefs = new ArrayList<SimpleTableDef>();
+
+ String labelsJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot + "/labels"));
+
+ JSONArray labelsJsonArray;
+ try {
+ labelsJsonArray = new JSONArray(labelsJsonString);
+ for (int i = 0; i < labelsJsonArray.length(); i++) {
+ String label = labelsJsonArray.getString(i);
+
+ List<JSONObject> nodesPerLabel = getAllNodesPerLabel(label);
+
+ List<String> propertiesPerLabel = new ArrayList<String>();
+ for (JSONObject node : nodesPerLabel) {
+ List<String> propertiesPerNode = getAllPropertiesPerNode(node);
+ for (String property : propertiesPerNode) {
+ if (!propertiesPerLabel.contains(property)) {
+ propertiesPerLabel.add(property);
+ }
+ }
+ }
+
+ List<String> relationshipPropertiesPerLabel = new ArrayList<String>();
+ for (JSONObject node : nodesPerLabel) {
+ Integer nodeId = (Integer) node.getJSONObject("metadata").get("id");
+ List<JSONObject> relationshipsPerNode = getOutgoingRelationshipsPerNode(nodeId);
+ for (JSONObject relationship : relationshipsPerNode) {
+ // Add the relationship as a column in the table
+ String relationshipName = relationship.getString("type");
+ String relationshipNameProperty = RELATIONSHIP_PREFIX + relationshipName;
+ if (!relationshipPropertiesPerLabel.contains(relationshipNameProperty)) {
+ relationshipPropertiesPerLabel.add(relationshipNameProperty);
+ }
+
+ // Add all the relationship properties as table columns
+ List<String> propertiesPerRelationship = getAllPropertiesPerRelationship(relationship);
+ relationshipPropertiesPerLabel.addAll(propertiesPerRelationship);
+ }
+ }
+ propertiesPerLabel.addAll(relationshipPropertiesPerLabel);
+
+ // Do not add a table if label has no nodes (empty tables are
+ // considered non-existent)
+ if (!nodesPerLabel.isEmpty()) {
+ SimpleTableDef tableDef = new SimpleTableDef(label,
+ propertiesPerLabel.toArray(new String[propertiesPerLabel.size()]));
+ tableDefs.add(tableDef);
+ }
+ }
+ return tableDefs.toArray(new SimpleTableDef[tableDefs.size()]);
+ } catch (JSONException e) {
+ logger.error("Error occured in parsing JSON while detecting the schema: ", e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private List<String> getAllPropertiesPerRelationship(JSONObject relationship) {
+ List<String> propertyNames = new ArrayList<String>();
+ try {
+ String relationshipName = RELATIONSHIP_PREFIX + relationship.getJSONObject("metadata").getString("type");
+ JSONObject relationshipPropertiesJSONObject = relationship.getJSONObject("data");
+ if (relationshipPropertiesJSONObject.length() > 0) {
+ JSONArray relationshipPropertiesNamesJSONArray = relationshipPropertiesJSONObject.names();
+ for (int i = 0; i < relationshipPropertiesNamesJSONArray.length(); i++) {
+ String propertyName = relationshipName + RELATIONSHIP_COLUMN_SEPARATOR
+ + relationshipPropertiesNamesJSONArray.getString(i);
+ if (!propertyNames.contains(propertyName)) {
+ propertyNames.add(propertyName);
+ }
+ }
+ }
+ return propertyNames;
+ } catch (JSONException e) {
+ logger.error("Error occured in parsing JSON while getting relationship properties: ", e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private List<JSONObject> getOutgoingRelationshipsPerNode(Integer nodeId) {
+ List<JSONObject> outgoingRelationshipsPerNode = new ArrayList<JSONObject>();
+
+ String outgoingRelationshipsPerNodeJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot
+ + "/node/" + nodeId + "/relationships/out"));
+
+ JSONArray outgoingRelationshipsPerNodeJsonArray;
+ try {
+ outgoingRelationshipsPerNodeJsonArray = new JSONArray(outgoingRelationshipsPerNodeJsonString);
+ for (int i = 0; i < outgoingRelationshipsPerNodeJsonArray.length(); i++) {
+ JSONObject relationship = outgoingRelationshipsPerNodeJsonArray.getJSONObject(i);
+ if (!outgoingRelationshipsPerNode.contains(relationship)) {
+ outgoingRelationshipsPerNode.add(relationship);
+ }
+ }
+ return outgoingRelationshipsPerNode;
+ } catch (JSONException e) {
+ logger.error("Error occured in parsing JSON while detecting outgoing relationships for node: " + nodeId, e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private List<JSONObject> getAllNodesPerLabel(String label) {
+ List<JSONObject> allNodesPerLabel = new ArrayList<JSONObject>();
+
+ String allNodesForLabelJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot + "/label/"
+ + label + "/nodes"));
+
+ JSONArray allNodesForLabelJsonArray;
+ try {
+ allNodesForLabelJsonArray = new JSONArray(allNodesForLabelJsonString);
+ for (int i = 0; i < allNodesForLabelJsonArray.length(); i++) {
+ JSONObject node = allNodesForLabelJsonArray.getJSONObject(i);
+ allNodesPerLabel.add(node);
+ }
+ return allNodesPerLabel;
+ } catch (JSONException e) {
+ logger.error("Error occured in parsing JSON while detecting the nodes for a label: " + label, e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private List<String> getAllPropertiesPerNode(JSONObject node) {
+ List<String> properties = new ArrayList<String>();
+ properties.add("_id");
+
+ String propertiesEndpoint;
+ try {
+ propertiesEndpoint = node.getString("properties");
+
+ String allPropertiesPerNodeJsonString = _requestWrapper.executeRestRequest(new HttpGet(propertiesEndpoint));
+
+ JSONObject allPropertiesPerNodeJsonObject = new JSONObject(allPropertiesPerNodeJsonString);
+ for (int j = 0; j < allPropertiesPerNodeJsonObject.length(); j++) {
+ JSONArray propertiesJsonArray = allPropertiesPerNodeJsonObject.names();
+ for (int k = 0; k < propertiesJsonArray.length(); k++) {
+ String property = propertiesJsonArray.getString(k);
+ properties.add(property);
+ }
+ }
+ return properties;
+ } catch (JSONException e) {
+ logger.error("Error occured in parsing JSON while detecting the properties of a node: " + node, e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int firstRow, int maxRows) {
+ if ((columns != null) && (columns.length > 0)) {
+ Neo4jDataSet dataSet = null;
+ try {
+ String selectQuery = Neo4jCypherQueryBuilder.buildSelectQuery(table, columns, firstRow, maxRows);
+ String responseJSONString = _requestWrapper.executeCypherQuery(selectQuery);
+ JSONObject resultJSONObject = new JSONObject(responseJSONString);
+ final SelectItem[] selectItems = MetaModelHelper.createSelectItems(columns);
+ dataSet = new Neo4jDataSet(selectItems, resultJSONObject);
+ } catch (JSONException e) {
+ logger.error("Error occured in parsing JSON while materializing the schema: ", e);
+ throw new IllegalStateException(e);
+ }
+
+ return dataSet;
+ } else {
+ logger.error("Encountered null or empty columns array for materializing main schema table.");
+ throw new IllegalArgumentException("Columns cannot be null or empty array");
+ }
+ }
+
+ @Override
+ protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int maxRows) {
+ return materializeMainSchemaTable(table, columns, 1, maxRows);
+ }
+
+ @Override
+ protected Number executeCountQuery(Table table, List<FilterItem> whereItems, boolean functionApproximationAllowed) {
+ String countQuery = Neo4jCypherQueryBuilder.buildCountQuery(table.getName(), whereItems);
+ String jsonResponse = _requestWrapper.executeCypherQuery(countQuery);
+
+ JSONObject jsonResponseObject;
+ try {
+ jsonResponseObject = new JSONObject(jsonResponse);
+ JSONArray resultsJSONArray = jsonResponseObject.getJSONArray("results");
+ JSONObject resultJSONObject = (JSONObject) resultsJSONArray.get(0);
+ JSONArray dataJSONArray = resultJSONObject.getJSONArray("data");
+ JSONObject rowJSONObject = (JSONObject) dataJSONArray.get(0);
+ JSONArray valueJSONArray = rowJSONObject.getJSONArray("row");
+ Number value = (Number) valueJSONArray.get(0);
+ return value;
+ } catch (JSONException e) {
+ logger.error("Error occured in parsing JSON response: ", e);
+ // Do not throw an exception here. Returning null here will make
+ // MetaModel attempt to count records manually and therefore recover
+ // from the error.
+ return null;
+ }
+ }
+
+ @Override
+ public DocumentSource getMixedDocumentSourceForSampling() {
+ return null;
+ }
+
+ @Override
+ public DocumentSource getDocumentSourceForTable(String sourceCollectionName) {
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java
new file mode 100644
index 0000000..bf132e8
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jDataSet.java
@@ -0,0 +1,77 @@
+/**
+ * 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.metamodel.neo4j;
+
+import org.apache.metamodel.data.AbstractDataSet;
+import org.apache.metamodel.data.DefaultRow;
+import org.apache.metamodel.data.Row;
+import org.apache.metamodel.data.SimpleDataSetHeader;
+import org.apache.metamodel.query.SelectItem;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+final class Neo4jDataSet extends AbstractDataSet {
+
+ private JSONObject _resultJSONObject;
+ private int _currentRowIndex;
+ private Row _row;
+
+ public Neo4jDataSet(SelectItem[] selectItems, JSONObject resultJSONObject) {
+ super(selectItems);
+ _resultJSONObject = resultJSONObject;
+ _currentRowIndex = 0;
+ }
+
+ @Override
+ public boolean next() {
+ try {
+ JSONArray resultsArray = _resultJSONObject.getJSONArray("results");
+ if (resultsArray.length() > 0) {
+ JSONObject results = resultsArray.getJSONObject(0);
+ JSONArray data = results.getJSONArray("data");
+ if (_currentRowIndex < data.length()) {
+ JSONObject row = data.getJSONObject(_currentRowIndex);
+ JSONArray jsonValues = row.getJSONArray("row");
+
+ Object[] objectValues = new Object[jsonValues.length()];
+ for (int i = 0; i < jsonValues.length(); i++) {
+ objectValues[i] = jsonValues.getString(i);
+ }
+ _row = new DefaultRow(new SimpleDataSetHeader(getSelectItems()), objectValues);
+ _currentRowIndex++;
+ return true;
+ }
+ } else {
+ JSONArray errorArray = _resultJSONObject.getJSONArray("errors");
+ JSONObject error = errorArray.getJSONObject(0);
+ throw new IllegalStateException(error.toString());
+ }
+ } catch (JSONException e) {
+ throw new IllegalStateException(e);
+ }
+ return false;
+ }
+
+ @Override
+ public Row getRow() {
+ return _row;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java
new file mode 100644
index 0000000..864a2b9
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/Neo4jRequestWrapper.java
@@ -0,0 +1,136 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.io.BaseEncoding;
+
+/**
+ * The class takes care of sending an {@link HttpRequestBase} or a Cypher query
+ * to the specified Neo4j instance. Also takes care of the authentication.
+ *
+ */
+public class Neo4jRequestWrapper {
+
+ private static final Logger logger = LoggerFactory.getLogger(Neo4jRequestWrapper.class);
+
+ private final CloseableHttpClient _httpClient;
+ private final HttpHost _httpHost;
+ private final HttpPost _cypherQueryHttpPost;
+ private final String _username;
+ private final String _password;
+
+ public Neo4jRequestWrapper(CloseableHttpClient httpClient, HttpHost httpHost, String username, String password,
+ String serviceRoot) {
+ _httpClient = httpClient;
+ _httpHost = httpHost;
+ _username = username;
+ _password = password;
+ _cypherQueryHttpPost = new HttpPost(serviceRoot + "/transaction/commit");
+ }
+
+ public Neo4jRequestWrapper(CloseableHttpClient httpClient, HttpHost httpHost, String serviceRoot) {
+ this(httpClient, httpHost, null, null, serviceRoot);
+ }
+
+ public String executeRestRequest(HttpRequestBase httpRequest) {
+ return executeRestRequest(httpRequest, _username, _password);
+ }
+
+ public String executeRestRequest(HttpRequestBase httpRequest, String username, String password) {
+ if ((username != null) && (password != null)) {
+ String base64credentials = BaseEncoding.base64().encode(
+ (username + ":" + password).getBytes(StandardCharsets.UTF_8));
+ httpRequest.addHeader("Authorization", "Basic " + base64credentials);
+ }
+
+ try {
+ CloseableHttpResponse response = _httpClient.execute(_httpHost, httpRequest);
+ if (response.getEntity() != null) {
+ return EntityUtils.toString(response.getEntity());
+ }
+ return null;
+ } catch (ClientProtocolException e) {
+ logger.error("An error occured while executing " + httpRequest, e);
+ throw new IllegalStateException(e);
+ } catch (IOException e) {
+ logger.error("An error occured while executing " + httpRequest, e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public String executeCypherQuery(String cypherQuery) {
+ JSONObject cypherQueryRequest = new JSONObject();
+ HashMap<String, String> statement = new HashMap<String, String>();
+ statement.put("statement", cypherQuery);
+
+ JSONArray statementsArray = new JSONArray();
+ statementsArray.put(statement);
+
+ return executeRequest(cypherQueryRequest, statementsArray);
+ }
+
+ public String executeCypherQueries(List<String> cypherQueries) {
+ JSONObject cypherQueryRequest = new JSONObject();
+ JSONArray statementsArray = new JSONArray();
+ for (String cypherQuery : cypherQueries) {
+ HashMap<String, String> statement = new HashMap<String, String>();
+ statement.put("statement", cypherQuery);
+
+ statementsArray.put(statement);
+ }
+
+ return executeRequest(cypherQueryRequest, statementsArray);
+ }
+
+ private String executeRequest(JSONObject cypherQueryRequest, JSONArray statementsArray) {
+ try {
+ cypherQueryRequest.put("statements", statementsArray);
+
+ String requestBody = cypherQueryRequest.toString();
+ _cypherQueryHttpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
+
+ String responseJSONString = executeRestRequest(_cypherQueryHttpPost);
+ return responseJSONString;
+ } catch (JSONException e) {
+ logger.error("Error occured while constructing JSON request body for " + _cypherQueryHttpPost, e);
+ throw new IllegalStateException(e);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java
----------------------------------------------------------------------
diff --git a/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java b/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java
new file mode 100644
index 0000000..55db480
--- /dev/null
+++ b/neo4j/src/main/java/org/apache/metamodel/neo4j/package-info.java
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+/**
+ * Module package for Neo4j support
+ */
+package org.apache.metamodel.neo4j;
+
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java
----------------------------------------------------------------------
diff --git a/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java
new file mode 100644
index 0000000..d4e3774
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jDataContextTest.java
@@ -0,0 +1,545 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.http.HttpHost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.metamodel.DataContext;
+import org.apache.metamodel.data.DataSet;
+import org.apache.metamodel.data.Row;
+import org.apache.metamodel.query.CompiledQuery;
+import org.apache.metamodel.schema.Column;
+import org.apache.metamodel.schema.Schema;
+import org.apache.metamodel.schema.Table;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Neo4jDataContextTest extends Neo4jTestCase {
+
+ private static final Logger logger = LoggerFactory.getLogger(Neo4jDataContextTest.class);
+
+ Neo4jRequestWrapper requestWrapper;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ if (isConfigured()) {
+ final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+
+ final HttpHost httpHost = new HttpHost(getHostname(), getPort());
+ requestWrapper = new Neo4jRequestWrapper(httpClient, httpHost, getUsername(), getPassword(),
+ getServiceRoot());
+ }
+ }
+
+ @Test
+ public void testTableDetection() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ // Insert a node
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 1, property2: 2 })");
+
+ // Adding a node, but deleting it afterwards - should not be included in
+ // the schema as the table would be empty
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabelTemp { property1: 3, property2: 4 })");
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitLabelTemp) DELETE n;");
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+ Schema schema = strategy.getSchemaByName(strategy.getDefaultSchemaName());
+
+ // Do not check the precise count, Neo4j keeps labels forever, there are
+ // probably many more than you imagine...
+ List<String> tableNames = Arrays.asList(schema.getTableNames());
+ logger.info("Tables (labels) detected: " + tableNames);
+ assertTrue(tableNames.contains("JUnitLabel"));
+ assertFalse(tableNames.contains("JUnitLabelTemp"));
+
+ Table table = schema.getTableByName("JUnitLabel");
+ List<String> columnNames = Arrays.asList(table.getColumnNames());
+ assertTrue(columnNames.contains("property1"));
+ assertTrue(columnNames.contains("property2"));
+ }
+
+ @Test
+ public void testTableDetectionWithRelationships() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ // Insert nodes
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+ Schema schema = strategy.getSchemaByName(strategy.getDefaultSchemaName());
+
+ // Do not check the precise count, Neo4j keeps labels forever, there are
+ // probably many more than you imagine...
+ List<String> tableNames = Arrays.asList(schema.getTableNames());
+ logger.info("Tables (labels) detected: " + tableNames);
+ assertTrue(tableNames.contains("JUnitPerson"));
+ assertTrue(tableNames.contains("JUnitBook"));
+
+ Table tablePerson = schema.getTableByName("JUnitPerson");
+ List<String> personColumnNames = Arrays.asList(tablePerson.getColumnNames());
+ assertEquals("[_id, name, age, rel_HAS_READ, rel_HAS_READ#rating, rel_HAS_BROWSED]",
+ personColumnNames.toString());
+
+ Table tableBook = schema.getTableByName("JUnitBook");
+ List<String> bookColumnNames = Arrays.asList(tableBook.getColumnNames());
+ assertEquals("[_id, title]", bookColumnNames.toString());
+ }
+
+ @Test
+ public void testSelectQueryWithProjection() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 1, property2: 2 })");
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ {
+ CompiledQuery query = strategy.query().from("JUnitLabel").select("property1").compile();
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ assertTrue(dataSet.next());
+ assertEquals("Row[values=[1]]", dataSet.getRow().toString());
+ assertFalse(dataSet.next());
+ }
+ }
+ {
+ CompiledQuery query = strategy.query().from("JUnitLabel").select("property1").select("property2").compile();
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ assertTrue(dataSet.next());
+ assertEquals("Row[values=[1, 2]]", dataSet.getRow().toString());
+ assertFalse(dataSet.next());
+ }
+ }
+ }
+
+ public void ignoredTestSelectQueryWithLargeDataset() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ int rowCount = 100000;
+
+ for (int j = 0; j < rowCount / 10000; j++) {
+ List<String> cypherQueries = new ArrayList<>();
+ for (int i = 0; i < 10000; i++) {
+ cypherQueries.add("CREATE (n:JUnitLabel { i: " + (j * 10000 + i) + "})");
+ }
+ requestWrapper.executeCypherQueries(cypherQueries);
+ }
+ System.out.println("Inserted " + rowCount + " rows to the database.");
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ {
+ CompiledQuery query = strategy.query().from("JUnitLabel").select("i").orderBy("i").compile();
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ for (int i = 0; i < rowCount; i++) {
+ assertTrue(dataSet.next());
+ }
+ assertFalse(dataSet.next());
+ }
+ }
+ }
+
+ @Test
+ public void testSelectQueryWithProjectionAndRelationships() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ // Insert nodes
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+ String bookNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitBook)"
+ + " WHERE n.title = 'Introduction to algorithms'" + " RETURN id(n);");
+ String bookNodeId = new JSONObject(bookNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+ .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ {
+ CompiledQuery query = strategy.query().from("JUnitPerson").select("name", "rel_HAS_READ").compile();
+ try (DataSet dataSet = strategy.executeQuery(query)) {
+ List<Row> rows = new ArrayList<>();
+ while (dataSet.next()) {
+ rows.add(dataSet.getRow());
+ }
+ // Sorting to have deterministic order
+ Collections.sort(rows, new Comparator<Row>() {
+
+ @Override
+ public int compare(Row arg0, Row arg1) {
+ return arg0.toString().compareTo(arg1.toString());
+ }
+ });
+ assertEquals(3, rows.size());
+ assertEquals("Row[values=[Helena, null]]", rows.get(0).toString());
+ assertEquals("Row[values=[Philomeena, null]]", rows.get(1).toString());
+ assertEquals("Row[values=[Tomasz, " + bookNodeId + "]]", rows.get(2).toString());
+ }
+ }
+ {
+ CompiledQuery query = strategy.query().from("JUnitPerson").select("rel_HAS_READ#rating").compile();
+ try (DataSet dataSet = strategy.executeQuery(query)) {
+ List<Row> rows = new ArrayList<>();
+ while (dataSet.next()) {
+ rows.add(dataSet.getRow());
+ }
+ // Sorting to have deterministic order
+ Collections.sort(rows, new Comparator<Row>() {
+
+ @Override
+ public int compare(Row arg0, Row arg1) {
+ return arg0.toString().compareTo(arg1.toString());
+ }
+ });
+
+ assertEquals(3, rows.size());
+ assertEquals("Row[values=[5]]", rows.get(0).toString());
+ assertEquals("Row[values=[null]]", rows.get(1).toString());
+ assertEquals("Row[values=[null]]", rows.get(2).toString());
+ }
+ }
+ }
+
+ @Test
+ public void testSelectAllQueryWithRelationships() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ // Insert nodes
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+ String bookNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitBook)"
+ + " WHERE n.title = 'Introduction to algorithms'" + " RETURN id(n);");
+ String bookNodeId = new JSONObject(bookNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+ .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+ String helenaNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)"
+ + " WHERE n.name = 'Helena'" + " RETURN id(n);");
+ String helenaNodeId = new JSONObject(helenaNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+ .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+ String tomaszNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)"
+ + " WHERE n.name = 'Tomasz'" + " RETURN id(n);");
+ String tomaszNodeId = new JSONObject(tomaszNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+ .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+ String philomeenaNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)"
+ + " WHERE n.name = 'Philomeena'" + " RETURN id(n);");
+ String philomeenaNodeId = new JSONObject(philomeenaNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+ .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ CompiledQuery query = strategy.query().from("JUnitPerson").selectAll().compile();
+ try (DataSet dataSet = strategy.executeQuery(query)) {
+ List<Row> rows = new ArrayList<>();
+ while (dataSet.next()) {
+ rows.add(dataSet.getRow());
+ }
+ // Sorting to have deterministic order
+ Collections.sort(rows, new Comparator<Row>() {
+
+ @Override
+ public int compare(Row arg0, Row arg1) {
+ return arg0.getValue(1).toString().compareTo(arg1.getValue(1).toString());
+ }
+ });
+ assertEquals(3, rows.size());
+ assertEquals("Row[values=[" + helenaNodeId + ", Helena, 100, null, null, null]]", rows.get(0).toString());
+ assertEquals("Row[values=[" + philomeenaNodeId + ", Philomeena, 18, null, null, " + bookNodeId + "]]", rows
+ .get(1).toString());
+ assertEquals("Row[values=[" + tomaszNodeId + ", Tomasz, 26, " + bookNodeId + ", 5, null]]", rows.get(2)
+ .toString());
+ }
+ }
+
+ @Test
+ public void testWhereClause() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 1, property2: 2 })");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { property1: 10, property2: 20 })");
+
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+ {
+ CompiledQuery query = strategy.query().from("JUnitLabel").select("property1").where("property2").eq(20)
+ .compile();
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ assertTrue(dataSet.next());
+ assertEquals("Row[values=[10]]", dataSet.getRow().toString());
+ assertFalse(dataSet.next());
+ }
+ }
+ {
+ CompiledQuery query = strategy.query().from("JUnitPerson").select("rel_HAS_READ#rating").where("name")
+ .eq("Tomasz").compile();
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ assertTrue(dataSet.next());
+ assertEquals("Row[values=[5]]", dataSet.getRow().toString());
+ assertFalse(dataSet.next());
+ }
+ }
+ {
+ CompiledQuery query = strategy.query().from("JUnitPerson").select("rel_HAS_READ#rating").where("name")
+ .eq("Philomeena").compile();
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ assertTrue(dataSet.next());
+ assertEquals("Row[values=[null]]", dataSet.getRow().toString());
+ assertFalse(dataSet.next());
+ }
+ }
+ }
+
+ @Test
+ public void testJoin() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel1 { id2: 1, propertyTable1: \"prop-table1-row1\" })");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel1 { id2: 2, propertyTable1: \"prop-table1-row2\" })");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel2 { id1: 2, propertyTable2: \"prop-table2-row2\" })");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel2 { id1: 1, propertyTable2: \"prop-table2-row1\" })");
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ Table table1 = strategy.getTableByQualifiedLabel("JUnitLabel1");
+ Table table2 = strategy.getTableByQualifiedLabel("JUnitLabel2");
+ Column id1Column = table2.getColumnByName("id1");
+ Column id2Column = table1.getColumnByName("id2");
+ Column propertyTable1Column = table1.getColumnByName("propertyTable1");
+ Column propertyTable2Column = table2.getColumnByName("propertyTable2");
+
+ CompiledQuery query = strategy.query().from(table1).and(table2)
+ .select(id1Column, id2Column, propertyTable1Column, propertyTable2Column).where(id1Column)
+ .eq(id2Column).compile();
+
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ List<Row> rows = new ArrayList<>();
+ while (dataSet.next()) {
+ rows.add(dataSet.getRow());
+ }
+ Collections.sort(rows, new Comparator<Row>() {
+
+ @Override
+ public int compare(Row o1, Row o2) {
+ return o1.toString().compareTo(o2.toString());
+ }
+ });
+ assertEquals(2, rows.size());
+ assertEquals("Row[values=[1, 1, prop-table1-row1, prop-table2-row1]]", rows.get(0).toString());
+ assertEquals("Row[values=[2, 2, prop-table1-row2, prop-table2-row2]]", rows.get(1).toString());
+ }
+ }
+
+ @Test
+ public void testJoinWithRelationships() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Tomasz', age: 26})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Philomeena', age: 18})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitPerson { name: 'Helena', age: 100})");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitBook { title: 'Introduction to algorithms'})");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Tomasz' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_READ { rating : 5 }]->(b)");
+ requestWrapper.executeCypherQuery("MATCH (a:JUnitPerson),(b:JUnitBook)"
+ + "WHERE a.name = 'Philomeena' AND b.title = 'Introduction to algorithms'"
+ + "CREATE (a)-[r:HAS_BROWSED]->(b)");
+
+ String bookNodeIdJSONObject = requestWrapper.executeCypherQuery("MATCH (n:JUnitBook)"
+ + " WHERE n.title = 'Introduction to algorithms'" + " RETURN id(n);");
+ String bookNodeId = new JSONObject(bookNodeIdJSONObject).getJSONArray("results").getJSONObject(0)
+ .getJSONArray("data").getJSONObject(0).getJSONArray("row").getString(0);
+
+ Neo4jDataContext strategy = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ Table table1 = strategy.getTableByQualifiedLabel("JUnitPerson");
+ Table table2 = strategy.getTableByQualifiedLabel("JUnitBook");
+ Column personNameColumn = table1.getColumnByName("name");
+ Column personHasReadColumn = table1.getColumnByName("rel_HAS_READ");
+ Column bookIdColumn = table2.getColumnByName("_id");
+ Column bookTitleColumn = table2.getColumnByName("title");
+
+ CompiledQuery query = strategy.query().from(table1).and(table2)
+ .select(personNameColumn, bookIdColumn, bookTitleColumn).where(personHasReadColumn).eq(bookIdColumn)
+ .compile();
+
+ try (final DataSet dataSet = strategy.executeQuery(query)) {
+ List<Row> rows = new ArrayList<>();
+ while (dataSet.next()) {
+ rows.add(dataSet.getRow());
+ }
+ assertEquals(1, rows.size());
+ assertEquals("Row[values=[Tomasz, " + bookNodeId + ", Introduction to algorithms]]", rows.get(0).toString());
+ }
+ }
+
+ @Test
+ public void testFirstRowAndLastRow() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ // insert a few records
+ {
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'John Doe', age: 30 })");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'Jane Doe', gender: 'F' })");
+ }
+
+ // create datacontext using detected schema
+ final DataContext dc = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ try (final DataSet ds = dc.query().from("JUnitLabel").select("name").and("age").firstRow(2).execute()) {
+ assertTrue("Class: " + ds.getClass().getName(), ds instanceof Neo4jDataSet);
+ assertTrue(ds.next());
+ final Row row = ds.getRow();
+ assertEquals("Row[values=[Jane Doe, null]]", row.toString());
+ assertFalse(ds.next());
+ }
+
+ try (final DataSet ds = dc.query().from("JUnitLabel").select("name").and("age").maxRows(1).execute()) {
+ assertTrue("Class: " + ds.getClass().getName(), ds instanceof Neo4jDataSet);
+ assertTrue(ds.next());
+ final Row row = ds.getRow();
+ assertEquals("Row[values=[John Doe, 30]]", row.toString());
+ assertFalse(ds.next());
+ }
+ }
+
+ @Test
+ public void testCountQuery() throws Exception {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ // insert a few records
+ {
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'John Doe', age: 30 })");
+ requestWrapper.executeCypherQuery("CREATE (n:JUnitLabel { name: 'Sofia Unknown', gender: 'F' })");
+ }
+
+ // create datacontext using detected schema
+ final DataContext dc = new Neo4jDataContext(getHostname(), getPort(), getUsername(), getPassword());
+
+ try (final DataSet ds = dc.query().from("JUnitLabel").selectCount().where("name").eq("John Doe").execute()) {
+ assertTrue(ds.next());
+ final Row row = ds.getRow();
+ assertFalse(ds.next());
+ assertEquals("Row[values=[1]]", row.toString());
+
+ }
+
+ try (final DataSet ds = dc.query().from("JUnitLabel").selectCount().execute()) {
+ assertTrue(ds.next());
+ final Row row = ds.getRow();
+ assertFalse(ds.next());
+ assertEquals("Row[values=[2]]", row.toString());
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (isConfigured()) {
+ // Delete the test nodes
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitLabel) DELETE n");
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitLabelTemp) DELETE n");
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitLabel1) DELETE n");
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitLabel2) DELETE n");
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson)-[r]-() DELETE n,r");
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitPerson) DELETE n");
+ requestWrapper.executeCypherQuery("MATCH (n:JUnitBook) DELETE n");
+ }
+
+ super.tearDown();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java
----------------------------------------------------------------------
diff --git a/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java
new file mode 100644
index 0000000..d7fa77f
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jRequestWrapperTest.java
@@ -0,0 +1,172 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.io.IOException;
+import java.io.StringBufferInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpVersion;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.junit.Test;
+
+import com.google.common.io.BaseEncoding;
+
+@SuppressWarnings("deprecation")
+public class Neo4jRequestWrapperTest extends Neo4jTestCase {
+
+ private class MockClosableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse {
+
+ public MockClosableHttpResponse(ProtocolVersion ver, int code, String reason) {
+ super(ver, code, reason);
+ }
+
+ @Override
+ public HttpEntity getEntity() {
+ BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
+ basicHttpEntity.setContent(new StringBufferInputStream("MockContent for BasicHttpEntity"));
+ return basicHttpEntity;
+ }
+
+ @Override
+ public void close() throws IOException {
+ // Do nothing
+ }
+
+ }
+
+ @Test
+ public void testCreateCypherQueryWithAuthentication() {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ CloseableHttpClient mockHttpClient = new CloseableHttpClient() {
+
+ @Override
+ public void close() throws IOException {
+ // Do nothing
+ }
+
+ @Override
+ public HttpParams getParams() {
+ // Do nothing
+ return null;
+ }
+
+ @Override
+ public ClientConnectionManager getConnectionManager() {
+ // Do nothing
+ return null;
+ }
+
+ @Override
+ protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context)
+ throws IOException, ClientProtocolException {
+ assertTrue(request instanceof HttpPost);
+ HttpPost httpPost = (HttpPost) request;
+
+ Header[] headers = httpPost.getHeaders("Authorization");
+ assertNotNull(headers);
+ assertEquals(1, headers.length);
+ String base64Encoded = headers[0].getValue();
+ base64Encoded = base64Encoded.replace("Basic ", "");
+ String decoded = new String(BaseEncoding.base64().decode(base64Encoded), StandardCharsets.UTF_8);
+ assertEquals("testUsername:testPassword", decoded);
+
+ assertEquals("{\"statements\":[{\"statement\":\"MATCH (n) RETURN n;\"}]}",
+ EntityUtils.toString(httpPost.getEntity()));
+
+ CloseableHttpResponse mockResponse = new MockClosableHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
+ return mockResponse;
+ }
+ };
+
+ Neo4jRequestWrapper wrapper = new Neo4jRequestWrapper(mockHttpClient, new HttpHost(getHostname(), getPort()),
+ "testUsername", "testPassword", getServiceRoot());
+ wrapper.executeCypherQuery("MATCH (n) RETURN n;");
+ // Assertions are in the HttpClient
+ }
+
+ @Test
+ public void testCreateCypherQueryWithoutAuthentication() {
+ if (!isConfigured()) {
+ System.err.println(getInvalidConfigurationMessage());
+ return;
+ }
+
+ CloseableHttpClient mockHttpClient = new CloseableHttpClient() {
+
+ @Override
+ public void close() throws IOException {
+ // Do nothing
+ }
+
+ @Override
+ public HttpParams getParams() {
+ // Do nothing
+ return null;
+ }
+
+ @Override
+ public ClientConnectionManager getConnectionManager() {
+ // Do nothing
+ return null;
+ }
+
+ @Override
+ protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context)
+ throws IOException, ClientProtocolException {
+ assertTrue(request instanceof HttpPost);
+ HttpPost httpPost = (HttpPost) request;
+
+ Header[] headers = httpPost.getHeaders("Authorization");
+ assertNotNull(headers);
+ assertEquals(0, headers.length);
+
+ assertEquals("{\"statements\":[{\"statement\":\"MATCH (n) RETURN n;\"}]}",
+ EntityUtils.toString(httpPost.getEntity()));
+
+ CloseableHttpResponse mockResponse = new MockClosableHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
+ return mockResponse;
+ }
+ };
+
+ Neo4jRequestWrapper wrapper = new Neo4jRequestWrapper(mockHttpClient, new HttpHost(getHostname(), getPort()),
+ getServiceRoot());
+ wrapper.executeCypherQuery("MATCH (n) RETURN n;");
+ // Assertions are in the HttpClient
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java
----------------------------------------------------------------------
diff --git a/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java
new file mode 100644
index 0000000..e3353e4
--- /dev/null
+++ b/neo4j/src/test/java/org/apache/metamodel/neo4j/Neo4jTestCase.java
@@ -0,0 +1,117 @@
+/**
+ * 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.metamodel.neo4j;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public abstract class Neo4jTestCase extends TestCase {
+
+ private String _hostname;
+ private int _port = Neo4jDataContext.DEFAULT_PORT;
+ private String _username;
+ private String _password;
+ private String _serviceRoot = "/db/data";
+ private boolean _configured;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ File file = new File(getPropertyFilePath());
+ if (file.exists()) {
+ loadPropertyFile(file);
+ } else {
+ // Continuous integration case
+ if (System.getenv("CONTINUOUS_INTEGRATION") != null) {
+ File travisFile = new File("../travis-metamodel-integrationtest-configuration.properties");
+ if (travisFile.exists()) {
+ loadPropertyFile(travisFile);
+ } else {
+ _configured = false;
+ }
+ } else {
+ _configured = false;
+ }
+ }
+ }
+
+ private String getPropertyFilePath() {
+ String userHome = System.getProperty("user.home");
+ return userHome + "/metamodel-integrationtest-configuration.properties";
+ }
+
+ protected String getInvalidConfigurationMessage() {
+ return "!!! WARN !!! Neo4j module ignored\n" + "Please configure Neo4j connection locally ("
+ + getPropertyFilePath() + "), to run integration tests";
+ }
+
+ private void loadPropertyFile(File file) throws IOException, FileNotFoundException {
+ Properties properties = new Properties();
+ properties.load(new FileReader(file));
+ _hostname = properties.getProperty("neo4j.hostname");
+ String portString = properties.getProperty("neo4j.port");
+ if (portString != null) {
+ _port = Integer.parseInt(portString);
+ }
+ _username = properties.getProperty("neo4j.username");
+ _password = properties.getProperty("neo4j.password");
+ String serviceRoot = properties.getProperty("neo4j.serviceroot");
+ if (serviceRoot != null) {
+ _serviceRoot = serviceRoot;
+ }
+
+ _configured = (_hostname != null && !_hostname.isEmpty());
+
+ if (_configured) {
+ System.out.println("Loaded Neo4j configuration. Hostname=" + _hostname + ", port=" + _port + ", username="
+ + _username + ", _serviceRoot=" + _serviceRoot);
+ }
+ }
+
+ public boolean isConfigured() {
+ return _configured;
+ }
+
+ public String getHostname() {
+ return _hostname;
+ }
+
+ public int getPort() {
+ return _port;
+ }
+
+ public String getUsername() {
+ return _username;
+ }
+
+ public String getPassword() {
+ return _password;
+ }
+
+ public String getServiceRoot() {
+ return _serviceRoot;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ce1e4d6..be4763e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,8 @@ 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">
+<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>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -27,7 +28,7 @@ under the License.
<junit.version>4.11</junit.version>
<guava.version>16.0.1</guava.version>
<hadoop.version>2.6.0</hadoop.version>
- <jackson.version>2.4.6</jackson.version>
+ <jackson.version>2.6.3</jackson.version>
<easymock.version>3.2</easymock.version>
<httpcomponents.version>4.4.1</httpcomponents.version>
<checksum-maven-plugin.version>1.2</checksum-maven-plugin.version>
@@ -76,6 +77,7 @@ under the License.
<module>sugarcrm</module>
<module>full</module>
<module>spring</module>
+ <module>neo4j</module>
</modules>
<issueManagement>
<system>Jira</system>
@@ -273,14 +275,15 @@ under the License.
<excludes>
<exclude>commons-logging:commons-logging:compile</exclude>
<exclude>org.codehaus.jackson:compile</exclude>
-
- <!-- commons-beanutils-core is redundant when we already depend on commons-beanutils -->
+
+ <!-- commons-beanutils-core is redundant when we already depend
+ on commons-beanutils -->
<exclude>commons-beanutils:commons-beanutils-core:*</exclude>
-
+
<!-- stax-api is overlapping with xml-apis -->
<exclude>stax:stax-api:*</exclude>
<exclude>javax.xml.stream:stax-api</exclude>
-
+
<!-- findbugs-annotations is overlapping with annotations -->
<exclude>com.github.stephenc.findbugs:findbugs-annotations:*</exclude>
</excludes>
@@ -354,7 +357,8 @@ under the License.
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<licenses>
- <license implementation="org.apache.rat.analysis.license.SimplePatternBasedLicense">
+ <license
+ implementation="org.apache.rat.analysis.license.SimplePatternBasedLicense">
<licenseFamilyCategory>ASL20</licenseFamilyCategory>
<licenseFamilyName>Apache Software License, 2.0</licenseFamilyName>
<notes>Single licensed ASL v2.0</notes>
@@ -388,6 +392,7 @@ under the License.
<exclude>**/tattletale-filters.properties</exclude>
<exclude>DEPENDENCIES</exclude>
<exclude>DISCLAIMER</exclude>
+ <exclude>neo4j-community-*/**</exclude>
</excludes>
</configuration>
</plugin>
@@ -512,6 +517,21 @@ under the License.
<version>${jackson.version}</version>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-json-org</artifactId>
+ <version>${jackson.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-core-asl</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.10</version>
http://git-wip-us.apache.org/repos/asf/metamodel/blob/0ddd0cdf/travis-metamodel-integrationtest-configuration.properties
----------------------------------------------------------------------
diff --git a/travis-metamodel-integrationtest-configuration.properties b/travis-metamodel-integrationtest-configuration.properties
index 9b52f5d..f551fef 100644
--- a/travis-metamodel-integrationtest-configuration.properties
+++ b/travis-metamodel-integrationtest-configuration.properties
@@ -26,6 +26,13 @@ mongodb.collectionName=my_collection
#hbase.zookeeper.hostname=localhost
#hbase.zookeeper.port=2181
+# ------------------------
+# Neo4j module properties:
+# ------------------------
+
+neo4j.hostname=localhost
+neo4j.port=7474
+
# -----------------------
# JDBC module properties:
# -----------------------