diff --git a/.github/actions/test-gradle-project/action.yml b/.github/actions/test-gradle-project/action.yml
index 52a149ef26..bc39f2a6f7 100644
--- a/.github/actions/test-gradle-project/action.yml
+++ b/.github/actions/test-gradle-project/action.yml
@@ -12,10 +12,10 @@ runs:
- uses: ./.github/actions/setup-gradle-cache
- name: Run compile tests
shell: bash
- run: ./gradlew :${{inputs.project-name}}:compileJava :${{inputs.project-name}}:compileTestJava
+ run: ./gradlew --info -Pneo4jVersionOverride=$NEO4JVERSION :${{inputs.project-name}}:compileJava :${{inputs.project-name}}:compileTestJava
- name: Run tests
shell: bash
- run: ./gradlew :${{inputs.project-name}}:check --parallel
+ run: ./gradlew --info -Pneo4jVersionOverride=$NEO4JVERSION :${{inputs.project-name}}:check --parallel
- name: Archive test results
uses: actions/upload-artifact@v2
if: always()
diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml
index 9d43e4b095..4ac3396f0c 100644
--- a/.github/workflows/CI.yaml
+++ b/.github/workflows/CI.yaml
@@ -2,11 +2,13 @@ name: CI
on:
push:
- branches: [ "dev" ]
+ branches: [ "5.23" ]
pull_request:
- branches: [ "dev" ]
+ branches: [ "5.23" ]
env:
+ CODEARTIFACT_DOWNLOAD_URL: ${{ secrets.CODEARTIFACT_DOWNLOAD_URL }}
+ CODEARTIFACT_USERNAME: ${{ secrets.CODEARTIFACT_USERNAME }}
DOCKER_ENTERPRISE_URL: ${{ secrets.DOCKER_ENTERPRISE_DEV_URL }}
DOCKER_COMMUNITY_URL: ${{ secrets.DOCKER_COMMUNITY_DEV_URL }}
TEAMCITY_DEV_URL: ${{ secrets.TEAMCITY_DEV_URL }}
@@ -14,7 +16,6 @@ env:
TEAMCITY_PASSWORD: ${{ secrets.TEAMCITY_PASSWORD }}
ENTERPRISE_TAR: enterprise-docker.tar
COMMUNITY_TAR: community-docker.tar
-
jobs:
code-ql:
@@ -28,23 +29,43 @@ jobs:
matrix:
language: [ 'java', 'javascript' ]
steps:
+ - name: Configure AWS CLI
+ uses: aws-actions/configure-aws-credentials@v2
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: eu-west-1
+
+ - name: Configure CodeArtifact Authentication Token
+ run: |
+ CODEARTIFACT_TOKEN=`aws codeartifact get-authorization-token --domain build-service-live --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} --query authorizationToken --output text`
+ echo "::add-mask::$CODEARTIFACT_TOKEN"
+ echo "CODEARTIFACT_TOKEN=$CODEARTIFACT_TOKEN" >> "$GITHUB_ENV"
+
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-jdk
- uses: ./.github/actions/setup-gradle-cache
+
+ - name: Determine latest neo4j CI version
+ run: |
+ neo4j_version_base=$(grep -e "neo4jVersion = .*" build.gradle | cut -d '=' -f 2 | tr -d \'\" | tr -d ' ')
+ echo "neo4j_version_base=$neo4j_version_base"
+ NEO4JVERSION=`aws codeartifact list-package-versions --domain build-service-live --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} --repository ci-live --format maven --namespace org.neo4j --package neo4j --sort-by PUBLISHED_TIME --query "versions[?starts_with(version,'$neo4j_version_base')] | [0].version" | tr -d '" '`
+ echo "NEO4JVERSION=$NEO4JVERSION" >> "$GITHUB_ENV"
+ echo "Found NEO4j_VERSION_CI=$NEO4JVERSION"
- name: Compile Java
run: |
chmod +x gradlew
- ./gradlew --no-daemon --init-script init.gradle clean
+ ./gradlew --no-daemon --info -Pneo4jVersionOverride=$NEO4JVERSION --init-script init.gradle clean
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- # Autobuild attempts to build any compiled languages
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ - name: Compile
+ run: ./gradlew --info -Pneo4jVersionOverride=$NEO4JVERSION compileJava compileTestJava
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
@@ -58,6 +79,19 @@ jobs:
project: [ 'extended', 'extended-it' ]
runs-on: ubuntu-latest
steps:
+ - name: Configure AWS CLI
+ uses: aws-actions/configure-aws-credentials@v2
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: eu-west-1
+
+ - name: Configure CodeArtifact Authentication Token
+ run: |
+ CODEARTIFACT_TOKEN=`aws codeartifact get-authorization-token --domain build-service-live --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} --query authorizationToken --output text`
+ echo "::add-mask::$CODEARTIFACT_TOKEN"
+ echo "CODEARTIFACT_TOKEN=$CODEARTIFACT_TOKEN" >> "$GITHUB_ENV"
+
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
@@ -70,11 +104,19 @@ jobs:
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+
+ - name: Determine latest neo4j CI version
+ run: |
+ neo4j_version_base=$(grep -e "neo4jVersion = .*" build.gradle | cut -d '=' -f 2 | tr -d \'\" | tr -d ' ')
+ echo "neo4j_version_base=$neo4j_version_base"
+ NEO4JVERSION=`aws codeartifact list-package-versions --domain build-service-live --domain-owner ${{ secrets.AWS_ACCOUNT_ID }} --repository ci-live --format maven --namespace org.neo4j --package neo4j --sort-by PUBLISHED_TIME --query "versions[?starts_with(version,'$neo4j_version_base')] | [0].version" | tr -d '" '`
+ echo "NEO4JVERSION=$NEO4JVERSION" >> "$GITHUB_ENV"
+ echo "Found NEO4j_VERSION_CI=$NEO4JVERSION"
- name: Init gradle
run: |
chmod +x gradlew
- ./gradlew --init-script init.gradle
+ ./gradlew --info -Pneo4jVersionOverride=$NEO4JVERSION --init-script init.gradle
- name: Run ${{ matrix.project }} tests
uses: ./.github/actions/test-gradle-project
diff --git a/.gitmodules b/.gitmodules
index c98aabddc7..ed40549eee 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,4 +1,4 @@
[submodule "apoc-core"]
path = apoc-core
url = https://github.com/neo4j/apoc
- branch = 5.22
+ branch = 5.23
diff --git a/apoc-core b/apoc-core
index 5d16a544e9..656ddb21ec 160000
--- a/apoc-core
+++ b/apoc-core
@@ -1 +1 @@
-Subproject commit 5d16a544e917b8dd0c52cfeedb3fe41cd5b8063c
+Subproject commit 656ddb21ec4e00d4d640275b928948c475f7d5b5
diff --git a/build.gradle b/build.gradle
index fc699da9f8..5331ec2239 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ downloadLicenses {
allprojects {
group = 'org.neo4j.procedure'
- version = '5.22.0'
+ version = '5.23.0'
archivesBaseName = 'apoc'
description = """neo4j-apoc-procedures"""
}
@@ -28,7 +28,17 @@ repositories {
/*maven { // this contains the neo4j 4.0.0-beta jars
url "https://neo4j.bintray.com/community/"
}*/
- mavenCentral()
+ if (System.getenv("CODEARTIFACT_DOWNLOAD_URL") ?: "" != "") {
+ maven {
+ url System.getenv('CODEARTIFACT_DOWNLOAD_URL')
+ credentials {
+ username System.getenv('CODEARTIFACT_USERNAME')
+ password System.getenv('CODEARTIFACT_TOKEN')
+ }
+ }
+ } else {
+ mavenCentral()
+ }
maven {
url "https://repo.gradle.org/gradle/libs-releases"
}
@@ -43,7 +53,17 @@ subprojects {
/*maven { // this contains the neo4j 4.0.0-beta jars
url "https://neo4j.bintray.com/community/"
}*/
- mavenCentral()
+ if (System.getenv("CODEARTIFACT_DOWNLOAD_URL") ?: "" != "") {
+ maven {
+ url System.getenv('CODEARTIFACT_DOWNLOAD_URL')
+ credentials {
+ username System.getenv('CODEARTIFACT_USERNAME')
+ password System.getenv('CODEARTIFACT_TOKEN')
+ }
+ }
+ } else {
+ mavenCentral()
+ }
maven {
url "https://repo.gradle.org/gradle/libs-releases"
}
@@ -71,8 +91,8 @@ subprojects {
// neo4jDockerImage system property is used in TestContainerUtil
systemProperties 'user.language' : 'en' ,
'user.country' : 'US',
- 'neo4jDockerImage' : System.getProperty("NEO4JVERSION") ? 'neo4j:' + System.getProperty("NEO4JVERSION") + '-enterprise-debian' : 'neo4j:5.22.0-enterprise',
- 'neo4jCommunityDockerImage': System.getProperty("NEO4JVERSION") ? 'neo4j:' + System.getProperty("NEO4JVERSION") + '-debian' : 'neo4j:5.22.0',
+ 'neo4jDockerImage' : System.getProperty("NEO4JVERSION") ? 'neo4j:' + System.getProperty("NEO4JVERSION") + '-enterprise-debian' : 'neo4j:5.23.0-enterprise',
+ 'neo4jCommunityDockerImage': System.getProperty("NEO4JVERSION") ? 'neo4j:' + System.getProperty("NEO4JVERSION") + '-debian' : 'neo4j:5.23.0',
'coreDir': 'apoc-core/core',
'testDockerBundle': false
@@ -131,7 +151,7 @@ subprojects {
ext {
// NB: due to version.json generation by parsing this file, the next line must not have any if/then/else logic
- neo4jVersion = "5.22.0"
+ neo4jVersion = "5.23.0"
// instead we apply the override logic here
neo4jVersionEffective = project.hasProperty("neo4jVersionOverride") ? project.getProperty("neo4jVersionOverride") : neo4jVersion
testContainersVersion = '1.18.3'
diff --git a/docs/antora/publish.yml b/docs/antora/publish.yml
index 747ba218db..6bc5971b05 100644
--- a/docs/antora/publish.yml
+++ b/docs/antora/publish.yml
@@ -6,7 +6,7 @@ site:
content:
sources:
- url: https://github.com/neo4j-contrib/neo4j-apoc-procedures
- branches: ['5.20', '4.4', '4.3', '4.2', '4.1', '4.0']
+ branches: ['5.23', '4.4', '4.3', '4.2', '4.1', '4.0']
start_path: docs/asciidoc
exclude:
- '!**/_includes/*'
diff --git a/docs/asciidoc/antora.yml b/docs/asciidoc/antora.yml
index 18e590048b..59f6f307d3 100644
--- a/docs/asciidoc/antora.yml
+++ b/docs/asciidoc/antora.yml
@@ -8,4 +8,4 @@ asciidoc:
docs-version: "5.0"
branch: "5.0"
apoc-release-absolute: "5.0"
- apoc-release: "5.22.0"
+ apoc-release: "5.23.0"
diff --git a/docs/asciidoc/modules/ROOT/nav.adoc b/docs/asciidoc/modules/ROOT/nav.adoc
index ef3ac084a5..5fe025da14 100644
--- a/docs/asciidoc/modules/ROOT/nav.adoc
+++ b/docs/asciidoc/modules/ROOT/nav.adoc
@@ -26,6 +26,8 @@ include::partial$generated-documentation/nav.adoc[]
** xref::import/load-csv.adoc[]
** xref::import/xls.adoc[]
** xref::import/html.adoc[]
+ ** xref::import/parquet.adoc[]
+ ** xref::import/gexf.adoc[]
* xref:export/index.adoc[]
** xref::export/xls.adoc[]
diff --git a/docs/asciidoc/modules/ROOT/pages/import/gexf.adoc b/docs/asciidoc/modules/ROOT/pages/import/gexf.adoc
new file mode 100644
index 0000000000..bf2b773fba
--- /dev/null
+++ b/docs/asciidoc/modules/ROOT/pages/import/gexf.adoc
@@ -0,0 +1,294 @@
+[[gexf]]
+= Load GEXF (Graph Exchange XML Format)
+:description: This section describes procedures that can be used to import data from GEXF files.
+
+
+
+Many existing applications and data integrations use GEXF to describes a graph with nodes and relationships.
+For further information, you should visit the https://gexf.net/[official documentation].
+
+It is possible to load or import nodes and relationship from a GEXF file with the procedures
+ `apoc.load.gexf` and `apoc.import.gexf`. You need to:
+
+* provide a path to a GEXF file
+* provide configuration (optional)
+
+The `apoc.import.gexf` read as the `apoc.load.gexf` but also create nodes and relationships in Neo4j.
+
+For reading from files you'll have to enable the config option:
+
+----
+apoc.import.file.enabled=true
+----
+
+By default file paths are global, for paths relative to the `import` directory set:
+
+----
+apoc.import.file.use_neo4j_config=true
+----
+
+== Examples for apoc.load.gexf
+
+.load.gexf
+----
+
+
+
+
+
+
+
+
+
+
+
+
+----
+
+[source, cypher]
+----
+CALL apoc.load.gexf('load.gexf')
+----
+
+.Results
+[opts="header"]
+|===
+| value
+| {_type: gexf, _children: [{_type: graph, defaultedgetype: directed, _children: [{_type: nodes, _children: [{_type: node, _children: [{_type: attvalues, _children: [{_type: attvalue, for: 0, value: http://gephi.org}]}], foo: bar}]}]}], version: 1.2}
+|===
+
+
+With a malformed GEXF file, like the following one:
+
+----
+
+
+
+ Gephi.org
+ A Web network
+
+
+
+
+
+
+
+
+
+
+
+----
+
+we get the following error:
+```
+[Fatal Error] :9:9: The element type "attributes" must be terminated by the matching end-tag "".
+```
+
+== Examples for apoc.import.gexf
+
+Besides the file you can pass in a config map:
+
+.Config parameters
+[opts=header]
+|===
+| name | type | default | description
+| readLabels | Boolean | false | Creates node labels based on the value in the `labels` property of `node` elements
+| defaultRelationshipType | String | RELATED | The default relationship type to use if none is specified in the GraphML file
+| storeNodeIds | Boolean | false | store the `id` property of `node` elements
+| batchSize | Integer | 20000 | The number of elements to process per transaction
+| compression | `Enum[NONE, BYTES, GZIP, BZIP2, DEFLATE, BLOCK_LZ4, FRAMED_SNAPPY]` | `null` | Allow taking binary data, either not compressed (value: `NONE`) or compressed (other values)
+| source | Map | Empty map | See `source / target config` parameter below
+| target | Map | Empty map | See `source / target config` parameter below
+See the xref::overview/apoc.load/apoc.load.csv.adoc#_binary_file[Binary file example]
+|===
+
+
+With the following file will be created:
+
+* 1 node with label Gephi
+* 2 nodes with label Webatlas
+* 1 node with label RTGI
+* 1 node with label BarabasiLab
+* 6 relationships of kind KNOWS
+* 1 relationship of kind HAS_TICKET
+* 1 relationship of kind BAZ
+
+.data.gexf
+----
+
+
+
+ Gephi.org
+ A Web network
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+----
+
+[source, cypher]
+----
+CALL apoc.import.gexf('data.gexf', {readLabels:true})
+----
+
+.Results
+[opts="header"]
+|===
+| value
+| {
+"relationships" : 8,
+"batches" : 0,
+"file" : "file:/../data.gexf",
+"nodes" : 5,
+"format" : "gexf",
+"source" : "file",
+"time" : 9736,
+"rows" : 0,
+"batchSize" : -1,
+"done" : true,
+"properties" : 21
+}
+|===
+
+We can also store the node IDs by executing:
+[source, cypher]
+----
+CALL apoc.import.gexf('data.gexf', {readLabels:true, storeNodeIds: true})
+----
+
+
+With a malformed GEXF file, like the following one:
+
+----
+
+
+
+ Gephi.org
+ A Web network
+
+
+
+
+
+
+
+
+
+
+
+----
+
+we get the following result, without nodes, relationships and properties imported:
+
+.Results
+[opts="header"]
+|===
+| value
+| {
+"relationships" : 8,
+"batches" : 0,
+"file" : "file:/../malformed.gexf",
+"nodes" : 0,
+"format" : "gexf",
+"source" : "file",
+"time" : 9736,
+"rows" : 0,
+"batchSize" : -1,
+"done" : true,
+"properties" : 0
+}
+|===
+
+
+
+=== source / target config
+
+Allows the import of relations in case the source and / or target nodes are not present in the file, searching for nodes via a custom label and property.
+To do this, we can insert into the config map `source: {label: '', id: `''`}` and/or `source: {label: '', id: `''`}`
+In this way, we can search start and end nodes via the source and end attribute of `edge` tag.
+
+For example, with a config map `{source: {id: 'myId', label: 'Foo'}, target: {id: 'other', label: 'Bar'}}`
+with a edge row like `KNOWS`
+we search a source node `(:Foo {myId: 'n0'})` and an end node `(:Bar {other: 'n1'})`.
+The id key is optional (the default is `'id'`).
+
+
+
+
diff --git a/docs/asciidoc/modules/ROOT/pages/import/index.adoc b/docs/asciidoc/modules/ROOT/pages/import/index.adoc
index 8b645759e4..732f3dbd39 100644
--- a/docs/asciidoc/modules/ROOT/pages/import/index.adoc
+++ b/docs/asciidoc/modules/ROOT/pages/import/index.adoc
@@ -13,3 +13,4 @@ For more information on these procedures, see:
* xref::import/xls.adoc[]
* xref::import/html.adoc[]
* xref::import/parquet.adoc[]
+* xref::import/gexf.adoc[]
diff --git a/docs/asciidoc/modules/ROOT/partials/docker.adoc b/docs/asciidoc/modules/ROOT/partials/docker.adoc
index c3cfb67b33..dddcc8e0d1 100644
--- a/docs/asciidoc/modules/ROOT/partials/docker.adoc
+++ b/docs/asciidoc/modules/ROOT/partials/docker.adoc
@@ -1,17 +1,17 @@
-=== Using APOC with the Neo4j Docker image
+=== Using APOC Extended with the Neo4j Docker image
// tag::docker[]
APOC Extended can be used with the https://hub.docker.com/_/neo4j/[Neo4j Docker image] via the `NEO4J_PLUGINS=apoc-extended` environment variable.
-If we use this environment variable, the APOC plugin will be downloaded and configured at runtime.
+If we use this environment variable, the APOC Extended plugin will be downloaded and configured at runtime.
[NOTE]
====
This feature is intended to facilitate using APOC in development environments, but it is not recommended for use in production environments.
====
-.The following runs Neo4j {neo4j-version} in a Docker container with the latest version of the APOC Library
+.The following runs Neo4j {neo4j-version} in a Docker container with the latest version of the APOC Extended Library
[source,bash,subs=attributes]
----
docker run \
@@ -25,6 +25,12 @@ docker run \
neo4j:{neo4j-version}
----
+[NOTE]
+====
+This installs only the APOC Extended library, to download both APOC Extended and APOC Core specify: `-e NEO4J_PLUGINS=\[\"apoc\", \"apoc-extended\"\]`.
+See https://neo4j.com/docs/apoc/{branch}[here] for the APOC Core documentation.
+====
+
We should see the following two lines in the output after running this command:
[source,text,subs=attributes]
diff --git a/docs/asciidoc/modules/ROOT/partials/neo4j-server.adoc b/docs/asciidoc/modules/ROOT/partials/neo4j-server.adoc
index 8c72b99c19..2d1e41d9c1 100644
--- a/docs/asciidoc/modules/ROOT/partials/neo4j-server.adoc
+++ b/docs/asciidoc/modules/ROOT/partials/neo4j-server.adoc
@@ -24,6 +24,7 @@ The version compatibility matrix explains the mapping between Neo4j and APOC ver
[opts=header]
|===
|apoc version | neo4j version
+| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/5.23.0[5.23.0^] | 5.23.0 (5.23.x)
| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/5.22.0[5.22.0^] | 5.22.0 (5.22.x)
| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/5.21.0[5.21.0^] | 5.21.0 (5.21.x)
| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/5.20.0[5.20.0^] | 5.20.0 (5.20.x)
@@ -46,7 +47,7 @@ The version compatibility matrix explains the mapping between Neo4j and APOC ver
| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/5.3.1[5.3.1^] | 5.3.0 (5.3.x)
| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/5.2.0[5.2.0^] | 5.2.0 (5.2.x)
| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/5.1.0[5.1.0^] | 5.1.0 (5.1.x)
-| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/4.4.0.18[4.4.0.18^] | 4.4.0 (4.4.x)
+| https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/4.4.0.30[4.4.0.30^] | 4.4.0 (4.4.x)
|===
// end::version-matrix[]
diff --git a/docs/build.gradle b/docs/build.gradle
index b1ce837378..46d27229d3 100644
--- a/docs/build.gradle
+++ b/docs/build.gradle
@@ -29,7 +29,7 @@ plugins {
id 'org.neo4j.doc.build.docbook' version '1.0-alpha12'
}
-if (!project.hasProperty('apocVersion')) { ext.apocVersion = '5.22.0' }
+if (!project.hasProperty('apocVersion')) { ext.apocVersion = '5.23.0' }
ext {
versionParts = apocVersion.split('-')
diff --git a/extended-it/src/test/java/apoc/neo4j/docker/BoltTest.java b/extended-it/src/test/java/apoc/neo4j/docker/BoltTest.java
index df3ba3eaac..508f405d5b 100644
--- a/extended-it/src/test/java/apoc/neo4j/docker/BoltTest.java
+++ b/extended-it/src/test/java/apoc/neo4j/docker/BoltTest.java
@@ -15,7 +15,6 @@
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Test;
import org.neo4j.driver.Session;
import org.neo4j.graphdb.Entity;
@@ -46,7 +45,6 @@
* @author AgileLARUS
* @since 29.08.17
*/
-@Ignore
public class BoltTest {
public static String BOLT_URL;
diff --git a/extended-it/src/test/java/apoc/neo4j/docker/CoreExtendedTest.java b/extended-it/src/test/java/apoc/neo4j/docker/CoreExtendedTest.java
index 0236fcc786..7bbb2f4f45 100644
--- a/extended-it/src/test/java/apoc/neo4j/docker/CoreExtendedTest.java
+++ b/extended-it/src/test/java/apoc/neo4j/docker/CoreExtendedTest.java
@@ -2,7 +2,6 @@
import apoc.util.Neo4jContainerExtension;
import apoc.util.TestContainerUtil;
-import org.junit.Ignore;
import org.junit.Test;
import apoc.util.TestContainerUtil.ApocPackage;
import org.neo4j.driver.Record;
@@ -25,7 +24,6 @@
into a Neo4j instance without any startup issue.
If you don't have docker installed it will fail, and you can simply ignore it.
*/
-@Ignore
public class CoreExtendedTest {
@Test
public void checkForCoreAndExtended() {
diff --git a/extended-it/src/test/java/apoc/neo4j/docker/StartupExtendedTest.java b/extended-it/src/test/java/apoc/neo4j/docker/StartupExtendedTest.java
index 629e1f128c..7fbf4f4d5c 100644
--- a/extended-it/src/test/java/apoc/neo4j/docker/StartupExtendedTest.java
+++ b/extended-it/src/test/java/apoc/neo4j/docker/StartupExtendedTest.java
@@ -6,7 +6,6 @@
import apoc.util.TestContainerUtil;
import apoc.util.TestContainerUtil.Neo4jVersion;
import org.apache.commons.io.FileUtils;
-import org.junit.Ignore;
import org.junit.Test;
import org.neo4j.driver.Session;
@@ -30,7 +29,6 @@
/*
This test is just to verify if the APOC procedures and functions are correctly deployed into a Neo4j instance without any startup issue.
*/
-@Ignore
public class StartupExtendedTest {
private static final String APOC_HELP_QUERY = "CALL apoc.help('') YIELD core, type, name WHERE core = $core and type = $type RETURN name";
private static final List EXPECTED_EXTENDED_NAMES;
diff --git a/extended/build.gradle b/extended/build.gradle
index 87504eb534..dae45ea6be 100644
--- a/extended/build.gradle
+++ b/extended/build.gradle
@@ -90,7 +90,7 @@ dependencies {
// They need to be provided either through the database or in an extra .jar
compileOnly group: 'org.neo4j', name: 'neo4j', version: neo4jVersionEffective
// same version as the one included in neo4j `lib`
- compileOnly group: 'org.neo4j.driver', name: 'neo4j-java-driver', version: '5.20.0'
+ compileOnly group: 'org.neo4j.driver', name: 'neo4j-java-driver', version: '5.23.0'
compileOnly group: 'org.apache.poi', name: 'poi', version: '5.1.0'
compileOnly group: 'org.apache.poi', name: 'poi-ooxml', version: '5.1.0'
diff --git a/extended/src/main/java/apoc/load/Gexf.java b/extended/src/main/java/apoc/load/Gexf.java
new file mode 100644
index 0000000000..48e8aced04
--- /dev/null
+++ b/extended/src/main/java/apoc/load/Gexf.java
@@ -0,0 +1,83 @@
+package apoc.load;
+
+import apoc.Extended;
+import apoc.Pools;
+import apoc.export.util.CountingReader;
+import apoc.export.util.ExportConfig;
+import apoc.export.util.ProgressReporter;
+import apoc.load.util.XmlReadUtil.Import;
+import apoc.result.MapResult;
+import apoc.result.ProgressInfo;
+import apoc.util.FileUtils;
+import apoc.util.Util;
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.security.URLAccessChecker;
+import org.neo4j.procedure.Context;
+import org.neo4j.procedure.Description;
+import org.neo4j.procedure.Mode;
+import org.neo4j.procedure.Name;
+import org.neo4j.procedure.Procedure;
+import org.neo4j.procedure.TerminationGuard;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static apoc.load.util.XmlReadUtil.Load.xmlXpathToMapResult;
+
+@Extended
+public class Gexf {
+
+ @Context
+ public GraphDatabaseService db;
+
+ @Context
+ public URLAccessChecker urlAccessChecker;
+
+ @Context
+ public TerminationGuard terminationGuard;
+
+ @Context
+ public Pools pools;
+
+ @Procedure("apoc.load.gexf")
+ @Description("apoc.load.gexf(urlOrBinary, path, $config) - load Gexf file from URL or binary source")
+ public Stream gexf(
+ @Name("urlOrBinary") Object urlOrBinary,
+ @Name(value = "config", defaultValue = "{}") Map config
+ ) throws Exception {
+ return xmlXpathToMapResult(urlOrBinary, urlAccessChecker, terminationGuard, config);
+ }
+
+ @Procedure(name = "apoc.import.gexf", mode = Mode.WRITE)
+ @Description("Imports a graph from the provided GraphML file.")
+ public Stream importGexf(
+ @Name("urlOrBinaryFile") Object urlOrBinaryFile, @Name(value = "config", defaultValue = "{}") Map config) {
+ ProgressInfo result = Util.inThread(pools, () -> {
+ ExportConfig exportConfig = new ExportConfig(config);
+ String file = null;
+ String source = "binary";
+ if (urlOrBinaryFile instanceof String) {
+ file = (String) urlOrBinaryFile;
+ source = "file";
+ }
+ ProgressReporter reporter = new ProgressReporter(null, null, new ProgressInfo(file, source, "gexf"));
+ Import graphReader = new Import(db)
+ .reporter(reporter)
+ .batchSize(exportConfig.getBatchSize())
+ .relType(exportConfig.defaultRelationshipType())
+ .source(exportConfig.getSource())
+ .target(exportConfig.getTarget())
+ .nodeLabels(exportConfig.readLabels());
+
+ if (exportConfig.storeNodeIds()) graphReader.storeNodeIds();
+
+ try (CountingReader reader =
+ FileUtils.readerFor(urlOrBinaryFile, exportConfig.getCompressionAlgo(), urlAccessChecker)) {
+ graphReader.parseXML(reader, terminationGuard);
+ }
+
+ return reporter.getTotal();
+ });
+ return Stream.of(result);
+ }
+}
diff --git a/extended/src/main/java/apoc/load/util/XmlReadUtil.java b/extended/src/main/java/apoc/load/util/XmlReadUtil.java
new file mode 100644
index 0000000000..a46529ffe7
--- /dev/null
+++ b/extended/src/main/java/apoc/load/util/XmlReadUtil.java
@@ -0,0 +1,725 @@
+package apoc.load.util;
+
+import apoc.export.util.BatchTransaction;
+import apoc.export.util.CountingInputStream;
+import apoc.export.util.ExportConfig;
+import apoc.export.util.Reporter;
+import apoc.result.MapResult;
+import apoc.util.CompressionAlgo;
+import apoc.util.FileUtils;
+import apoc.util.JsonUtil;
+import apoc.util.Util;
+import org.apache.commons.lang3.StringUtils;
+import org.neo4j.graphdb.Entity;
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.Label;
+import org.neo4j.graphdb.Node;
+import org.neo4j.graphdb.Relationship;
+import org.neo4j.graphdb.RelationshipType;
+import org.neo4j.graphdb.Transaction;
+import org.neo4j.graphdb.security.URLAccessChecker;
+import org.neo4j.procedure.TerminationGuard;
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathFactory;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.lang.reflect.Array;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static apoc.load.util.XmlReadUtil.Load.generateXmlDoctypeException;
+import static apoc.util.CompressionConfig.COMPRESSION;
+import static apoc.util.ExtendedUtil.toValidValue;
+
+/**
+ * Taken from Xml
+ * placed in APOC Core
+ */
+public class XmlReadUtil {
+
+ public static class Load {
+ public static Stream xmlXpathToMapResult(
+ Object urlOrBinary, URLAccessChecker urlAccessChecker, TerminationGuard terminationGuard, Map config) throws Exception {
+ if (config == null) config = Collections.emptyMap();
+ boolean failOnError = (boolean) config.getOrDefault("failOnError", true);
+ String path = (String) config.getOrDefault("path", "/");
+ boolean simpleMode = Util.toBoolean(config.getOrDefault("simpleMode", false));
+ try {
+ Map headers = (Map) config.getOrDefault("headers", Collections.emptyMap());
+ CountingInputStream is = FileUtils.inputStreamFor(
+ urlOrBinary,
+ headers,
+ null,
+ (String) config.getOrDefault(COMPRESSION, CompressionAlgo.NONE.name()),
+ urlAccessChecker);
+ return parse(is, simpleMode, path, failOnError, terminationGuard);
+ } catch (Exception e) {
+ if (!failOnError) return Stream.of(new MapResult(Collections.emptyMap()));
+ else throw e;
+ }
+ }
+
+ private static Stream parse(InputStream data, boolean simpleMode, String path, boolean failOnError, TerminationGuard terminationGuard)
+ throws Exception {
+ List result = new ArrayList<>();
+ try {
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ documentBuilderFactory.setIgnoringElementContentWhitespace(true);
+ documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+ documentBuilder.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader("")));
+
+ Document doc = documentBuilder.parse(data);
+ XPathFactory xPathFactory = XPathFactory.newInstance();
+
+ XPath xPath = xPathFactory.newXPath();
+
+ path = StringUtils.isEmpty(path) ? "/" : path;
+ XPathExpression xPathExpression = xPath.compile(path);
+ NodeList nodeList = (NodeList) xPathExpression.evaluate(doc, XPathConstants.NODESET);
+
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ final Deque