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 <[email protected]> Authored: Sat Nov 28 11:30:30 2015 +0100 Committer: Tomasz Guzialek <[email protected]> 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: # -----------------------
