├── .all-contributorsrc ├── .github ├── CODEOWNERS ├── dependabot.yml ├── project.yml └── workflows │ ├── build.yml │ ├── pre-release.yml │ ├── quarkus-snapshot.yaml │ ├── release-perform.yml │ └── release-prepare.yml ├── .gitignore ├── LICENSE ├── README.md ├── deployment ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── quarkus │ │ │ └── neo4j │ │ │ └── deployment │ │ │ ├── BoltHandshaker.java │ │ │ ├── DevServicesBuildTimeConfig.java │ │ │ ├── Neo4jBuildTimeConfig.java │ │ │ ├── Neo4jDevServicesProcessor.java │ │ │ ├── Neo4jDevUiConsoleProcessor.java │ │ │ ├── Neo4jDriverBuildItem.java │ │ │ └── Neo4jDriverProcessor.java │ └── resources │ │ └── io │ │ └── quarkus │ │ └── neo4j │ │ └── deployment │ │ └── neo4j_dev_services_ext.sh │ └── test │ ├── java │ └── io │ │ └── quarkus │ │ └── neo4j │ │ └── deployment │ │ ├── Neo4jDevModeTests.java │ │ └── PortUtils.java │ └── resources │ └── application.properties ├── docs ├── antora.yml ├── modules │ └── ROOT │ │ ├── assets │ │ └── images │ │ │ └── .keepme │ │ ├── examples │ │ ├── Fruit.java │ │ ├── FruitResource.java │ │ └── ReactiveFruitResource.java │ │ ├── nav.adoc │ │ └── pages │ │ ├── includes │ │ ├── attributes.adoc │ │ ├── quarkus-neo4j-config-group-dev-services-build-time-config.adoc │ │ ├── quarkus-neo4j.adoc │ │ └── quarkus-neo4j_quarkus.neo4j.adoc │ │ └── index.adoc ├── pom.xml └── templates │ └── includes │ └── attributes.adoc ├── integration-tests ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── quarkus │ │ │ └── it │ │ │ └── neo4j │ │ │ ├── Fruit.java │ │ │ ├── FruitResource.java │ │ │ ├── Neo4jResource.java │ │ │ └── ReactiveFruitResource.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── io │ └── quarkus │ └── it │ └── neo4j │ ├── Neo4jFunctionalityInGraalITCase.java │ └── Neo4jFunctionalityTest.java ├── pom.xml └── runtime ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── quarkus │ │ └── neo4j │ │ └── runtime │ │ ├── Neo4jConfiguration.java │ │ ├── Neo4jDriverRecorder.java │ │ └── health │ │ └── Neo4jHealthCheck.java └── resources │ └── META-INF │ └── quarkus-extension.yaml └── test └── java └── io └── quarkus └── neo4j └── runtime └── Neo4jDriverRecorderTest.java /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "michael-simons", 10 | "name": "Michael Simons", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/526383?v=4", 12 | "profile": "http://michael-simons.eu", 13 | "contributions": [ 14 | "code", 15 | "maintenance" 16 | ] 17 | }, 18 | { 19 | "login": "gsmet", 20 | "name": "Guillaume Smet", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4", 22 | "profile": "https://lesincroyableslivres.fr/", 23 | "contributions": [ 24 | "maintenance", 25 | "doc" 26 | ] 27 | }, 28 | { 29 | "login": "phillip-kruger", 30 | "name": "Phillip Krüger", 31 | "avatar_url": "https://avatars.githubusercontent.com/u/6836179?v=4", 32 | "profile": "http://www.phillip-kruger.com", 33 | "contributions": [ 34 | "code" 35 | ] 36 | }, 37 | { 38 | "login": "cescoffier", 39 | "name": "Clement Escoffier", 40 | "avatar_url": "https://avatars.githubusercontent.com/u/402301?v=4", 41 | "profile": "https://github.com/cescoffier", 42 | "contributions": [ 43 | "question" 44 | ] 45 | }, 46 | { 47 | "login": "gastaldi", 48 | "name": "George Gastaldi", 49 | "avatar_url": "https://avatars.githubusercontent.com/u/54133?v=4", 50 | "profile": "http://gastaldi.wordpress.com", 51 | "contributions": [ 52 | "infra" 53 | ] 54 | }, 55 | { 56 | "login": "TheJavaGuy", 57 | "name": "Ivan Milosavljević", 58 | "avatar_url": "https://avatars.githubusercontent.com/u/11942401?v=4", 59 | "profile": "https://thejavaguy.org/", 60 | "contributions": [ 61 | "doc" 62 | ] 63 | }, 64 | { 65 | "login": "jmartisk", 66 | "name": "Jan Martiska", 67 | "avatar_url": "https://avatars.githubusercontent.com/u/937315?v=4", 68 | "profile": "https://github.com/jmartisk", 69 | "contributions": [ 70 | "code" 71 | ] 72 | }, 73 | { 74 | "login": "nijuichien", 75 | "name": "Jui-Chien Ni", 76 | "avatar_url": "https://avatars.githubusercontent.com/u/87717636?v=4", 77 | "profile": "https://github.com/nijuichien", 78 | "contributions": [ 79 | "bug" 80 | ] 81 | }, 82 | { 83 | "login": "injectives", 84 | "name": "Dmitriy Tverdiakov", 85 | "avatar_url": "https://avatars.githubusercontent.com/u/11927660?v=4", 86 | "profile": "https://github.com/injectives", 87 | "contributions": [ 88 | "code" 89 | ] 90 | } 91 | ], 92 | "contributorsPerLine": 7, 93 | "projectName": "quarkus-neo4j", 94 | "projectOwner": "quarkiverse", 95 | "repoType": "github", 96 | "repoHost": "https://github.com", 97 | "skipCi": true, 98 | "commitType": "docs", 99 | "commitConvention": "angular" 100 | } 101 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /.github/ @quarkiverse/quarkiverse-neo4j @michael-simons 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "org.apache.maven.plugins:maven-compiler-plugin" 14 | -------------------------------------------------------------------------------- /.github/project.yml: -------------------------------------------------------------------------------- 1 | release: 2 | current-version: 5.4.0 3 | next-version: 5.4.1-SNAPSHOT 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths-ignore: 8 | - '.gitignore' 9 | - 'CODEOWNERS' 10 | - 'LICENSE' 11 | - '*.md' 12 | - '*.adoc' 13 | - '*.txt' 14 | - '.all-contributorsrc' 15 | pull_request: 16 | paths-ignore: 17 | - '.gitignore' 18 | - 'CODEOWNERS' 19 | - 'LICENSE' 20 | - '*.md' 21 | - '*.adoc' 22 | - '*.txt' 23 | - '.all-contributorsrc' 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.ref }} 27 | cancel-in-progress: true 28 | 29 | defaults: 30 | run: 31 | shell: bash 32 | 33 | jobs: 34 | build: 35 | name: Build on ${{ matrix.os }} 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | # os: [windows-latest, macos-latest, ubuntu-latest] 40 | os: [ubuntu-latest] 41 | runs-on: ${{ matrix.os }} 42 | steps: 43 | - name: Prepare git 44 | run: git config --global core.autocrlf false 45 | if: startsWith(matrix.os, 'windows') 46 | 47 | - uses: actions/checkout@v3 48 | - name: Set up JDK 17 49 | uses: actions/setup-java@v3 50 | with: 51 | distribution: temurin 52 | java-version: 17 53 | cache: 'maven' 54 | 55 | - name: Build with Maven 56 | run: mvn -B clean install -Dno-format 57 | 58 | - name: Build with Maven (Native) 59 | run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Pre Release 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/project.yml' 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | pre-release: 14 | name: Pre-Release 15 | uses: quarkiverse/.github/.github/workflows/pre-release.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/quarkus-snapshot.yaml: -------------------------------------------------------------------------------- 1 | name: "Quarkus ecosystem CI" 2 | on: 3 | workflow_dispatch: 4 | watch: 5 | types: [started] 6 | 7 | # For this CI to work, ECOSYSTEM_CI_TOKEN needs to contain a GitHub with rights to close the Quarkus issue that the user/bot has opened, 8 | # while 'ECOSYSTEM_CI_REPO_PATH' needs to be set to the corresponding path in the 'quarkusio/quarkus-ecosystem-ci' repository 9 | 10 | env: 11 | ECOSYSTEM_CI_REPO: quarkusio/quarkus-ecosystem-ci 12 | ECOSYSTEM_CI_REPO_FILE: context.yaml 13 | JAVA_VERSION: 17 14 | 15 | ######################### 16 | # Repo specific setting # 17 | ######################### 18 | 19 | ECOSYSTEM_CI_REPO_PATH: quarkiverse-neo4j 20 | 21 | jobs: 22 | build: 23 | name: "Build against latest Quarkus snapshot" 24 | runs-on: ubuntu-latest 25 | # Allow to manually launch the ecosystem CI in addition to the bots 26 | if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == '' 27 | 28 | steps: 29 | - name: Install yq 30 | uses: dcarbone/install-yq-action@v1.0.1 31 | 32 | - name: Set up Java 33 | uses: actions/setup-java@v3 34 | with: 35 | distribution: temurin 36 | java-version: ${{ env.JAVA_VERSION }} 37 | 38 | - name: Checkout repo 39 | uses: actions/checkout@v3 40 | with: 41 | path: current-repo 42 | # Build against the 1.x branch. Remove it when the main branch is compatible with the Quarkus main branch 43 | ref: 1.x 44 | 45 | - name: Checkout Ecosystem 46 | uses: actions/checkout@v3 47 | with: 48 | repository: ${{ env.ECOSYSTEM_CI_REPO }} 49 | path: ecosystem-ci 50 | 51 | - name: Setup and Run Tests 52 | run: ./ecosystem-ci/setup-and-test 53 | env: 54 | ECOSYSTEM_CI_TOKEN: ${{ secrets.ECOSYSTEM_CI_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/release-perform.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Perform Release 2 | run-name: Perform ${{github.event.inputs.tag || github.ref_name}} Release 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'Tag to release' 11 | required: true 12 | 13 | permissions: 14 | attestations: write 15 | id-token: write 16 | contents: read 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | perform-release: 24 | name: Perform Release 25 | uses: quarkiverse/.github/.github/workflows/perform-release.yml@main 26 | secrets: inherit 27 | with: 28 | version: ${{github.event.inputs.tag || github.ref_name}} 29 | -------------------------------------------------------------------------------- /.github/workflows/release-prepare.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Prepare Release 2 | 3 | on: 4 | pull_request: 5 | types: [ closed ] 6 | paths: 7 | - '.github/project.yml' 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | prepare-release: 15 | name: Prepare Release 16 | if: ${{ github.event.pull_request.merged == true}} 17 | uses: quarkiverse/.github/.github/workflows/prepare-release.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # Eclipse 26 | .project 27 | .classpath 28 | .settings/ 29 | bin/ 30 | 31 | # IntelliJ 32 | .idea 33 | *.ipr 34 | *.iml 35 | *.iws 36 | 37 | # NetBeans 38 | nb-configuration.xml 39 | 40 | # Visual Studio Code 41 | .vscode 42 | .factorypath 43 | 44 | # OSX 45 | .DS_Store 46 | 47 | # Vim 48 | *.swp 49 | *.swo 50 | 51 | # patch 52 | *.orig 53 | *.rej 54 | 55 | # Gradle 56 | .gradle/ 57 | build/ 58 | 59 | # Maven 60 | target/ 61 | pom.xml.tag 62 | pom.xml.releaseBackup 63 | pom.xml.versionsBackup 64 | release.properties -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quarkus Neo4j 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | [![Version](https://img.shields.io/maven-central/v/io.quarkiverse.neo4j/quarkus-neo4j?logo=apache-maven&style=flat-square)](https://search.maven.org/artifact/io.quarkiverse.neo4j/quarkus-neo4j) 7 | 8 | ## Introduction 9 | 10 | Quarkus Neo4j is a Quarkus extension to connect to the [Neo4j graph database](https://neo4j.com). 11 | 12 | It enables the use of the [Neo4j Java Driver](https://github.com/neo4j/neo4j-java-driver) in both JVM mode and native executables. It provide configuration properties to configure all relevant aspects of the driver. 13 | 14 | ## Documentation 15 | 16 | The documentation for this extension can be found [here](https://quarkiverse.github.io/quarkiverse-docs/quarkus-neo4j/dev/index.html) while the documentation for the Neo4j Java Driver itself is in the official [manual](https://neo4j.com/docs/java-manual/4.4/). 17 | 18 | Other extension that build on this extension and are known to work well with it: 19 | 20 | * [Neo4j-Migrations](https://michael-simons.github.io/neo4j-migrations/current/#download_quarkus), an extension that allows running Cypher scripts in a controlled manner for migrating or refactoring databases 21 | * [Neo4j-OGM for Quarkus](https://github.com/michael-simons/neo4j-ogm-quarkus), an extension bringing in Neo4j-OGM and a couple of shims allowing to use Neo4js object mapper without too much hassle inside Quarkus 22 | 23 | ## Contributors ✨ 24 | 25 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Michael Simons
Michael Simons

💻 🚧
Guillaume Smet
Guillaume Smet

🚧 📖
Phillip Krüger
Phillip Krüger

💻
Clement Escoffier
Clement Escoffier

💬
George Gastaldi
George Gastaldi

🚇
Ivan Milosavljević
Ivan Milosavljević

📖
Jan Martiska
Jan Martiska

💻
Jui-Chien Ni
Jui-Chien Ni

🐛
Dmitriy Tverdiakov
Dmitriy Tverdiakov

💻
47 | 48 | 49 | 50 | 51 | 52 | 53 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 54 | -------------------------------------------------------------------------------- /deployment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.neo4j 6 | quarkus-neo4j-parent 7 | 5.4.1-SNAPSHOT 8 | 9 | quarkus-neo4j-deployment 10 | Quarkus - Neo4j - Deployment 11 | 12 | 13 | io.quarkus 14 | quarkus-arc-deployment 15 | 16 | 17 | io.quarkus 18 | quarkus-smallrye-health-spi 19 | 20 | 21 | io.quarkiverse.neo4j 22 | quarkus-neo4j 23 | ${project.version} 24 | 25 | 26 | org.testcontainers 27 | neo4j 28 | 29 | 30 | junit 31 | junit 32 | 33 | 34 | 35 | 36 | io.quarkus 37 | quarkus-devservices-common 38 | 39 | 40 | 41 | io.quarkus 42 | quarkus-junit5-internal 43 | test 44 | 45 | 46 | org.testcontainers 47 | junit-jupiter 48 | test 49 | 50 | 51 | org.assertj 52 | assertj-core 53 | test 54 | 55 | 56 | 57 | 58 | 59 | maven-compiler-plugin 60 | 61 | 62 | 63 | io.quarkus 64 | quarkus-extension-processor 65 | ${quarkus.version} 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | fast 76 | 77 | 78 | fastDeploymentBuild 79 | 80 | 81 | 82 | true 83 | true 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkus/neo4j/deployment/BoltHandshaker.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import java.io.DataInputStream; 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.net.Socket; 8 | import java.time.Duration; 9 | 10 | /** 11 | * This implements the protocol version negotiation of bolt. Testing to see if in address will respond to this is a 12 | * quick way to find out if it's a running bolt server. 13 | *

14 | * This class first appeared in https://github.com/michael-simons/junit-jupiter-causal-cluster-testcontainer-extension 15 | * by Andrew Jefferson and Michael Simons 16 | */ 17 | final class BoltHandshaker { 18 | 19 | private static final int magicToken = 1616949271; 20 | 21 | // Versions message that cannot be matched because it is all zeros. 22 | private static final byte[] versionsMessage = { 23 | (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 24 | (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 25 | (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 26 | (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 27 | }; 28 | 29 | private final String address; 30 | private final int port; 31 | 32 | BoltHandshaker(String address, int port) { 33 | this.address = address; 34 | this.port = port; 35 | } 36 | 37 | private boolean doBoltHandshake(String address, int port, int timeoutMillis) { 38 | 39 | try (Socket socket = new Socket()) { 40 | 41 | // Set the socket timeout for blocking operations 42 | socket.setSoTimeout(timeoutMillis); 43 | 44 | // Connects this socket to the server (also with the specified timeout value). 45 | socket.connect(new InetSocketAddress(address, port), timeoutMillis); 46 | 47 | DataOutputStream dOut = new DataOutputStream(socket.getOutputStream()); 48 | DataInputStream dIn = new DataInputStream(socket.getInputStream()); 49 | 50 | // Send magic token (0x6060B017) 51 | dOut.writeInt(magicToken); 52 | dOut.flush(); 53 | 54 | // Send 4 supported versions 55 | // Except we don't support any versions and communicate that by sending all zeros 56 | dOut.write(versionsMessage); 57 | dOut.flush(); 58 | 59 | // Receive agreed version 60 | // It should be 0 because there are no possible versions we can agree on 61 | int response = dIn.readInt(); 62 | assert response == 0; 63 | 64 | // Because we cannot agree on a version the server should close its side of the connection 65 | // resulting in EOF (-1) on all subsequent reads. 66 | return dIn.read() == -1; 67 | } catch (IOException exception) { 68 | // Return false if handshake fails 69 | return false; 70 | } 71 | } 72 | 73 | boolean isBoltPortReachable(Duration timeout) { 74 | int timeoutMillis = Math.toIntExact(timeout.toMillis()); 75 | return doBoltHandshake(address, port, timeoutMillis); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkus/neo4j/deployment/DevServicesBuildTimeConfig.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import java.util.Map; 4 | import java.util.Optional; 5 | import java.util.OptionalInt; 6 | 7 | import io.quarkus.runtime.annotations.ConfigGroup; 8 | import io.smallrye.config.WithDefault; 9 | 10 | @ConfigGroup 11 | public interface DevServicesBuildTimeConfig { 12 | 13 | /** 14 | * If DevServices has been explicitly enabled or disabled. DevServices is generally enabled 15 | * by default, unless there is an existing configuration present. 16 | * When DevServices is enabled Quarkus will attempt to automatically configure and start 17 | * a database when running in Dev or Test mode. 18 | * 19 | * @return whether dev services are enabled or not 20 | */ 21 | Optional enabled(); 22 | 23 | /** 24 | * {@return the container image name to use, for container based DevServices providers} 25 | */ 26 | @WithDefault("neo4j:5") 27 | String imageName(); 28 | 29 | /** 30 | * {@return additional environment entries that can be added to the container before its start} 31 | */ 32 | Map additionalEnv(); 33 | 34 | /** 35 | * This value can be used to specify the port to which the bolt-port of the container is exposed. It must be a free 36 | * port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be 37 | * logged on which port the Neo4j container is reachable over bolt. 38 | *

39 | * Ignored when container sharing is enabled. 40 | * 41 | * @return a specific port to bind the containers bolt-port to 42 | */ 43 | OptionalInt boltPort(); 44 | 45 | /** 46 | * This value can be used to specify the port to which the http-port of the container is exposed. It must be a free 47 | * port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be 48 | * logged on which port the Neo4j Browser is available. 49 | *

50 | * Ignored when container sharing is enabled. 51 | * 52 | * @return a specific port to bind the containers http-port to 53 | */ 54 | OptionalInt httpPort(); 55 | 56 | /** 57 | * Indicates if the Neo4j server managed by Quarkus Dev Services is shared. 58 | * When shared, Quarkus looks for running containers using label-based service discovery. 59 | * If a matching container is found, it is used, and so a second one is not started. 60 | * Otherwise, Dev Services for Neo4j starts a new container. 61 | *

62 | * The discovery uses the {@code quarkus-dev-service-neo4j} label. 63 | * The value is configured using the {@code service-name} property. 64 | *

65 | * Container sharing is only used in dev mode and disabled by default 66 | * 67 | * @return flag to enable container sharing 68 | */ 69 | @WithDefault("false") 70 | boolean shared(); 71 | 72 | /** 73 | * The value of the {@code quarkus-dev-service-neo4j} label attached to the started container. 74 | * This property is used when {@code shared} is set to {@code true}. 75 | * In this case, before starting a container, Dev Services for Neo4j looks for a container with the 76 | * {@code quarkus-dev-service-neo4j} label 77 | * set to the configured value. If found, it will use this container instead of starting a new one. Otherwise, it 78 | * starts a new container with the {@code quarkus-dev-service-neo4j} label set to the specified value. 79 | *

80 | * This property is used when you need multiple shared Neo4j servers. 81 | */ 82 | @WithDefault("neo4j") 83 | String serviceName(); 84 | 85 | /** 86 | * This property is used only when you create multiple shared Neo4j servers 87 | * 88 | * @return one password for 89 | */ 90 | @WithDefault("verysecret") 91 | String sharedPassword(); 92 | } 93 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jBuildTimeConfig.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import io.quarkus.runtime.annotations.ConfigPhase; 4 | import io.quarkus.runtime.annotations.ConfigRoot; 5 | import io.smallrye.config.ConfigMapping; 6 | import io.smallrye.config.WithDefault; 7 | import io.smallrye.config.WithName; 8 | 9 | @ConfigMapping(prefix = "quarkus.neo4j") 10 | @ConfigRoot(phase = ConfigPhase.BUILD_TIME) 11 | public interface Neo4jBuildTimeConfig { 12 | 13 | /** 14 | * {@return whether a health check is published in case the smallrye-health extension is present} 15 | */ 16 | @WithName("health.enabled") 17 | @WithDefault("true") 18 | boolean healthEnabled(); 19 | 20 | /** 21 | * DevServices allows Quarkus to automatically start a Neo4j instance in dev and test mode. 22 | * {@return Configuration for DevServices} 23 | */ 24 | DevServicesBuildTimeConfig devservices(); 25 | } 26 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDevServicesProcessor.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.time.Duration; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.OptionalInt; 9 | 10 | import org.jboss.logging.Logger; 11 | import org.testcontainers.containers.Neo4jContainer; 12 | import org.testcontainers.images.builder.Transferable; 13 | import org.testcontainers.utility.DockerImageName; 14 | import org.testcontainers.utility.MountableFile; 15 | 16 | import com.github.dockerjava.api.command.InspectContainerResponse; 17 | 18 | import io.quarkus.deployment.Feature; 19 | import io.quarkus.deployment.IsDockerWorking; 20 | import io.quarkus.deployment.IsNormal; 21 | import io.quarkus.deployment.annotations.BuildStep; 22 | import io.quarkus.deployment.annotations.Consume; 23 | import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; 24 | import io.quarkus.deployment.builditem.DevServicesResultBuildItem; 25 | import io.quarkus.deployment.builditem.DevServicesResultBuildItem.RunningDevService; 26 | import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem; 27 | import io.quarkus.deployment.builditem.LaunchModeBuildItem; 28 | import io.quarkus.deployment.console.ConsoleInstalledBuildItem; 29 | import io.quarkus.deployment.console.StartupLogCompressor; 30 | import io.quarkus.deployment.dev.devservices.DevServiceDescriptionBuildItem; 31 | import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; 32 | import io.quarkus.deployment.logging.LoggingSetupBuildItem; 33 | import io.quarkus.devservices.common.ConfigureUtil; 34 | import io.quarkus.devservices.common.ContainerLocator; 35 | import io.quarkus.runtime.LaunchMode; 36 | import io.quarkus.runtime.configuration.ConfigUtils; 37 | 38 | class Neo4jDevServicesProcessor { 39 | 40 | private static final Logger log = Logger.getLogger("io.quarkus.neo4j.deployment"); 41 | 42 | private static final String NEO4J_URI = "quarkus.neo4j.uri"; 43 | static final String NEO4J_BROWSER_URL = "quarkus.neo4j.browser-url"; 44 | private static final String NEO4J_USER_PROP = "quarkus.neo4j.authentication.username"; 45 | private static final String NEO4J_PASSWORD_PROP = "quarkus.neo4j.authentication.password"; 46 | private static final int DEFAULT_HTTP_PORT = 7474; 47 | private static final int DEFAULT_BOLT_PORT = 7687; 48 | 49 | /** 50 | * Label to add to shared Dev Service for Neo4j running in containers. 51 | * This allows other applications to discover the running service and use it instead of starting a new instance. 52 | */ 53 | private static final String DEV_SERVICE_LABEL = "quarkus-dev-service-neo4j"; 54 | 55 | private static final ContainerLocator CONTAINER_LOCATOR = new ContainerLocator(DEV_SERVICE_LABEL, DEFAULT_BOLT_PORT); 56 | 57 | static volatile RunningDevService devService; 58 | static volatile Neo4jDevServiceConfig runningConfiguration; 59 | static volatile boolean first = true; 60 | 61 | private final IsDockerWorking isDockerWorking = new IsDockerWorking(true); 62 | 63 | @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) 64 | public DevServicesResultBuildItem startNeo4jDevService( 65 | LaunchModeBuildItem launchMode, 66 | Neo4jBuildTimeConfig neo4jBuildTimeConfig, 67 | Optional consoleInstalledBuildItem, 68 | CuratedApplicationShutdownBuildItem closeBuildItem, 69 | LoggingSetupBuildItem loggingSetupBuildItem, 70 | GlobalDevServicesConfig globalDevServicesConfig, 71 | List devServicesSharedNetworkBuildItem) { 72 | 73 | var configuration = new Neo4jDevServiceConfig(neo4jBuildTimeConfig.devservices()); 74 | 75 | if (devService != null) { 76 | if (configuration.equals(runningConfiguration)) { 77 | return devService.toBuildItem(); 78 | } 79 | shutdownNeo4j(); 80 | runningConfiguration = null; 81 | } 82 | 83 | var compressor = new StartupLogCompressor( 84 | (launchMode.isTest() ? "(test) " : "") + "Neo4j Dev Services Starting:", consoleInstalledBuildItem, 85 | loggingSetupBuildItem); 86 | 87 | try { 88 | boolean useSharedNetwork = DevServicesSharedNetworkBuildItem.isSharedNetworkRequired(globalDevServicesConfig, 89 | devServicesSharedNetworkBuildItem); 90 | devService = startNeo4j(configuration, launchMode.getLaunchMode(), useSharedNetwork, 91 | globalDevServicesConfig.timeout); 92 | } catch (Throwable t) { 93 | compressor.closeAndDumpCaptured(); 94 | throw new RuntimeException(t); 95 | } 96 | 97 | // Configure the watch dog 98 | if (first) { 99 | first = false; 100 | Runnable closeTask = () -> { 101 | if (devService != null) { 102 | shutdownNeo4j(); 103 | log.info("Dev Services for Neo4j shut down."); 104 | } 105 | first = true; 106 | runningConfiguration = null; 107 | }; 108 | closeBuildItem.addCloseTask(closeTask, true); 109 | } 110 | runningConfiguration = configuration; 111 | 112 | return devService == null ? null : devService.toBuildItem(); 113 | } 114 | 115 | @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) 116 | @Consume(DevServicesResultBuildItem.class) 117 | DevServiceDescriptionBuildItem renderDevServiceDevUICard() { 118 | return devService == null ? null 119 | : new DevServiceDescriptionBuildItem(Feature.NEO4J.getName(), null, devService.getConfig()); 120 | } 121 | 122 | private RunningDevService startNeo4j(Neo4jDevServiceConfig configuration, LaunchMode launchMode, boolean useSharedNetwork, 123 | Optional timeout) { 124 | 125 | if (!isDockerWorking.getAsBoolean()) { 126 | log.debug("Not starting Dev Services for Neo4j, as Docker is not working."); 127 | return null; 128 | } 129 | 130 | if (!configuration.devServicesEnabled) { 131 | log.debug("Not starting Dev Services for Neo4j, as it has been disabled in the config."); 132 | return null; 133 | } 134 | 135 | // Check if Neo4j URL or password is set to explicitly 136 | if (ConfigUtils.isPropertyPresent(NEO4J_URI) || ConfigUtils.isPropertyPresent(NEO4J_USER_PROP) 137 | || ConfigUtils.isPropertyPresent(NEO4J_PASSWORD_PROP)) { 138 | log.debug("Not starting Dev Services for Neo4j, as there is explicit configuration present."); 139 | return null; 140 | } 141 | 142 | var boldIsReachable = Boolean.getBoolean("io.quarkus.neo4j.deployment.devservices.assumeBoltIsReachable") 143 | || new BoltHandshaker("localhost", Neo4jDevServicesProcessor.DEFAULT_BOLT_PORT) 144 | .isBoltPortReachable(Duration.ofSeconds(5)); 145 | if (boldIsReachable && configuration.fixedBoltPort.orElse(-1) == Neo4jDevServicesProcessor.DEFAULT_BOLT_PORT) { 146 | log.warn( 147 | "Not starting Dev Services for Neo4j, as the configuration requests the same fixed bolt port."); 148 | return null; 149 | } 150 | 151 | return CONTAINER_LOCATOR.locateContainer(configuration.serviceName(), configuration.shared(), launchMode) 152 | .map(containerAddress -> { 153 | var config = Map.of( 154 | NEO4J_URI, String.format("bolt://" + containerAddress.getUrl()), 155 | NEO4J_PASSWORD_PROP, configuration.sharedPassword()); 156 | return new RunningDevService(Feature.NEO4J.getName(), containerAddress.getId(), null, config); 157 | }) 158 | .orElseGet(() -> { 159 | @SuppressWarnings("resource") 160 | var neo4jContainer = ExtNeo4jContainer.of(launchMode, configuration, useSharedNetwork); 161 | configuration.additionalEnv.forEach(neo4jContainer::addEnv); 162 | timeout.ifPresent(neo4jContainer::withStartupTimeout); 163 | neo4jContainer.start(); 164 | 165 | var config = Map.of( 166 | NEO4J_URI, neo4jContainer.getBoltUrl(), 167 | NEO4J_BROWSER_URL, neo4jContainer.getBrowserUrl(), 168 | NEO4J_PASSWORD_PROP, neo4jContainer.getAdminPassword()); 169 | 170 | log.infof("Dev Services started a Neo4j container reachable at %s", neo4jContainer.getBoltUrl()); 171 | log.infof("Neo4j Browser is reachable at %s", neo4jContainer.getBrowserUrl()); 172 | log.infof("The username for both endpoints is `%s`, authenticated by `%s`", "neo4j", 173 | neo4jContainer.getAdminPassword()); 174 | log.infof("Connect via Cypher-Shell: cypher-shell -u %s -p %s -a %s", "neo4j", 175 | neo4jContainer.getAdminPassword(), neo4jContainer.getBoltUrl()); 176 | 177 | return new RunningDevService(Feature.NEO4J.getName(), neo4jContainer.getContainerId(), 178 | neo4jContainer::close, config); 179 | }); 180 | } 181 | 182 | private void shutdownNeo4j() { 183 | if (devService != null) { 184 | try { 185 | devService.close(); 186 | } catch (Throwable e) { 187 | log.error("Failed to stop Neo4j container", e); 188 | } finally { 189 | devService = null; 190 | } 191 | } 192 | } 193 | 194 | private final static class ExtNeo4jContainer extends Neo4jContainer { 195 | 196 | static ExtNeo4jContainer of(LaunchMode launchMode, Neo4jDevServiceConfig config, boolean useSharedNetwork) { 197 | 198 | var container = new ExtNeo4jContainer(DockerImageName.parse(config.imageName).asCompatibleSubstituteFor("neo4j")); 199 | if (config.shared) { 200 | container.withAdminPassword(config.sharedPassword()); 201 | } else { 202 | config.fixedBoltPort.ifPresent(port -> container.addFixedExposedPort(port, DEFAULT_BOLT_PORT)); 203 | config.fixedHttpPort.ifPresent(port -> container.addFixedExposedPort(port, DEFAULT_HTTP_PORT)); 204 | } 205 | 206 | if (useSharedNetwork) { 207 | var name = ConfigureUtil.configureSharedNetwork(container, "neo4j"); 208 | System.out.println(name); 209 | } 210 | 211 | if (launchMode == LaunchMode.DEVELOPMENT && config.serviceName() != null) { 212 | container.withLabel(DEV_SERVICE_LABEL, config.serviceName); 213 | } 214 | 215 | var extensionScript = "/neo4j_dev_services_ext.sh"; 216 | return container 217 | .withCopyFileToContainer( 218 | MountableFile.forClasspathResource("/io/quarkus/neo4j/deployment" + extensionScript, 0777), 219 | extensionScript) 220 | .withEnv("EXTENSION_SCRIPT", extensionScript); 221 | } 222 | 223 | ExtNeo4jContainer(DockerImageName dockerImageName) { 224 | super(dockerImageName); 225 | } 226 | 227 | String getBrowserUrl() { 228 | return String.format("%s/browser?dbms=bolt://%s@%s:%d", getHttpUrl(), "neo4j", getHost(), 229 | getMappedPort(DEFAULT_BOLT_PORT)); 230 | } 231 | 232 | @Override 233 | protected void containerIsStarting(InspectContainerResponse containerInfo, boolean reused) { 234 | super.containerIsStarting(containerInfo, reused); 235 | 236 | if (reused) { 237 | return; 238 | } 239 | 240 | var mappedPort = getMappedPort(DEFAULT_BOLT_PORT); 241 | var command = String.format("export NEO4J_dbms_connector_bolt_advertised__address=%s:%d\n", getHost(), 242 | mappedPort); 243 | copyFileToContainer(Transferable.of(command.getBytes(StandardCharsets.UTF_8)), "/testcontainers_env"); 244 | } 245 | } 246 | 247 | private record Neo4jDevServiceConfig( 248 | boolean devServicesEnabled, 249 | String imageName, 250 | Map additionalEnv, 251 | OptionalInt fixedBoltPort, 252 | OptionalInt fixedHttpPort, 253 | boolean shared, 254 | String serviceName, 255 | String sharedPassword) { 256 | 257 | private Neo4jDevServiceConfig { 258 | additionalEnv = additionalEnv == null ? Map.of() : Map.copyOf(additionalEnv); 259 | } 260 | 261 | Neo4jDevServiceConfig(DevServicesBuildTimeConfig devServicesConfig) { 262 | this(enabled(devServicesConfig), devServicesConfig.imageName(), devServicesConfig.additionalEnv(), 263 | devServicesConfig.boltPort(), devServicesConfig.httpPort(), devServicesConfig.shared(), 264 | devServicesConfig.serviceName(), devServicesConfig.sharedPassword()); 265 | } 266 | } 267 | 268 | /** 269 | * A helper method to encapsulate the {@code Optional} to {code boolean} mapping of that config flag. 270 | * 271 | * @param devServicesConfig The configuration of dev services for neo4j 272 | * @return {@literal true} if Neo4j dev services are enabled or not 273 | */ 274 | static boolean enabled(DevServicesBuildTimeConfig devServicesConfig) { 275 | return Optional.ofNullable(devServicesConfig).flatMap(DevServicesBuildTimeConfig::enabled).orElse(true); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDevUiConsoleProcessor.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import java.util.List; 4 | 5 | import io.quarkus.deployment.IsDevelopment; 6 | import io.quarkus.deployment.annotations.BuildStep; 7 | import io.quarkus.deployment.builditem.DevServicesResultBuildItem; 8 | import io.quarkus.devui.spi.page.CardPageBuildItem; 9 | import io.quarkus.devui.spi.page.Page; 10 | 11 | class Neo4jDevUiConsoleProcessor { 12 | 13 | @BuildStep(onlyIf = IsDevelopment.class) 14 | CardPageBuildItem create( 15 | List runningDevServices, 16 | Neo4jBuildTimeConfig neo4jBuildTimeConfig) { 17 | 18 | var cardPageBuildItem = new CardPageBuildItem(); 19 | if (Neo4jDevServicesProcessor.enabled(neo4jBuildTimeConfig.devservices())) { 20 | 21 | // Find the appropriate config 22 | for (DevServicesResultBuildItem runningDevService : runningDevServices) { 23 | if (runningDevService.getConfig().containsKey(Neo4jDevServicesProcessor.NEO4J_BROWSER_URL)) { 24 | cardPageBuildItem.addPage(Page.externalPageBuilder("Neo4J Browser") 25 | .icon("font-awesome-solid:diagram-project") 26 | .url(runningDevService.getConfig().get(Neo4jDevServicesProcessor.NEO4J_BROWSER_URL)) 27 | .doNotEmbed()); 28 | break; 29 | } 30 | } 31 | } 32 | 33 | return cardPageBuildItem; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverBuildItem.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import org.neo4j.driver.Driver; 4 | 5 | import io.quarkus.builder.item.SimpleBuildItem; 6 | import io.quarkus.runtime.RuntimeValue; 7 | 8 | /** 9 | * Allows access to the Neo4j Driver instance from within other extensions. 10 | */ 11 | public final class Neo4jDriverBuildItem extends SimpleBuildItem { 12 | 13 | private final RuntimeValue value; 14 | 15 | public Neo4jDriverBuildItem(RuntimeValue value) { 16 | this.value = value; 17 | } 18 | 19 | public RuntimeValue getValue() { 20 | return value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkus/neo4j/deployment/Neo4jDriverProcessor.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import org.neo4j.driver.Driver; 4 | 5 | import io.quarkus.arc.deployment.SyntheticBeanBuildItem; 6 | import io.quarkus.deployment.Feature; 7 | import io.quarkus.deployment.annotations.BuildProducer; 8 | import io.quarkus.deployment.annotations.BuildStep; 9 | import io.quarkus.deployment.annotations.ExecutionTime; 10 | import io.quarkus.deployment.annotations.Record; 11 | import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; 12 | import io.quarkus.deployment.builditem.FeatureBuildItem; 13 | import io.quarkus.deployment.builditem.ShutdownContextBuildItem; 14 | import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; 15 | import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem; 16 | import io.quarkus.deployment.metrics.MetricsFactoryConsumerBuildItem; 17 | import io.quarkus.neo4j.runtime.Neo4jConfiguration; 18 | import io.quarkus.neo4j.runtime.Neo4jDriverRecorder; 19 | import io.quarkus.runtime.RuntimeValue; 20 | import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; 21 | 22 | class Neo4jDriverProcessor { 23 | 24 | @BuildStep 25 | FeatureBuildItem createFeature(BuildProducer extensionSslNativeSupport) { 26 | 27 | // Indicates that this extension would like the SSL support to be enabled 28 | extensionSslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(Feature.NEO4J)); 29 | 30 | return new FeatureBuildItem(Feature.NEO4J); 31 | } 32 | 33 | @BuildStep 34 | @Record(ExecutionTime.RUNTIME_INIT) 35 | Neo4jDriverBuildItem configureDriverProducer(Neo4jDriverRecorder recorder, 36 | BuildProducer syntheticBeans, 37 | Neo4jConfiguration configuration, 38 | ShutdownContextBuildItem shutdownContext) { 39 | 40 | RuntimeValue driverHolder = recorder.initializeDriver(configuration, shutdownContext); 41 | syntheticBeans 42 | .produce(SyntheticBeanBuildItem.configure(Driver.class).runtimeValue(driverHolder).setRuntimeInit().done()); 43 | 44 | return new Neo4jDriverBuildItem(driverHolder); 45 | } 46 | 47 | @BuildStep 48 | HealthBuildItem addHealthCheck(Neo4jBuildTimeConfig buildTimeConfig) { 49 | return new HealthBuildItem("io.quarkus.neo4j.runtime.health.Neo4jHealthCheck", 50 | buildTimeConfig.healthEnabled()); 51 | } 52 | 53 | @BuildStep 54 | @Record(ExecutionTime.RUNTIME_INIT) 55 | void metrics(Neo4jConfiguration configuration, 56 | Neo4jDriverRecorder recorder, 57 | BuildProducer metrics) { 58 | metrics.produce(new MetricsFactoryConsumerBuildItem(recorder.registerMetrics(configuration))); 59 | } 60 | 61 | @BuildStep 62 | RuntimeInitializedPackageBuildItem deferNettySSLToRuntime() { 63 | return new RuntimeInitializedPackageBuildItem("io.netty.handler.ssl"); 64 | } 65 | 66 | @BuildStep 67 | RuntimeInitializedPackageBuildItem deferBoltConnectionImplToRuntime() { 68 | return new RuntimeInitializedPackageBuildItem("org.neo4j.bolt.connection.netty.impl.async.connection"); 69 | } 70 | 71 | @BuildStep 72 | void deferMiscellaneousClassesToRuntime(BuildProducer classes) { 73 | 74 | // Those are the ones we use, there are more in the package and if we move the whole package some Quarkus stuff 75 | // would need to be deferred too 76 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.buffer.AbstractReferenceCountedByteBuf")); 77 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.buffer.ByteBufAllocator")); 78 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.buffer.ByteBufUtil")); 79 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.buffer.ByteBufUtil$HexUtil")); 80 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.buffer.PooledByteBufAllocator")); 81 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.buffer.UnpooledHeapByteBuf")); 82 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.buffer.UnreleasableByteBuf")); 83 | 84 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.handler.codec.compression.ZstdConstants")); 85 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.internal.tcnative.SSL")); 86 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.util.AbstractReferenceCounted")); 87 | classes.produce(new RuntimeInitializedClassBuildItem("io.netty.util.internal.logging.Log4JLogger")); 88 | 89 | // See https://github.com/quarkiverse/quarkus-neo4j/pull/208#issuecomment-1784849182 90 | classes.produce(new RuntimeInitializedClassBuildItem("io.vertx.core.net.impl.SSLHelper")); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /deployment/src/main/resources/io/quarkus/neo4j/deployment/neo4j_dev_services_ext.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | while [ ! -f /testcontainers_env ]; do sleep 0.1; done; 3 | source /testcontainers_env 4 | add_env_setting_to_conf "dbms.connector.bolt.advertised_address" "${NEO4J_dbms_connector_bolt_advertised__address}" "${NEO4J_HOME}" 5 | -------------------------------------------------------------------------------- /deployment/src/test/java/io/quarkus/neo4j/deployment/Neo4jDevModeTests.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 5 | import static org.assertj.core.api.Assertions.assertThatNoException; 6 | 7 | import java.io.IOException; 8 | import java.io.UncheckedIOException; 9 | import java.net.ServerSocket; 10 | import java.util.function.Predicate; 11 | import java.util.logging.LogRecord; 12 | 13 | import jakarta.inject.Inject; 14 | 15 | import org.junit.jupiter.api.AfterAll; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.extension.RegisterExtension; 18 | import org.neo4j.driver.Driver; 19 | import org.neo4j.driver.exceptions.ServiceUnavailableException; 20 | import org.testcontainers.junit.jupiter.Testcontainers; 21 | 22 | import io.quarkus.test.QuarkusUnitTest; 23 | 24 | public class Neo4jDevModeTests { 25 | 26 | @Testcontainers(disabledWithoutDocker = true) 27 | static class DevServicesShouldStartNeo4jTest { 28 | 29 | @RegisterExtension 30 | static QuarkusUnitTest test = new QuarkusUnitTest() 31 | .withEmptyApplication() 32 | .withConfigurationResource("application.properties") 33 | .overrideConfigKey("quarkus.neo4j.devservices.additional-env.NEO4J_PLUGINS", "[\"apoc\"]") 34 | .setLogRecordPredicate(record -> true) 35 | .assertLogRecords(records -> assertThat(records).extracting(LogRecord::getMessage) 36 | .contains("Dev Services started a Neo4j container reachable at %s")); 37 | 38 | @Inject 39 | Driver driver; 40 | 41 | @Test 42 | public void shouldBeAbleToConnect() { 43 | 44 | assertThatNoException().isThrownBy(() -> driver.verifyConnectivity()); 45 | try (var session = driver.session()) { 46 | var apoc = session.run("RETURN apoc.version() AS output").single().get(0).asString(); 47 | assertThat(apoc).isNotNull().startsWith("5."); 48 | } 49 | } 50 | } 51 | 52 | static Predicate recordMatches(String message, String port) { 53 | return r -> message.equals(r.getMessage()) && r.getParameters().length > 0 54 | && r.getParameters()[0] instanceof String && ((String) r.getParameters()[0]).contains(port); 55 | } 56 | 57 | @Testcontainers(disabledWithoutDocker = true) 58 | static class DevServicesShouldBeAbleToUseFixedPortsTest { 59 | 60 | // Let it burn when there's no free port 61 | static final String FIXED_BOLD_PORT = PortUtils.findFreePort().get().toString(); 62 | static final String FIXED_HTTP_PORT = PortUtils.findFreePort().get().toString(); 63 | 64 | @RegisterExtension 65 | static QuarkusUnitTest test = new QuarkusUnitTest() 66 | .withEmptyApplication() 67 | .withConfigurationResource("application.properties") 68 | .overrideConfigKey("quarkus.neo4j.devservices.bolt-port", FIXED_BOLD_PORT) 69 | .overrideConfigKey("quarkus.neo4j.devservices.http-port", FIXED_HTTP_PORT) 70 | .setAllowTestClassOutsideDeployment(true) // Needed to use the PortUtils above. 71 | .setLogRecordPredicate(record -> true) 72 | .assertLogRecords(records -> assertThat(records) 73 | .isNotEmpty() 74 | .anyMatch(recordMatches("Dev Services started a Neo4j container reachable at %s", FIXED_BOLD_PORT)) 75 | .anyMatch(recordMatches("Neo4j Browser is reachable at %s", FIXED_HTTP_PORT))); 76 | 77 | @Inject 78 | Driver driver; 79 | 80 | @Test 81 | public void shouldBeAbleToConnect() { 82 | 83 | assertThatNoException().isThrownBy(() -> driver.verifyConnectivity()); 84 | } 85 | } 86 | 87 | @Testcontainers(disabledWithoutDocker = true) 88 | static class DevServicesShouldNotFailWhen7474IsUsedTest { 89 | 90 | static ServerSocket blockerFor7474; 91 | 92 | static void block7474() { 93 | 94 | if (PortUtils.isFree(7474)) { 95 | try { 96 | blockerFor7474 = new ServerSocket(7474); 97 | } catch (IOException e) { 98 | throw new UncheckedIOException(e); 99 | } 100 | } else { 101 | blockerFor7474 = null; 102 | } 103 | } 104 | 105 | static void unblock7474() { 106 | 107 | if (blockerFor7474 != null && !blockerFor7474.isClosed()) { 108 | try { 109 | blockerFor7474.close(); 110 | } catch (IOException e) { 111 | throw new UncheckedIOException(e); 112 | } 113 | } 114 | } 115 | 116 | @RegisterExtension 117 | static QuarkusUnitTest test = new QuarkusUnitTest() 118 | .withEmptyApplication() 119 | .withConfigurationResource("application.properties") 120 | .setBeforeAllCustomizer(DevServicesShouldNotFailWhen7474IsUsedTest::block7474) 121 | .setAfterAllCustomizer(DevServicesShouldNotFailWhen7474IsUsedTest::unblock7474) 122 | .setLogRecordPredicate(record -> true) 123 | .assertLogRecords(records -> assertThat(records) 124 | .isNotEmpty() 125 | .noneMatch(recordMatches("Neo4j Browser is reachable at %s", "7474"))); 126 | 127 | @Inject 128 | Driver driver; 129 | 130 | @Test 131 | public void shouldBeAbleToConnect() { 132 | 133 | assertThatNoException().isThrownBy(() -> driver.verifyConnectivity()); 134 | } 135 | } 136 | 137 | @Testcontainers(disabledWithoutDocker = true) 138 | static class WorkingWithDifferentImageAndAdditionalEnvTest { 139 | 140 | @RegisterExtension 141 | static QuarkusUnitTest test = new QuarkusUnitTest() 142 | .withEmptyApplication() 143 | .setLogRecordPredicate(record -> true) 144 | .withConfigurationResource("application.properties") 145 | .overrideConfigKey("quarkus.neo4j.devservices.image-name", "neo4j:4.4-enterprise") 146 | .overrideConfigKey("quarkus.neo4j.devservices.additional-env.NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes") 147 | .assertLogRecords(records -> assertThat(records).extracting(LogRecord::getMessage) 148 | .contains("Dev Services started a Neo4j container reachable at %s")); 149 | 150 | @Inject 151 | Driver driver; 152 | 153 | @Test 154 | public void shouldBeAbleToConnect() { 155 | 156 | assertThatNoException().isThrownBy(() -> driver.verifyConnectivity()); 157 | try (var session = driver.session()) { 158 | var cypher = "CALL dbms.components() YIELD versions, name, edition WHERE name = 'Neo4j Kernel' RETURN edition, versions[0] as version"; 159 | var result = session.run(cypher).single(); 160 | assertThat(result.get("edition").asString()).isEqualToIgnoringCase("enterprise"); 161 | } 162 | } 163 | } 164 | 165 | static class WithLocallyDisabledDevServicesTest { 166 | 167 | @RegisterExtension 168 | static QuarkusUnitTest test = new QuarkusUnitTest() 169 | .withEmptyApplication() 170 | .setLogRecordPredicate(record -> true) 171 | .withConfigurationResource("application.properties") 172 | .overrideConfigKey("quarkus.neo4j.devservices.enabled", "false") 173 | .assertLogRecords(records -> assertThat(records).extracting(LogRecord::getMessage) 174 | .contains("Not starting Dev Services for Neo4j, as it has been disabled in the config.")); 175 | 176 | @Inject 177 | Driver driver; 178 | 179 | @Test 180 | public void shouldNotBeAbleToConnect() { 181 | 182 | assertThatExceptionOfType(ServiceUnavailableException.class).isThrownBy(() -> driver.verifyConnectivity()); 183 | } 184 | } 185 | 186 | static class WithGloballyDisabledDevServicesTest { 187 | 188 | @RegisterExtension 189 | static QuarkusUnitTest test = new QuarkusUnitTest() 190 | .withEmptyApplication() 191 | .setLogRecordPredicate(record -> true) 192 | .withConfigurationResource("application.properties") 193 | .overrideConfigKey("quarkus.devservices.enabled", "false"); 194 | 195 | @Inject 196 | Driver driver; 197 | 198 | @Test 199 | public void shouldNotBeAbleToConnect() { 200 | 201 | assertThatExceptionOfType(ServiceUnavailableException.class).isThrownBy(() -> driver.verifyConnectivity()); 202 | } 203 | } 204 | 205 | @Testcontainers(disabledWithoutDocker = true) 206 | static class WithAlreadyReachableInstanceTest { 207 | 208 | static { 209 | // Make our check think that bolt is locally reachable 210 | System.setProperty("io.quarkus.neo4j.deployment.devservices.assumeBoltIsReachable", "true"); 211 | } 212 | 213 | @RegisterExtension 214 | static QuarkusUnitTest test = new QuarkusUnitTest() 215 | .withEmptyApplication() 216 | .setLogRecordPredicate(record -> true) 217 | .withConfigurationResource("application.properties") 218 | .overrideConfigKey("quarkus.neo4j.devservices.bolt-port", "7687") 219 | .assertLogRecords(records -> assertThat(records).extracting(LogRecord::getMessage) 220 | .anyMatch(s -> s.startsWith( 221 | "Not starting Dev Services for Neo4j, as the configuration requests the same fixed bolt port."))); 222 | 223 | @AfterAll 224 | static void deleteSystemPropertyAgain() { 225 | System.setProperty("io.quarkus.neo4j.deployment.devservices.assumeBoltIsReachable", ""); 226 | } 227 | 228 | @Inject 229 | Driver driver; 230 | 231 | @Test 232 | public void shouldNotBeAbleToConnect() { 233 | 234 | assertThatExceptionOfType(ServiceUnavailableException.class).isThrownBy(() -> driver.verifyConnectivity()); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /deployment/src/test/java/io/quarkus/neo4j/deployment/PortUtils.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.deployment; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import java.util.Optional; 6 | 7 | /** 8 | * Please keep it public to make the Quarkus class loader happy during test. 9 | */ 10 | public final class PortUtils { 11 | 12 | public static boolean isFree(int port) { 13 | try (ServerSocket ignored = new ServerSocket(port)) { 14 | return true; 15 | } catch (IOException e) { 16 | return false; 17 | } 18 | } 19 | 20 | public static Optional findFreePort() { 21 | 22 | // There is always a chance that the port number will be allocated between 23 | // the moment it was free and when the container is started, but that's a 24 | // risk to agree on to enable a frictionless Neo4j browser experience without using 25 | // a fixed bolt port. 26 | try (var socket = new ServerSocket(0)) { 27 | return Optional.of(socket.getLocalPort()); 28 | } catch (IOException e) { 29 | return Optional.empty(); 30 | } 31 | } 32 | 33 | private PortUtils() { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /deployment/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | # I much prefer just setting this, but I was not able to capture 2 | # debug log with QuarkusUnitTest and the InMemoryLogHandler without... 3 | quarkus.log.category."io.quarkus.neo4j.deployment".level=DEBUG 4 | 5 | # Forcing the whole of Quarkus into logging 6 | quarkus.log.level=DEBUG 7 | # And then taming console again 8 | quarkus.log.console.level=INFO 9 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: quarkus-neo4j 2 | title: Neo4j 3 | version: dev 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/assets/images/.keepme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkiverse/quarkus-neo4j/6d4d1477074ca9e27baf800508f17633deea28c5/docs/modules/ROOT/assets/images/.keepme -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/Fruit.java: -------------------------------------------------------------------------------- 1 | ../../../../integration-tests/src/main/java/io/quarkus/it/neo4j/Fruit.java -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/FruitResource.java: -------------------------------------------------------------------------------- 1 | ../../../../integration-tests/src/main/java/io/quarkus/it/neo4j/FruitResource.java -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/ReactiveFruitResource.java: -------------------------------------------------------------------------------- 1 | ../../../../integration-tests/src/main/java/io/quarkus/it/neo4j/ReactiveFruitResource.java -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Quarkus - Neo4j] 2 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/includes/attributes.adoc: -------------------------------------------------------------------------------- 1 | :quarkus-version: 3.22.1 2 | :quarkus-neo4j-version: 5.4.0 3 | :maven-version: 3.8.1+ 4 | 5 | :quarkus-org-url: https://github.com/quarkusio 6 | :quarkus-base-url: {quarkus-org-url}/quarkus 7 | :quarkus-clone-url: {quarkus-base-url}.git 8 | :quarkus-archive-url: {quarkus-base-url}/archive/master.zip 9 | :quarkus-tree-url: {quarkus-base-url}/tree/main 10 | :quarkus-issues-url: {quarkus-base-url}/issues 11 | :quarkus-guides-url: https://quarkus.io/guides 12 | :quickstarts-base-url: https://github.com/quarkusio/quarkus-quickstarts 13 | :quickstarts-clone-url: https://github.com/quarkusio/quarkus-quickstarts.git 14 | :quickstarts-archive-url: https://github.com/quarkusio/quarkus-quickstarts/archive/main.zip 15 | :quickstarts-blob-url: https://github.com/quarkusio/quarkus-quickstarts/blob/main 16 | :quickstarts-tree-url: https://github.com/quarkusio/quarkus-quickstarts/tree/main 17 | 18 | :examples-dir: ./../examples/ 19 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/includes/quarkus-neo4j-config-group-dev-services-build-time-config.adoc: -------------------------------------------------------------------------------- 1 | 2 | :summaryTableId: quarkus-neo4j-config-group-dev-services-build-time-config 3 | [.configuration-legend] 4 | icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime 5 | [.configuration-reference, cols="80,.^10,.^10"] 6 | |=== 7 | 8 | h|[[quarkus-neo4j-config-group-dev-services-build-time-config_configuration]]link:#quarkus-neo4j-config-group-dev-services-build-time-config_configuration[Configuration property] 9 | 10 | h|Type 11 | h|Default 12 | 13 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-enabled]]`link:#quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-enabled[quarkus.neo4j.devservices.enabled]` 14 | 15 | 16 | [.description] 17 | -- 18 | If DevServices has been explicitly enabled or disabled. DevServices is generally enabled by default, unless there is an existing configuration present. When DevServices is enabled Quarkus will attempt to automatically configure and start a database when running in Dev or Test mode. 19 | 20 | ifdef::add-copy-button-to-env-var[] 21 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_ENABLED+++[] 22 | endif::add-copy-button-to-env-var[] 23 | ifndef::add-copy-button-to-env-var[] 24 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_ENABLED+++` 25 | endif::add-copy-button-to-env-var[] 26 | --|boolean 27 | | 28 | 29 | 30 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-image-name]]`link:#quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-image-name[quarkus.neo4j.devservices.image-name]` 31 | 32 | 33 | [.description] 34 | -- 35 | The container image name to use, for container based DevServices providers. 36 | 37 | ifdef::add-copy-button-to-env-var[] 38 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_IMAGE_NAME+++[] 39 | endif::add-copy-button-to-env-var[] 40 | ifndef::add-copy-button-to-env-var[] 41 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_IMAGE_NAME+++` 42 | endif::add-copy-button-to-env-var[] 43 | --|string 44 | |`neo4j:5` 45 | 46 | 47 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-bolt-port]]`link:#quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-bolt-port[quarkus.neo4j.devservices.bolt-port]` 48 | 49 | 50 | [.description] 51 | -- 52 | This value can be used to specify the port to which the bolt-port of the container is exposed. It must be a free port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be logged on which port the Neo4j container is reachable over bolt. 53 | 54 | ifdef::add-copy-button-to-env-var[] 55 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_BOLT_PORT+++[] 56 | endif::add-copy-button-to-env-var[] 57 | ifndef::add-copy-button-to-env-var[] 58 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_BOLT_PORT+++` 59 | endif::add-copy-button-to-env-var[] 60 | --|int 61 | | 62 | 63 | 64 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-http-port]]`link:#quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-http-port[quarkus.neo4j.devservices.http-port]` 65 | 66 | 67 | [.description] 68 | -- 69 | This value can be used to specify the port to which the http-port of the container is exposed. It must be a free port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be logged on which port the Neo4j Browser is available. 70 | 71 | ifdef::add-copy-button-to-env-var[] 72 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_HTTP_PORT+++[] 73 | endif::add-copy-button-to-env-var[] 74 | ifndef::add-copy-button-to-env-var[] 75 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_HTTP_PORT+++` 76 | endif::add-copy-button-to-env-var[] 77 | --|int 78 | | 79 | 80 | 81 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-additional-env-additional-env]]`link:#quarkus-neo4j-config-group-dev-services-build-time-config_quarkus-neo4j-devservices-additional-env-additional-env[quarkus.neo4j.devservices.additional-env."additional-env"]` 82 | 83 | 84 | [.description] 85 | -- 86 | Additional environment entries that can be added to the container before its start. 87 | 88 | ifdef::add-copy-button-to-env-var[] 89 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_ADDITIONAL_ENV__ADDITIONAL_ENV_+++[] 90 | endif::add-copy-button-to-env-var[] 91 | ifndef::add-copy-button-to-env-var[] 92 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_ADDITIONAL_ENV__ADDITIONAL_ENV_+++` 93 | endif::add-copy-button-to-env-var[] 94 | --|link:https://docs.oracle.com/javase/8/docs/api/java/lang/String.html[String] 95 | 96 | | 97 | 98 | |=== -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/includes/quarkus-neo4j.adoc: -------------------------------------------------------------------------------- 1 | [.configuration-legend] 2 | icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime 3 | [.configuration-reference.searchable, cols="80,.^10,.^10"] 4 | |=== 5 | 6 | h|[.header-title]##Configuration property## 7 | h|Type 8 | h|Default 9 | 10 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-health-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-health-enabled[`quarkus.neo4j.health.enabled`]## 11 | ifdef::add-copy-button-to-config-props[] 12 | config_property_copy_button:+++quarkus.neo4j.health.enabled+++[] 13 | endif::add-copy-button-to-config-props[] 14 | 15 | 16 | [.description] 17 | -- 18 | whether a health check is published in case the smallrye-health extension is present 19 | 20 | 21 | ifdef::add-copy-button-to-env-var[] 22 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_HEALTH_ENABLED+++[] 23 | endif::add-copy-button-to-env-var[] 24 | ifndef::add-copy-button-to-env-var[] 25 | Environment variable: `+++QUARKUS_NEO4J_HEALTH_ENABLED+++` 26 | endif::add-copy-button-to-env-var[] 27 | -- 28 | |boolean 29 | |`true` 30 | 31 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-enabled[`quarkus.neo4j.devservices.enabled`]## 32 | ifdef::add-copy-button-to-config-props[] 33 | config_property_copy_button:+++quarkus.neo4j.devservices.enabled+++[] 34 | endif::add-copy-button-to-config-props[] 35 | 36 | 37 | [.description] 38 | -- 39 | If DevServices has been explicitly enabled or disabled. DevServices is generally enabled by default, unless there is an existing configuration present. When DevServices is enabled Quarkus will attempt to automatically configure and start a database when running in Dev or Test mode. 40 | 41 | 42 | ifdef::add-copy-button-to-env-var[] 43 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_ENABLED+++[] 44 | endif::add-copy-button-to-env-var[] 45 | ifndef::add-copy-button-to-env-var[] 46 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_ENABLED+++` 47 | endif::add-copy-button-to-env-var[] 48 | -- 49 | |boolean 50 | | 51 | 52 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-image-name]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-image-name[`quarkus.neo4j.devservices.image-name`]## 53 | ifdef::add-copy-button-to-config-props[] 54 | config_property_copy_button:+++quarkus.neo4j.devservices.image-name+++[] 55 | endif::add-copy-button-to-config-props[] 56 | 57 | 58 | [.description] 59 | -- 60 | the container image name to use, for container based DevServices providers 61 | 62 | 63 | ifdef::add-copy-button-to-env-var[] 64 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_IMAGE_NAME+++[] 65 | endif::add-copy-button-to-env-var[] 66 | ifndef::add-copy-button-to-env-var[] 67 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_IMAGE_NAME+++` 68 | endif::add-copy-button-to-env-var[] 69 | -- 70 | |string 71 | |`neo4j:5` 72 | 73 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-additional-env-additional-env]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-additional-env-additional-env[`quarkus.neo4j.devservices.additional-env."additional-env"`]## 74 | ifdef::add-copy-button-to-config-props[] 75 | config_property_copy_button:+++quarkus.neo4j.devservices.additional-env."additional-env"+++[] 76 | endif::add-copy-button-to-config-props[] 77 | 78 | 79 | [.description] 80 | -- 81 | additional environment entries that can be added to the container before its start 82 | 83 | 84 | ifdef::add-copy-button-to-env-var[] 85 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_ADDITIONAL_ENV__ADDITIONAL_ENV_+++[] 86 | endif::add-copy-button-to-env-var[] 87 | ifndef::add-copy-button-to-env-var[] 88 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_ADDITIONAL_ENV__ADDITIONAL_ENV_+++` 89 | endif::add-copy-button-to-env-var[] 90 | -- 91 | |Map 92 | | 93 | 94 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-bolt-port]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-bolt-port[`quarkus.neo4j.devservices.bolt-port`]## 95 | ifdef::add-copy-button-to-config-props[] 96 | config_property_copy_button:+++quarkus.neo4j.devservices.bolt-port+++[] 97 | endif::add-copy-button-to-config-props[] 98 | 99 | 100 | [.description] 101 | -- 102 | This value can be used to specify the port to which the bolt-port of the container is exposed. It must be a free port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be logged on which port the Neo4j container is reachable over bolt. 103 | 104 | Ignored when container sharing is enabled. 105 | 106 | 107 | ifdef::add-copy-button-to-env-var[] 108 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_BOLT_PORT+++[] 109 | endif::add-copy-button-to-env-var[] 110 | ifndef::add-copy-button-to-env-var[] 111 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_BOLT_PORT+++` 112 | endif::add-copy-button-to-env-var[] 113 | -- 114 | |int 115 | | 116 | 117 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-http-port]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-http-port[`quarkus.neo4j.devservices.http-port`]## 118 | ifdef::add-copy-button-to-config-props[] 119 | config_property_copy_button:+++quarkus.neo4j.devservices.http-port+++[] 120 | endif::add-copy-button-to-config-props[] 121 | 122 | 123 | [.description] 124 | -- 125 | This value can be used to specify the port to which the http-port of the container is exposed. It must be a free port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be logged on which port the Neo4j Browser is available. 126 | 127 | Ignored when container sharing is enabled. 128 | 129 | 130 | ifdef::add-copy-button-to-env-var[] 131 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_HTTP_PORT+++[] 132 | endif::add-copy-button-to-env-var[] 133 | ifndef::add-copy-button-to-env-var[] 134 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_HTTP_PORT+++` 135 | endif::add-copy-button-to-env-var[] 136 | -- 137 | |int 138 | | 139 | 140 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-shared]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-shared[`quarkus.neo4j.devservices.shared`]## 141 | ifdef::add-copy-button-to-config-props[] 142 | config_property_copy_button:+++quarkus.neo4j.devservices.shared+++[] 143 | endif::add-copy-button-to-config-props[] 144 | 145 | 146 | [.description] 147 | -- 148 | Indicates if the Neo4j server managed by Quarkus Dev Services is shared. When shared, Quarkus looks for running containers using label-based service discovery. If a matching container is found, it is used, and so a second one is not started. Otherwise, Dev Services for Neo4j starts a new container. 149 | 150 | The discovery uses the `quarkus-dev-service-neo4j` label. The value is configured using the `service-name` property. 151 | 152 | Container sharing is only used in dev mode and disabled by default 153 | 154 | 155 | ifdef::add-copy-button-to-env-var[] 156 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_SHARED+++[] 157 | endif::add-copy-button-to-env-var[] 158 | ifndef::add-copy-button-to-env-var[] 159 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_SHARED+++` 160 | endif::add-copy-button-to-env-var[] 161 | -- 162 | |boolean 163 | |`false` 164 | 165 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-service-name]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-service-name[`quarkus.neo4j.devservices.service-name`]## 166 | ifdef::add-copy-button-to-config-props[] 167 | config_property_copy_button:+++quarkus.neo4j.devservices.service-name+++[] 168 | endif::add-copy-button-to-config-props[] 169 | 170 | 171 | [.description] 172 | -- 173 | The value of the `quarkus-dev-service-neo4j` label attached to the started container. This property is used when `shared` is set to `true`. In this case, before starting a container, Dev Services for Neo4j looks for a container with the `quarkus-dev-service-neo4j` label set to the configured value. If found, it will use this container instead of starting a new one. Otherwise, it starts a new container with the `quarkus-dev-service-neo4j` label set to the specified value. 174 | 175 | This property is used when you need multiple shared Neo4j servers. 176 | 177 | 178 | ifdef::add-copy-button-to-env-var[] 179 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_SERVICE_NAME+++[] 180 | endif::add-copy-button-to-env-var[] 181 | ifndef::add-copy-button-to-env-var[] 182 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_SERVICE_NAME+++` 183 | endif::add-copy-button-to-env-var[] 184 | -- 185 | |string 186 | |`neo4j` 187 | 188 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-shared-password]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-shared-password[`quarkus.neo4j.devservices.shared-password`]## 189 | ifdef::add-copy-button-to-config-props[] 190 | config_property_copy_button:+++quarkus.neo4j.devservices.shared-password+++[] 191 | endif::add-copy-button-to-config-props[] 192 | 193 | 194 | [.description] 195 | -- 196 | This property is used only when you create multiple shared Neo4j servers 197 | 198 | 199 | ifdef::add-copy-button-to-env-var[] 200 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_SHARED_PASSWORD+++[] 201 | endif::add-copy-button-to-env-var[] 202 | ifndef::add-copy-button-to-env-var[] 203 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_SHARED_PASSWORD+++` 204 | endif::add-copy-button-to-env-var[] 205 | -- 206 | |string 207 | |`verysecret` 208 | 209 | a| [[quarkus-neo4j_quarkus-neo4j-uri]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-uri[`quarkus.neo4j.uri`]## 210 | ifdef::add-copy-button-to-config-props[] 211 | config_property_copy_button:+++quarkus.neo4j.uri+++[] 212 | endif::add-copy-button-to-config-props[] 213 | 214 | 215 | [.description] 216 | -- 217 | the uri this driver should connect to. The driver supports bolt, bolt{plus}routing or neo4j as schemes 218 | 219 | 220 | ifdef::add-copy-button-to-env-var[] 221 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_URI+++[] 222 | endif::add-copy-button-to-env-var[] 223 | ifndef::add-copy-button-to-env-var[] 224 | Environment variable: `+++QUARKUS_NEO4J_URI+++` 225 | endif::add-copy-button-to-env-var[] 226 | -- 227 | |string 228 | |`bolt://localhost:7687` 229 | 230 | a| [[quarkus-neo4j_quarkus-neo4j-encrypted]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-encrypted[`quarkus.neo4j.encrypted`]## 231 | ifdef::add-copy-button-to-config-props[] 232 | config_property_copy_button:+++quarkus.neo4j.encrypted+++[] 233 | endif::add-copy-button-to-config-props[] 234 | 235 | 236 | [.description] 237 | -- 238 | if the driver should use encrypted traffic 239 | 240 | 241 | ifdef::add-copy-button-to-env-var[] 242 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_ENCRYPTED+++[] 243 | endif::add-copy-button-to-env-var[] 244 | ifndef::add-copy-button-to-env-var[] 245 | Environment variable: `+++QUARKUS_NEO4J_ENCRYPTED+++` 246 | endif::add-copy-button-to-env-var[] 247 | -- 248 | |boolean 249 | |`false` 250 | 251 | a| [[quarkus-neo4j_quarkus-neo4j-max-transaction-retry-time]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-max-transaction-retry-time[`quarkus.neo4j.max-transaction-retry-time`]## 252 | ifdef::add-copy-button-to-config-props[] 253 | config_property_copy_button:+++quarkus.neo4j.max-transaction-retry-time+++[] 254 | endif::add-copy-button-to-config-props[] 255 | 256 | 257 | [.description] 258 | -- 259 | the maximum time transactions are allowed to retry 260 | 261 | 262 | ifdef::add-copy-button-to-env-var[] 263 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_MAX_TRANSACTION_RETRY_TIME+++[] 264 | endif::add-copy-button-to-env-var[] 265 | ifndef::add-copy-button-to-env-var[] 266 | Environment variable: `+++QUARKUS_NEO4J_MAX_TRANSACTION_RETRY_TIME+++` 267 | endif::add-copy-button-to-env-var[] 268 | -- 269 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 270 | |`30S` 271 | 272 | h|[[quarkus-neo4j_section_quarkus-neo4j-authentication]] [.section-name.section-level0]##link:#quarkus-neo4j_section_quarkus-neo4j-authentication[return the authentication++}++]## 273 | h|Type 274 | h|Default 275 | 276 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-username]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-username[`quarkus.neo4j.authentication.username`]## 277 | ifdef::add-copy-button-to-config-props[] 278 | config_property_copy_button:+++quarkus.neo4j.authentication.username+++[] 279 | endif::add-copy-button-to-config-props[] 280 | 281 | 282 | [.description] 283 | -- 284 | the login of the user connecting to the database 285 | 286 | 287 | ifdef::add-copy-button-to-env-var[] 288 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_USERNAME+++[] 289 | endif::add-copy-button-to-env-var[] 290 | ifndef::add-copy-button-to-env-var[] 291 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_USERNAME+++` 292 | endif::add-copy-button-to-env-var[] 293 | -- 294 | |string 295 | |`neo4j` 296 | 297 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-password]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-password[`quarkus.neo4j.authentication.password`]## 298 | ifdef::add-copy-button-to-config-props[] 299 | config_property_copy_button:+++quarkus.neo4j.authentication.password+++[] 300 | endif::add-copy-button-to-config-props[] 301 | 302 | 303 | [.description] 304 | -- 305 | the password of the user connecting to the database 306 | 307 | 308 | ifdef::add-copy-button-to-env-var[] 309 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_PASSWORD+++[] 310 | endif::add-copy-button-to-env-var[] 311 | ifndef::add-copy-button-to-env-var[] 312 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_PASSWORD+++` 313 | endif::add-copy-button-to-env-var[] 314 | -- 315 | |string 316 | |`neo4j` 317 | 318 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-disabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-disabled[`quarkus.neo4j.authentication.disabled`]## 319 | ifdef::add-copy-button-to-config-props[] 320 | config_property_copy_button:+++quarkus.neo4j.authentication.disabled+++[] 321 | endif::add-copy-button-to-config-props[] 322 | 323 | 324 | [.description] 325 | -- 326 | whether disable authentication or not 327 | 328 | 329 | ifdef::add-copy-button-to-env-var[] 330 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_DISABLED+++[] 331 | endif::add-copy-button-to-env-var[] 332 | ifndef::add-copy-button-to-env-var[] 333 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_DISABLED+++` 334 | endif::add-copy-button-to-env-var[] 335 | -- 336 | |boolean 337 | |`false` 338 | 339 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-value]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-value[`quarkus.neo4j.authentication.value`]## 340 | ifdef::add-copy-button-to-config-props[] 341 | config_property_copy_button:+++quarkus.neo4j.authentication.value+++[] 342 | endif::add-copy-button-to-config-props[] 343 | 344 | 345 | [.description] 346 | -- 347 | An optional field that when is not empty has precedence over `username` and `password`. It behaves the same way as `NEO4J_AUTH` in the official docker image, containing both the username and password separated via a single forward slash (`/`). 348 | 349 | 350 | ifdef::add-copy-button-to-env-var[] 351 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_VALUE+++[] 352 | endif::add-copy-button-to-env-var[] 353 | ifndef::add-copy-button-to-env-var[] 354 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_VALUE+++` 355 | endif::add-copy-button-to-env-var[] 356 | -- 357 | |string 358 | | 359 | 360 | 361 | h|[[quarkus-neo4j_section_quarkus-neo4j-trust-settings]] [.section-name.section-level0]##link:#quarkus-neo4j_section_quarkus-neo4j-trust-settings[return the trust settings for encrypted traffic++}++]## 362 | h|Type 363 | h|Default 364 | 365 | a| [[quarkus-neo4j_quarkus-neo4j-trust-settings-strategy]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-trust-settings-strategy[`quarkus.neo4j.trust-settings.strategy`]## 366 | ifdef::add-copy-button-to-config-props[] 367 | config_property_copy_button:+++quarkus.neo4j.trust-settings.strategy+++[] 368 | endif::add-copy-button-to-config-props[] 369 | 370 | 371 | [.description] 372 | -- 373 | which trust strategy to apply when using encrypted traffic 374 | 375 | 376 | ifdef::add-copy-button-to-env-var[] 377 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_TRUST_SETTINGS_STRATEGY+++[] 378 | endif::add-copy-button-to-env-var[] 379 | ifndef::add-copy-button-to-env-var[] 380 | Environment variable: `+++QUARKUS_NEO4J_TRUST_SETTINGS_STRATEGY+++` 381 | endif::add-copy-button-to-env-var[] 382 | -- 383 | a|`trust-all-certificates`, `trust-custom-ca-signed-certificates`, `trust-system-ca-signed-certificates` 384 | |`trust-system-ca-signed-certificates` 385 | 386 | a| [[quarkus-neo4j_quarkus-neo4j-trust-settings-cert-file]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-trust-settings-cert-file[`quarkus.neo4j.trust-settings.cert-file`]## 387 | ifdef::add-copy-button-to-config-props[] 388 | config_property_copy_button:+++quarkus.neo4j.trust-settings.cert-file+++[] 389 | endif::add-copy-button-to-config-props[] 390 | 391 | 392 | [.description] 393 | -- 394 | the file of the certificate to use 395 | 396 | 397 | ifdef::add-copy-button-to-env-var[] 398 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_TRUST_SETTINGS_CERT_FILE+++[] 399 | endif::add-copy-button-to-env-var[] 400 | ifndef::add-copy-button-to-env-var[] 401 | Environment variable: `+++QUARKUS_NEO4J_TRUST_SETTINGS_CERT_FILE+++` 402 | endif::add-copy-button-to-env-var[] 403 | -- 404 | |path 405 | | 406 | 407 | a| [[quarkus-neo4j_quarkus-neo4j-trust-settings-hostname-verification-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-trust-settings-hostname-verification-enabled[`quarkus.neo4j.trust-settings.hostname-verification-enabled`]## 408 | ifdef::add-copy-button-to-config-props[] 409 | config_property_copy_button:+++quarkus.neo4j.trust-settings.hostname-verification-enabled+++[] 410 | endif::add-copy-button-to-config-props[] 411 | 412 | 413 | [.description] 414 | -- 415 | whether hostname verification is used 416 | 417 | 418 | ifdef::add-copy-button-to-env-var[] 419 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_TRUST_SETTINGS_HOSTNAME_VERIFICATION_ENABLED+++[] 420 | endif::add-copy-button-to-env-var[] 421 | ifndef::add-copy-button-to-env-var[] 422 | Environment variable: `+++QUARKUS_NEO4J_TRUST_SETTINGS_HOSTNAME_VERIFICATION_ENABLED+++` 423 | endif::add-copy-button-to-env-var[] 424 | -- 425 | |boolean 426 | |`false` 427 | 428 | 429 | h|[[quarkus-neo4j_section_quarkus-neo4j-pool]] [.section-name.section-level0]##link:#quarkus-neo4j_section_quarkus-neo4j-pool[return the connection pool++}++]## 430 | h|Type 431 | h|Default 432 | 433 | a| [[quarkus-neo4j_quarkus-neo4j-pool-metrics-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-metrics-enabled[`quarkus.neo4j.pool.metrics.enabled`]## 434 | ifdef::add-copy-button-to-config-props[] 435 | config_property_copy_button:+++quarkus.neo4j.pool.metrics.enabled+++[] 436 | endif::add-copy-button-to-config-props[] 437 | 438 | 439 | [.description] 440 | -- 441 | lag, if metrics are enabled 442 | 443 | 444 | ifdef::add-copy-button-to-env-var[] 445 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_METRICS_ENABLED+++[] 446 | endif::add-copy-button-to-env-var[] 447 | ifndef::add-copy-button-to-env-var[] 448 | Environment variable: `+++QUARKUS_NEO4J_POOL_METRICS_ENABLED+++` 449 | endif::add-copy-button-to-env-var[] 450 | -- 451 | |boolean 452 | |`false` 453 | 454 | a| [[quarkus-neo4j_quarkus-neo4j-pool-log-leaked-sessions]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-log-leaked-sessions[`quarkus.neo4j.pool.log-leaked-sessions`]## 455 | ifdef::add-copy-button-to-config-props[] 456 | config_property_copy_button:+++quarkus.neo4j.pool.log-leaked-sessions+++[] 457 | endif::add-copy-button-to-config-props[] 458 | 459 | 460 | [.description] 461 | -- 462 | if leaked sessions logging is enabled 463 | 464 | 465 | ifdef::add-copy-button-to-env-var[] 466 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_LOG_LEAKED_SESSIONS+++[] 467 | endif::add-copy-button-to-env-var[] 468 | ifndef::add-copy-button-to-env-var[] 469 | Environment variable: `+++QUARKUS_NEO4J_POOL_LOG_LEAKED_SESSIONS+++` 470 | endif::add-copy-button-to-env-var[] 471 | -- 472 | |boolean 473 | |`false` 474 | 475 | a| [[quarkus-neo4j_quarkus-neo4j-pool-max-connection-pool-size]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-max-connection-pool-size[`quarkus.neo4j.pool.max-connection-pool-size`]## 476 | ifdef::add-copy-button-to-config-props[] 477 | config_property_copy_button:+++quarkus.neo4j.pool.max-connection-pool-size+++[] 478 | endif::add-copy-button-to-config-props[] 479 | 480 | 481 | [.description] 482 | -- 483 | the maximum amount of connections in the connection pool towards a single database 484 | 485 | 486 | ifdef::add-copy-button-to-env-var[] 487 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_POOL_SIZE+++[] 488 | endif::add-copy-button-to-env-var[] 489 | ifndef::add-copy-button-to-env-var[] 490 | Environment variable: `+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_POOL_SIZE+++` 491 | endif::add-copy-button-to-env-var[] 492 | -- 493 | |int 494 | |`100` 495 | 496 | a| [[quarkus-neo4j_quarkus-neo4j-pool-idle-time-before-connection-test]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-idle-time-before-connection-test[`quarkus.neo4j.pool.idle-time-before-connection-test`]## 497 | ifdef::add-copy-button-to-config-props[] 498 | config_property_copy_button:+++quarkus.neo4j.pool.idle-time-before-connection-test+++[] 499 | endif::add-copy-button-to-config-props[] 500 | 501 | 502 | [.description] 503 | -- 504 | Pooled connections that have been idle in the pool for longer than this timeout will be tested before they are used again. The value `0` means connections will always be tested for validity and negative values mean connections will never be tested. 505 | 506 | 507 | ifdef::add-copy-button-to-env-var[] 508 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_IDLE_TIME_BEFORE_CONNECTION_TEST+++[] 509 | endif::add-copy-button-to-env-var[] 510 | ifndef::add-copy-button-to-env-var[] 511 | Environment variable: `+++QUARKUS_NEO4J_POOL_IDLE_TIME_BEFORE_CONNECTION_TEST+++` 512 | endif::add-copy-button-to-env-var[] 513 | -- 514 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 515 | |`-0.001S` 516 | 517 | a| [[quarkus-neo4j_quarkus-neo4j-pool-max-connection-lifetime]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-max-connection-lifetime[`quarkus.neo4j.pool.max-connection-lifetime`]## 518 | ifdef::add-copy-button-to-config-props[] 519 | config_property_copy_button:+++quarkus.neo4j.pool.max-connection-lifetime+++[] 520 | endif::add-copy-button-to-config-props[] 521 | 522 | 523 | [.description] 524 | -- 525 | Pooled connections older than this threshold will be closed and removed from the pool. 526 | 527 | 528 | ifdef::add-copy-button-to-env-var[] 529 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_LIFETIME+++[] 530 | endif::add-copy-button-to-env-var[] 531 | ifndef::add-copy-button-to-env-var[] 532 | Environment variable: `+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_LIFETIME+++` 533 | endif::add-copy-button-to-env-var[] 534 | -- 535 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 536 | |`1H` 537 | 538 | a| [[quarkus-neo4j_quarkus-neo4j-pool-connection-acquisition-timeout]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-connection-acquisition-timeout[`quarkus.neo4j.pool.connection-acquisition-timeout`]## 539 | ifdef::add-copy-button-to-config-props[] 540 | config_property_copy_button:+++quarkus.neo4j.pool.connection-acquisition-timeout+++[] 541 | endif::add-copy-button-to-config-props[] 542 | 543 | 544 | [.description] 545 | -- 546 | Acquisition of new connections will be attempted for at most configured timeout. 547 | 548 | 549 | ifdef::add-copy-button-to-env-var[] 550 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_CONNECTION_ACQUISITION_TIMEOUT+++[] 551 | endif::add-copy-button-to-env-var[] 552 | ifndef::add-copy-button-to-env-var[] 553 | Environment variable: `+++QUARKUS_NEO4J_POOL_CONNECTION_ACQUISITION_TIMEOUT+++` 554 | endif::add-copy-button-to-env-var[] 555 | -- 556 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 557 | |`1M` 558 | 559 | 560 | |=== 561 | 562 | ifndef::no-duration-note[] 563 | [NOTE] 564 | [id=duration-note-anchor-quarkus-neo4j_quarkus-neo4j] 565 | .About the Duration format 566 | ==== 567 | To write duration values, use the standard `java.time.Duration` format. 568 | See the link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence)[Duration#parse() Java API documentation] for more information. 569 | 570 | You can also use a simplified format, starting with a number: 571 | 572 | * If the value is only a number, it represents time in seconds. 573 | * If the value is a number followed by `ms`, it represents time in milliseconds. 574 | 575 | In other cases, the simplified format is translated to the `java.time.Duration` format for parsing: 576 | 577 | * If the value is a number followed by `h`, `m`, or `s`, it is prefixed with `PT`. 578 | * If the value is a number followed by `d`, it is prefixed with `P`. 579 | ==== 580 | endif::no-duration-note[] 581 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/includes/quarkus-neo4j_quarkus.neo4j.adoc: -------------------------------------------------------------------------------- 1 | [.configuration-legend] 2 | icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime 3 | [.configuration-reference.searchable, cols="80,.^10,.^10"] 4 | |=== 5 | 6 | h|[.header-title]##Configuration property## 7 | h|Type 8 | h|Default 9 | 10 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-health-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-health-enabled[`quarkus.neo4j.health.enabled`]## 11 | ifdef::add-copy-button-to-config-props[] 12 | config_property_copy_button:+++quarkus.neo4j.health.enabled+++[] 13 | endif::add-copy-button-to-config-props[] 14 | 15 | 16 | [.description] 17 | -- 18 | whether a health check is published in case the smallrye-health extension is present 19 | 20 | 21 | ifdef::add-copy-button-to-env-var[] 22 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_HEALTH_ENABLED+++[] 23 | endif::add-copy-button-to-env-var[] 24 | ifndef::add-copy-button-to-env-var[] 25 | Environment variable: `+++QUARKUS_NEO4J_HEALTH_ENABLED+++` 26 | endif::add-copy-button-to-env-var[] 27 | -- 28 | |boolean 29 | |`true` 30 | 31 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-enabled[`quarkus.neo4j.devservices.enabled`]## 32 | ifdef::add-copy-button-to-config-props[] 33 | config_property_copy_button:+++quarkus.neo4j.devservices.enabled+++[] 34 | endif::add-copy-button-to-config-props[] 35 | 36 | 37 | [.description] 38 | -- 39 | If DevServices has been explicitly enabled or disabled. DevServices is generally enabled by default, unless there is an existing configuration present. When DevServices is enabled Quarkus will attempt to automatically configure and start a database when running in Dev or Test mode. 40 | 41 | 42 | ifdef::add-copy-button-to-env-var[] 43 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_ENABLED+++[] 44 | endif::add-copy-button-to-env-var[] 45 | ifndef::add-copy-button-to-env-var[] 46 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_ENABLED+++` 47 | endif::add-copy-button-to-env-var[] 48 | -- 49 | |boolean 50 | | 51 | 52 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-image-name]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-image-name[`quarkus.neo4j.devservices.image-name`]## 53 | ifdef::add-copy-button-to-config-props[] 54 | config_property_copy_button:+++quarkus.neo4j.devservices.image-name+++[] 55 | endif::add-copy-button-to-config-props[] 56 | 57 | 58 | [.description] 59 | -- 60 | the container image name to use, for container based DevServices providers 61 | 62 | 63 | ifdef::add-copy-button-to-env-var[] 64 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_IMAGE_NAME+++[] 65 | endif::add-copy-button-to-env-var[] 66 | ifndef::add-copy-button-to-env-var[] 67 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_IMAGE_NAME+++` 68 | endif::add-copy-button-to-env-var[] 69 | -- 70 | |string 71 | |`neo4j:5` 72 | 73 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-additional-env-additional-env]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-additional-env-additional-env[`quarkus.neo4j.devservices.additional-env."additional-env"`]## 74 | ifdef::add-copy-button-to-config-props[] 75 | config_property_copy_button:+++quarkus.neo4j.devservices.additional-env."additional-env"+++[] 76 | endif::add-copy-button-to-config-props[] 77 | 78 | 79 | [.description] 80 | -- 81 | additional environment entries that can be added to the container before its start 82 | 83 | 84 | ifdef::add-copy-button-to-env-var[] 85 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_ADDITIONAL_ENV__ADDITIONAL_ENV_+++[] 86 | endif::add-copy-button-to-env-var[] 87 | ifndef::add-copy-button-to-env-var[] 88 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_ADDITIONAL_ENV__ADDITIONAL_ENV_+++` 89 | endif::add-copy-button-to-env-var[] 90 | -- 91 | |Map 92 | | 93 | 94 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-bolt-port]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-bolt-port[`quarkus.neo4j.devservices.bolt-port`]## 95 | ifdef::add-copy-button-to-config-props[] 96 | config_property_copy_button:+++quarkus.neo4j.devservices.bolt-port+++[] 97 | endif::add-copy-button-to-config-props[] 98 | 99 | 100 | [.description] 101 | -- 102 | This value can be used to specify the port to which the bolt-port of the container is exposed. It must be a free port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be logged on which port the Neo4j container is reachable over bolt. 103 | 104 | Ignored when container sharing is enabled. 105 | 106 | 107 | ifdef::add-copy-button-to-env-var[] 108 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_BOLT_PORT+++[] 109 | endif::add-copy-button-to-env-var[] 110 | ifndef::add-copy-button-to-env-var[] 111 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_BOLT_PORT+++` 112 | endif::add-copy-button-to-env-var[] 113 | -- 114 | |int 115 | | 116 | 117 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-http-port]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-http-port[`quarkus.neo4j.devservices.http-port`]## 118 | ifdef::add-copy-button-to-config-props[] 119 | config_property_copy_button:+++quarkus.neo4j.devservices.http-port+++[] 120 | endif::add-copy-button-to-config-props[] 121 | 122 | 123 | [.description] 124 | -- 125 | This value can be used to specify the port to which the http-port of the container is exposed. It must be a free port, otherwise startup will fail. A random, free port will be used by default. Either way, a messsage will be logged on which port the Neo4j Browser is available. 126 | 127 | Ignored when container sharing is enabled. 128 | 129 | 130 | ifdef::add-copy-button-to-env-var[] 131 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_HTTP_PORT+++[] 132 | endif::add-copy-button-to-env-var[] 133 | ifndef::add-copy-button-to-env-var[] 134 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_HTTP_PORT+++` 135 | endif::add-copy-button-to-env-var[] 136 | -- 137 | |int 138 | | 139 | 140 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-shared]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-shared[`quarkus.neo4j.devservices.shared`]## 141 | ifdef::add-copy-button-to-config-props[] 142 | config_property_copy_button:+++quarkus.neo4j.devservices.shared+++[] 143 | endif::add-copy-button-to-config-props[] 144 | 145 | 146 | [.description] 147 | -- 148 | Indicates if the Neo4j server managed by Quarkus Dev Services is shared. When shared, Quarkus looks for running containers using label-based service discovery. If a matching container is found, it is used, and so a second one is not started. Otherwise, Dev Services for Neo4j starts a new container. 149 | 150 | The discovery uses the `quarkus-dev-service-neo4j` label. The value is configured using the `service-name` property. 151 | 152 | Container sharing is only used in dev mode and disabled by default 153 | 154 | 155 | ifdef::add-copy-button-to-env-var[] 156 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_SHARED+++[] 157 | endif::add-copy-button-to-env-var[] 158 | ifndef::add-copy-button-to-env-var[] 159 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_SHARED+++` 160 | endif::add-copy-button-to-env-var[] 161 | -- 162 | |boolean 163 | |`false` 164 | 165 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-service-name]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-service-name[`quarkus.neo4j.devservices.service-name`]## 166 | ifdef::add-copy-button-to-config-props[] 167 | config_property_copy_button:+++quarkus.neo4j.devservices.service-name+++[] 168 | endif::add-copy-button-to-config-props[] 169 | 170 | 171 | [.description] 172 | -- 173 | The value of the `quarkus-dev-service-neo4j` label attached to the started container. This property is used when `shared` is set to `true`. In this case, before starting a container, Dev Services for Neo4j looks for a container with the `quarkus-dev-service-neo4j` label set to the configured value. If found, it will use this container instead of starting a new one. Otherwise, it starts a new container with the `quarkus-dev-service-neo4j` label set to the specified value. 174 | 175 | This property is used when you need multiple shared Neo4j servers. 176 | 177 | 178 | ifdef::add-copy-button-to-env-var[] 179 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_SERVICE_NAME+++[] 180 | endif::add-copy-button-to-env-var[] 181 | ifndef::add-copy-button-to-env-var[] 182 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_SERVICE_NAME+++` 183 | endif::add-copy-button-to-env-var[] 184 | -- 185 | |string 186 | |`neo4j` 187 | 188 | a|icon:lock[title=Fixed at build time] [[quarkus-neo4j_quarkus-neo4j-devservices-shared-password]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-devservices-shared-password[`quarkus.neo4j.devservices.shared-password`]## 189 | ifdef::add-copy-button-to-config-props[] 190 | config_property_copy_button:+++quarkus.neo4j.devservices.shared-password+++[] 191 | endif::add-copy-button-to-config-props[] 192 | 193 | 194 | [.description] 195 | -- 196 | This property is used only when you create multiple shared Neo4j servers 197 | 198 | 199 | ifdef::add-copy-button-to-env-var[] 200 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_DEVSERVICES_SHARED_PASSWORD+++[] 201 | endif::add-copy-button-to-env-var[] 202 | ifndef::add-copy-button-to-env-var[] 203 | Environment variable: `+++QUARKUS_NEO4J_DEVSERVICES_SHARED_PASSWORD+++` 204 | endif::add-copy-button-to-env-var[] 205 | -- 206 | |string 207 | |`verysecret` 208 | 209 | a| [[quarkus-neo4j_quarkus-neo4j-uri]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-uri[`quarkus.neo4j.uri`]## 210 | ifdef::add-copy-button-to-config-props[] 211 | config_property_copy_button:+++quarkus.neo4j.uri+++[] 212 | endif::add-copy-button-to-config-props[] 213 | 214 | 215 | [.description] 216 | -- 217 | the uri this driver should connect to. The driver supports bolt, bolt{plus}routing or neo4j as schemes 218 | 219 | 220 | ifdef::add-copy-button-to-env-var[] 221 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_URI+++[] 222 | endif::add-copy-button-to-env-var[] 223 | ifndef::add-copy-button-to-env-var[] 224 | Environment variable: `+++QUARKUS_NEO4J_URI+++` 225 | endif::add-copy-button-to-env-var[] 226 | -- 227 | |string 228 | |`bolt://localhost:7687` 229 | 230 | a| [[quarkus-neo4j_quarkus-neo4j-encrypted]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-encrypted[`quarkus.neo4j.encrypted`]## 231 | ifdef::add-copy-button-to-config-props[] 232 | config_property_copy_button:+++quarkus.neo4j.encrypted+++[] 233 | endif::add-copy-button-to-config-props[] 234 | 235 | 236 | [.description] 237 | -- 238 | if the driver should use encrypted traffic 239 | 240 | 241 | ifdef::add-copy-button-to-env-var[] 242 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_ENCRYPTED+++[] 243 | endif::add-copy-button-to-env-var[] 244 | ifndef::add-copy-button-to-env-var[] 245 | Environment variable: `+++QUARKUS_NEO4J_ENCRYPTED+++` 246 | endif::add-copy-button-to-env-var[] 247 | -- 248 | |boolean 249 | |`false` 250 | 251 | a| [[quarkus-neo4j_quarkus-neo4j-max-transaction-retry-time]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-max-transaction-retry-time[`quarkus.neo4j.max-transaction-retry-time`]## 252 | ifdef::add-copy-button-to-config-props[] 253 | config_property_copy_button:+++quarkus.neo4j.max-transaction-retry-time+++[] 254 | endif::add-copy-button-to-config-props[] 255 | 256 | 257 | [.description] 258 | -- 259 | the maximum time transactions are allowed to retry 260 | 261 | 262 | ifdef::add-copy-button-to-env-var[] 263 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_MAX_TRANSACTION_RETRY_TIME+++[] 264 | endif::add-copy-button-to-env-var[] 265 | ifndef::add-copy-button-to-env-var[] 266 | Environment variable: `+++QUARKUS_NEO4J_MAX_TRANSACTION_RETRY_TIME+++` 267 | endif::add-copy-button-to-env-var[] 268 | -- 269 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 270 | |`30S` 271 | 272 | h|[[quarkus-neo4j_section_quarkus-neo4j-authentication]] [.section-name.section-level0]##link:#quarkus-neo4j_section_quarkus-neo4j-authentication[return the authentication++}++]## 273 | h|Type 274 | h|Default 275 | 276 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-username]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-username[`quarkus.neo4j.authentication.username`]## 277 | ifdef::add-copy-button-to-config-props[] 278 | config_property_copy_button:+++quarkus.neo4j.authentication.username+++[] 279 | endif::add-copy-button-to-config-props[] 280 | 281 | 282 | [.description] 283 | -- 284 | the login of the user connecting to the database 285 | 286 | 287 | ifdef::add-copy-button-to-env-var[] 288 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_USERNAME+++[] 289 | endif::add-copy-button-to-env-var[] 290 | ifndef::add-copy-button-to-env-var[] 291 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_USERNAME+++` 292 | endif::add-copy-button-to-env-var[] 293 | -- 294 | |string 295 | |`neo4j` 296 | 297 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-password]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-password[`quarkus.neo4j.authentication.password`]## 298 | ifdef::add-copy-button-to-config-props[] 299 | config_property_copy_button:+++quarkus.neo4j.authentication.password+++[] 300 | endif::add-copy-button-to-config-props[] 301 | 302 | 303 | [.description] 304 | -- 305 | the password of the user connecting to the database 306 | 307 | 308 | ifdef::add-copy-button-to-env-var[] 309 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_PASSWORD+++[] 310 | endif::add-copy-button-to-env-var[] 311 | ifndef::add-copy-button-to-env-var[] 312 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_PASSWORD+++` 313 | endif::add-copy-button-to-env-var[] 314 | -- 315 | |string 316 | |`neo4j` 317 | 318 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-disabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-disabled[`quarkus.neo4j.authentication.disabled`]## 319 | ifdef::add-copy-button-to-config-props[] 320 | config_property_copy_button:+++quarkus.neo4j.authentication.disabled+++[] 321 | endif::add-copy-button-to-config-props[] 322 | 323 | 324 | [.description] 325 | -- 326 | whether disable authentication or not 327 | 328 | 329 | ifdef::add-copy-button-to-env-var[] 330 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_DISABLED+++[] 331 | endif::add-copy-button-to-env-var[] 332 | ifndef::add-copy-button-to-env-var[] 333 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_DISABLED+++` 334 | endif::add-copy-button-to-env-var[] 335 | -- 336 | |boolean 337 | |`false` 338 | 339 | a| [[quarkus-neo4j_quarkus-neo4j-authentication-value]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-authentication-value[`quarkus.neo4j.authentication.value`]## 340 | ifdef::add-copy-button-to-config-props[] 341 | config_property_copy_button:+++quarkus.neo4j.authentication.value+++[] 342 | endif::add-copy-button-to-config-props[] 343 | 344 | 345 | [.description] 346 | -- 347 | An optional field that when is not empty has precedence over `username` and `password`. It behaves the same way as `NEO4J_AUTH` in the official docker image, containing both the username and password separated via a single forward slash (`/`). 348 | 349 | 350 | ifdef::add-copy-button-to-env-var[] 351 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_AUTHENTICATION_VALUE+++[] 352 | endif::add-copy-button-to-env-var[] 353 | ifndef::add-copy-button-to-env-var[] 354 | Environment variable: `+++QUARKUS_NEO4J_AUTHENTICATION_VALUE+++` 355 | endif::add-copy-button-to-env-var[] 356 | -- 357 | |string 358 | | 359 | 360 | 361 | h|[[quarkus-neo4j_section_quarkus-neo4j-trust-settings]] [.section-name.section-level0]##link:#quarkus-neo4j_section_quarkus-neo4j-trust-settings[return the trust settings for encrypted traffic++}++]## 362 | h|Type 363 | h|Default 364 | 365 | a| [[quarkus-neo4j_quarkus-neo4j-trust-settings-strategy]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-trust-settings-strategy[`quarkus.neo4j.trust-settings.strategy`]## 366 | ifdef::add-copy-button-to-config-props[] 367 | config_property_copy_button:+++quarkus.neo4j.trust-settings.strategy+++[] 368 | endif::add-copy-button-to-config-props[] 369 | 370 | 371 | [.description] 372 | -- 373 | which trust strategy to apply when using encrypted traffic 374 | 375 | 376 | ifdef::add-copy-button-to-env-var[] 377 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_TRUST_SETTINGS_STRATEGY+++[] 378 | endif::add-copy-button-to-env-var[] 379 | ifndef::add-copy-button-to-env-var[] 380 | Environment variable: `+++QUARKUS_NEO4J_TRUST_SETTINGS_STRATEGY+++` 381 | endif::add-copy-button-to-env-var[] 382 | -- 383 | a|`trust-all-certificates`, `trust-custom-ca-signed-certificates`, `trust-system-ca-signed-certificates` 384 | |`trust-system-ca-signed-certificates` 385 | 386 | a| [[quarkus-neo4j_quarkus-neo4j-trust-settings-cert-file]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-trust-settings-cert-file[`quarkus.neo4j.trust-settings.cert-file`]## 387 | ifdef::add-copy-button-to-config-props[] 388 | config_property_copy_button:+++quarkus.neo4j.trust-settings.cert-file+++[] 389 | endif::add-copy-button-to-config-props[] 390 | 391 | 392 | [.description] 393 | -- 394 | the file of the certificate to use 395 | 396 | 397 | ifdef::add-copy-button-to-env-var[] 398 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_TRUST_SETTINGS_CERT_FILE+++[] 399 | endif::add-copy-button-to-env-var[] 400 | ifndef::add-copy-button-to-env-var[] 401 | Environment variable: `+++QUARKUS_NEO4J_TRUST_SETTINGS_CERT_FILE+++` 402 | endif::add-copy-button-to-env-var[] 403 | -- 404 | |path 405 | | 406 | 407 | a| [[quarkus-neo4j_quarkus-neo4j-trust-settings-hostname-verification-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-trust-settings-hostname-verification-enabled[`quarkus.neo4j.trust-settings.hostname-verification-enabled`]## 408 | ifdef::add-copy-button-to-config-props[] 409 | config_property_copy_button:+++quarkus.neo4j.trust-settings.hostname-verification-enabled+++[] 410 | endif::add-copy-button-to-config-props[] 411 | 412 | 413 | [.description] 414 | -- 415 | whether hostname verification is used 416 | 417 | 418 | ifdef::add-copy-button-to-env-var[] 419 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_TRUST_SETTINGS_HOSTNAME_VERIFICATION_ENABLED+++[] 420 | endif::add-copy-button-to-env-var[] 421 | ifndef::add-copy-button-to-env-var[] 422 | Environment variable: `+++QUARKUS_NEO4J_TRUST_SETTINGS_HOSTNAME_VERIFICATION_ENABLED+++` 423 | endif::add-copy-button-to-env-var[] 424 | -- 425 | |boolean 426 | |`false` 427 | 428 | 429 | h|[[quarkus-neo4j_section_quarkus-neo4j-pool]] [.section-name.section-level0]##link:#quarkus-neo4j_section_quarkus-neo4j-pool[return the connection pool++}++]## 430 | h|Type 431 | h|Default 432 | 433 | a| [[quarkus-neo4j_quarkus-neo4j-pool-metrics-enabled]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-metrics-enabled[`quarkus.neo4j.pool.metrics.enabled`]## 434 | ifdef::add-copy-button-to-config-props[] 435 | config_property_copy_button:+++quarkus.neo4j.pool.metrics.enabled+++[] 436 | endif::add-copy-button-to-config-props[] 437 | 438 | 439 | [.description] 440 | -- 441 | lag, if metrics are enabled 442 | 443 | 444 | ifdef::add-copy-button-to-env-var[] 445 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_METRICS_ENABLED+++[] 446 | endif::add-copy-button-to-env-var[] 447 | ifndef::add-copy-button-to-env-var[] 448 | Environment variable: `+++QUARKUS_NEO4J_POOL_METRICS_ENABLED+++` 449 | endif::add-copy-button-to-env-var[] 450 | -- 451 | |boolean 452 | |`false` 453 | 454 | a| [[quarkus-neo4j_quarkus-neo4j-pool-log-leaked-sessions]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-log-leaked-sessions[`quarkus.neo4j.pool.log-leaked-sessions`]## 455 | ifdef::add-copy-button-to-config-props[] 456 | config_property_copy_button:+++quarkus.neo4j.pool.log-leaked-sessions+++[] 457 | endif::add-copy-button-to-config-props[] 458 | 459 | 460 | [.description] 461 | -- 462 | if leaked sessions logging is enabled 463 | 464 | 465 | ifdef::add-copy-button-to-env-var[] 466 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_LOG_LEAKED_SESSIONS+++[] 467 | endif::add-copy-button-to-env-var[] 468 | ifndef::add-copy-button-to-env-var[] 469 | Environment variable: `+++QUARKUS_NEO4J_POOL_LOG_LEAKED_SESSIONS+++` 470 | endif::add-copy-button-to-env-var[] 471 | -- 472 | |boolean 473 | |`false` 474 | 475 | a| [[quarkus-neo4j_quarkus-neo4j-pool-max-connection-pool-size]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-max-connection-pool-size[`quarkus.neo4j.pool.max-connection-pool-size`]## 476 | ifdef::add-copy-button-to-config-props[] 477 | config_property_copy_button:+++quarkus.neo4j.pool.max-connection-pool-size+++[] 478 | endif::add-copy-button-to-config-props[] 479 | 480 | 481 | [.description] 482 | -- 483 | the maximum amount of connections in the connection pool towards a single database 484 | 485 | 486 | ifdef::add-copy-button-to-env-var[] 487 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_POOL_SIZE+++[] 488 | endif::add-copy-button-to-env-var[] 489 | ifndef::add-copy-button-to-env-var[] 490 | Environment variable: `+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_POOL_SIZE+++` 491 | endif::add-copy-button-to-env-var[] 492 | -- 493 | |int 494 | |`100` 495 | 496 | a| [[quarkus-neo4j_quarkus-neo4j-pool-idle-time-before-connection-test]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-idle-time-before-connection-test[`quarkus.neo4j.pool.idle-time-before-connection-test`]## 497 | ifdef::add-copy-button-to-config-props[] 498 | config_property_copy_button:+++quarkus.neo4j.pool.idle-time-before-connection-test+++[] 499 | endif::add-copy-button-to-config-props[] 500 | 501 | 502 | [.description] 503 | -- 504 | Pooled connections that have been idle in the pool for longer than this timeout will be tested before they are used again. The value `0` means connections will always be tested for validity and negative values mean connections will never be tested. 505 | 506 | 507 | ifdef::add-copy-button-to-env-var[] 508 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_IDLE_TIME_BEFORE_CONNECTION_TEST+++[] 509 | endif::add-copy-button-to-env-var[] 510 | ifndef::add-copy-button-to-env-var[] 511 | Environment variable: `+++QUARKUS_NEO4J_POOL_IDLE_TIME_BEFORE_CONNECTION_TEST+++` 512 | endif::add-copy-button-to-env-var[] 513 | -- 514 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 515 | |`-0.001S` 516 | 517 | a| [[quarkus-neo4j_quarkus-neo4j-pool-max-connection-lifetime]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-max-connection-lifetime[`quarkus.neo4j.pool.max-connection-lifetime`]## 518 | ifdef::add-copy-button-to-config-props[] 519 | config_property_copy_button:+++quarkus.neo4j.pool.max-connection-lifetime+++[] 520 | endif::add-copy-button-to-config-props[] 521 | 522 | 523 | [.description] 524 | -- 525 | Pooled connections older than this threshold will be closed and removed from the pool. 526 | 527 | 528 | ifdef::add-copy-button-to-env-var[] 529 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_LIFETIME+++[] 530 | endif::add-copy-button-to-env-var[] 531 | ifndef::add-copy-button-to-env-var[] 532 | Environment variable: `+++QUARKUS_NEO4J_POOL_MAX_CONNECTION_LIFETIME+++` 533 | endif::add-copy-button-to-env-var[] 534 | -- 535 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 536 | |`1H` 537 | 538 | a| [[quarkus-neo4j_quarkus-neo4j-pool-connection-acquisition-timeout]] [.property-path]##link:#quarkus-neo4j_quarkus-neo4j-pool-connection-acquisition-timeout[`quarkus.neo4j.pool.connection-acquisition-timeout`]## 539 | ifdef::add-copy-button-to-config-props[] 540 | config_property_copy_button:+++quarkus.neo4j.pool.connection-acquisition-timeout+++[] 541 | endif::add-copy-button-to-config-props[] 542 | 543 | 544 | [.description] 545 | -- 546 | Acquisition of new connections will be attempted for at most configured timeout. 547 | 548 | 549 | ifdef::add-copy-button-to-env-var[] 550 | Environment variable: env_var_with_copy_button:+++QUARKUS_NEO4J_POOL_CONNECTION_ACQUISITION_TIMEOUT+++[] 551 | endif::add-copy-button-to-env-var[] 552 | ifndef::add-copy-button-to-env-var[] 553 | Environment variable: `+++QUARKUS_NEO4J_POOL_CONNECTION_ACQUISITION_TIMEOUT+++` 554 | endif::add-copy-button-to-env-var[] 555 | -- 556 | |link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-neo4j_quarkus-neo4j[icon:question-circle[title=More information about the Duration format]] 557 | |`1M` 558 | 559 | 560 | |=== 561 | 562 | ifndef::no-duration-note[] 563 | [NOTE] 564 | [id=duration-note-anchor-quarkus-neo4j_quarkus-neo4j] 565 | .About the Duration format 566 | ==== 567 | To write duration values, use the standard `java.time.Duration` format. 568 | See the link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html#parse(java.lang.CharSequence)[Duration#parse() Java API documentation] for more information. 569 | 570 | You can also use a simplified format, starting with a number: 571 | 572 | * If the value is only a number, it represents time in seconds. 573 | * If the value is a number followed by `ms`, it represents time in milliseconds. 574 | 575 | In other cases, the simplified format is translated to the `java.time.Duration` format for parsing: 576 | 577 | * If the value is a number followed by `h`, `m`, or `s`, it is prefixed with `PT`. 578 | * If the value is a number followed by `d`, it is prefixed with `P`. 579 | ==== 580 | endif::no-duration-note[] 581 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = Quarkus Neo4j 2 | 3 | :neo4j_version: 4.4 4 | 5 | include::./includes/attributes.adoc[] 6 | 7 | https://neo4j.com[Neo4j] is a graph database management system developed by Neo4j, Inc. Neo4j is a native graph database focused not only on the data itself, but especially on the relations between data. 8 | Neo4j stores data as a property graph, which consists of vertices or nodes as we call them, connected with edges or relationships. 9 | Both of them can have properties. 10 | 11 | Neo4j offers Cypher, a declarative query language much like SQL. 12 | Cypher is used to for both querying the graph and creating or updating nodes and relationships. 13 | As a declarative language it used to tell the database what to do and not how to do it. 14 | 15 | NOTE: Learn more about Cypher in the https://neo4j.com/docs/cypher-manual/current/[Neo4j Cypher manual]. 16 | Cypher is not only available in Neo4j, but for example coming to https://github.com/opencypher/morpheus[Apache Spark]. 17 | A spec called http://www.opencypher.org[OpenCypher] is available, too. 18 | 19 | Clients communicate over the so-called Bolt protocol with the database. 20 | 21 | Neo4j - as the most popular graph database according to DB-Engines ranking - provides a variety of drivers for various languages. 22 | 23 | The Quarkus Neo4j extension is based on the official https://github.com/neo4j/neo4j-java-driver[Neo4j Java Driver]. 24 | The extension provides an instance of the driver configured ready for usage in any Quarkus application. 25 | You will be able to issue arbitrary Cypher statements over Bolt with this extension. 26 | Those statements can be simple CRUD statements as well as complex queries, calling graph algorithms and more. 27 | 28 | The driver itself is released under the Apache 2.0 license, 29 | while Neo4j itself is available in a GPL3-licensed open-source "community edition", 30 | with online backup and high availability extensions licensed under a closed-source commercial license. 31 | 32 | == Programming model 33 | 34 | The driver and thus the Quarkus extension support three different programming models: 35 | 36 | * Blocking database access (much like standard JDBC) 37 | * Asynchronous programming based on JDK's completable futures and related infrastructure 38 | * Reactive programming based on http://www.reactive-streams.org[Reactive Streams] 39 | 40 | The reactive programming model is only available when connected against a 4.0+ version of Neo4j. 41 | Reactive programming in Neo4j is fully end-to-end reactive and therefore requires a server that supports backpressure. 42 | 43 | In this guide you will learn how to 44 | 45 | * Add the Neo4j extension to your project 46 | * Configure the driver 47 | * And how to use the driver to access a Neo4j database 48 | 49 | This guide will focus on asynchronous access to Neo4j, as this is ready to use for everyone. 50 | At the end of this guide, there will be a reactive version, which needs however a 4.0 database version. 51 | 52 | == The domain 53 | 54 | As with some other guides, the application shall manage fruit entities. 55 | 56 | [source,java] 57 | ---- 58 | package org.acme.neo4j; 59 | 60 | include::{examples-dir}/Fruit.java[tag=intro,indent=0,tabsize=2] 61 | ---- 62 | 63 | == Prerequisites 64 | 65 | To complete this guide, you need: 66 | 67 | * JDK 11+ installed with `JAVA_HOME` configured appropriately 68 | * an IDE 69 | * Apache Maven {maven-version} 70 | * Access to a Neo4j Database 71 | * Optional Docker for your system 72 | 73 | === Setup Neo4j 74 | 75 | The easiest way to start a Neo4j instance is a locally installed Docker environment. 76 | 77 | [source,bash,subs="verbatim,attributes"] 78 | ---- 79 | docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' neo4j:{neo4j_version} 80 | ---- 81 | 82 | This starts a Neo4j instance, that publishes its Bolt port on `7687` and a web interface on http://localhost:7474. 83 | 84 | Have a look at the https://neo4j.com/download/?ref=product[download page] for other options to get started with the product itself. 85 | 86 | == Solution 87 | 88 | We recommend that you follow the instructions in the next sections and create the application step by step. 89 | However, you can go right to the completed example. 90 | 91 | Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive. 92 | 93 | The solution is located in the `neo4j-quickstart` {quickstarts-tree-url}/neo4j-quickstart[directory]. 94 | It contains a very simple UI to use the JAX-RS resources created here, too. 95 | 96 | == Creating the Maven project 97 | 98 | First, we need a new project. Create a new project with the following command: 99 | 100 | [source,bash,subs=attributes+] 101 | ---- 102 | mvn io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:create \ 103 | -DprojectGroupId=org.acme \ 104 | -DprojectArtifactId=neo4j-quickstart \ 105 | -DclassName="org.acme.datasource.GreetingResource" \ 106 | -Dextensions="neo4j,resteasy-reactive-jackson" 107 | cd neo4j-quickstart 108 | ---- 109 | 110 | It generates: 111 | 112 | * the Maven structure 113 | * a landing page accessible on `http://localhost:8080` 114 | * example `Dockerfile` files for both `native` and `jvm` modes 115 | * the application configuration file 116 | * an `org.acme.datasource.GreetingResource` resource 117 | * an associated test 118 | 119 | The Neo4j extension has been added already to your `pom.xml`. 120 | In addition, we added `resteasy-reactive-jackson`, which allows us to expose `Fruit` instances over HTTP in the JSON format via JAX-RS resources. 121 | If you have an already created project, the `neo4j` extension can be added to an existing Quarkus project with the `add-extension` command: 122 | 123 | [source,bash] 124 | ---- 125 | ./mvnw quarkus:add-extension -Dextensions="neo4j" 126 | ---- 127 | 128 | Otherwise, you can manually add this to the dependencies section of your `pom.xml` file: 129 | 130 | [source,xml,subs=attributes+] 131 | ---- 132 | 133 | io.quarkiverse.neo4j 134 | quarkus-neo4j 135 | {quarkus-neo4j-version} 136 | 137 | ---- 138 | 139 | == Configuring 140 | 141 | The Neo4j driver can be configured with standard Quarkus properties: 142 | 143 | [source,properties] 144 | .src/main/resources/application.properties 145 | ---- 146 | # Those are the default values and are implicitly assumed 147 | quarkus.neo4j.uri = bolt://localhost:7687 148 | quarkus.neo4j.authentication.username = neo4j 149 | quarkus.neo4j.authentication.password = secret 150 | ---- 151 | 152 | You'll recognize the authentication here that you passed on to the docker command above. 153 | 154 | Having done that, the driver is ready to use, there are however other configuration options, detailed below. 155 | 156 | [[dev-services]] 157 | === Dev Services (Configuration Free Databases) 158 | 159 | Quarkus supports a feature called Dev Services that allows you to create various datasources without any config. 160 | In the case of Neo4j this support applies to the single Neo4j driver instance. 161 | Dev Services will bring up a Neo4j container if you didn't explicit add the default values or configured custom values for 162 | any of `quarkus.neo4j.uri`, `quarkus.neo4j.authentication.username` or `quarkus.neo4j.authentication.password`. 163 | If Neo4j seems to be reachable via the default properties, Dev Services will also step back. 164 | 165 | Otherwise, Quarkus will automatically start a Neo4j container when running tests or dev-mode, 166 | and automatically configure the connection. 167 | 168 | When running the production version of the application, the Neo4j connection need to be configured as normal, 169 | so if you want to include a production database config in your `application.properties` and continue to use Dev Services 170 | we recommend that you use the `%prod.` profile to define your Neo4j settings. 171 | 172 | include::./includes/quarkus-neo4j-config-group-dev-services-build-time-config.adoc[] 173 | 174 | == Using the driver 175 | 176 | === General remarks 177 | 178 | The result of a statement consists usually of one or more `org.neo4j.driver.Record`. 179 | Those records contain arbitrary values, supported by the driver. 180 | If you return a node of the graph, it will be a `org.neo4j.driver.types.Node`. 181 | 182 | We add the following method to the `Fruit`, as a convenient way to create them: 183 | 184 | [source,java] 185 | ---- 186 | include::{examples-dir}/Fruit.java[tag=builder,indent=0,tabsize=2] 187 | ---- 188 | 189 | Add a `FruitResource` skeleton like this and `@Inject` a `org.neo4j.driver.Driver` instance and a `ThreadContext` instance: 190 | 191 | [source,java] 192 | .src/main/java/org/acme/neo4j/FruitResource.java 193 | ---- 194 | package org.acme.neo4j; 195 | 196 | include::{examples-dir}/FruitResource.java[tag=skeleton,indent=0,tabsize=2] 197 | ---- 198 | <.> The `ThreadContext` is related to context propagation with completion stage. A completion stage, 199 | unlike Mutiny, does not have a hook to automatically "capture and restore" the context. 200 | So, we need to use this construct in later steps when using the connections asynchronous api. 201 | 202 | === Reading nodes 203 | 204 | Add the following method to the fruit resource: 205 | 206 | [source,java] 207 | .FruitResource#get 208 | ---- 209 | include::{examples-dir}/FruitResource.java[tag=reading,indent=0,tabsize=2] 210 | ---- 211 | <.> Open a new, asynchronous session with Neo4j 212 | <.> Execute a query. This is a Cypher statement. 213 | <.> Retrieve a cursor, list the results and create ``Fruit``s. This must happen inside the transactional function, not outside. 214 | <.> Wrap the completion stage so that the current context is captured, and restored it before calling a continuation 215 | method of the completion stage. With that, the context is restored and available in any callback. 216 | <.> Close the session after processing 217 | <.> Create a JAX-RS response 218 | 219 | Now start Quarkus in `dev` mode with: 220 | 221 | [source,bash] 222 | ---- 223 | ./mvnw compile quarkus:dev 224 | ---- 225 | 226 | and retrieve the endpoint like this 227 | 228 | [source,bash] 229 | ---- 230 | curl localhost:8080/fruits 231 | ---- 232 | 233 | There are not any fruits, so let's create some. 234 | 235 | === Creating nodes 236 | 237 | The `POST` method looks similar. 238 | It uses transaction functions of the driver: 239 | 240 | [source,java] 241 | .FruitResource#create 242 | ---- 243 | include::{examples-dir}/FruitResource.java[tag=create,indent=0,tabsize=2] 244 | ---- 245 | 246 | As you can see, we are now using a Cypher statement with named parameters (The `$name` of the fruit). 247 | The node is returned, a `Fruit` entity created and then mapped to a `201` created response. 248 | 249 | A curl request against this path may look like this: 250 | 251 | [source,bash] 252 | ---- 253 | curl -v -X "POST" "http://localhost:8080/fruits" \ 254 | -H 'Content-Type: application/json; charset=utf-8' \ 255 | -d $'{ 256 | "name": "Banana" 257 | }' 258 | ---- 259 | 260 | The response contains an URI that shall return single nodes. 261 | 262 | === Read single nodes 263 | 264 | This time, we ask for a read-only transaction. 265 | We also add some exception handling, in case the resource is called with an invalid id: 266 | 267 | [source,java] 268 | .FruitResource#getSingle 269 | ---- 270 | include::{examples-dir}/FruitResource.java[tag=getSingle,indent=0,tabsize=2] 271 | ---- 272 | 273 | A request may look like this: 274 | 275 | [source,bash] 276 | ---- 277 | curl localhost:8080/fruits/42 278 | ---- 279 | 280 | NOTE: In case Neo4j has been setup as a cluster, the transaction mode is used to decide whether a request is routed 281 | to a leader or a follower instance. Write transactions must be handled by a leader, whereas read-only transactions 282 | can be handled by followers. 283 | 284 | === Deleting nodes 285 | 286 | Finally, we want to get rid of fruits again and we add the `DELETE` method: 287 | 288 | [source,java] 289 | .FruitResource#delete 290 | ---- 291 | include::{examples-dir}/FruitResource.java[tag=delete,indent=0,tabsize=2] 292 | ---- 293 | <1> There is no result for us, only a summary of the query executed. 294 | 295 | A request may look like this 296 | 297 | [source,bash] 298 | ---- 299 | curl -X DELETE localhost:8080/fruits/42 300 | ---- 301 | 302 | And that's already the most simple CRUD application with one type of nodes. 303 | Feel free to add relationships to the model. 304 | One idea would be to model recipes that contain fruits. 305 | The Cypher manual linked in the introduction will help you with modelling your queries. 306 | 307 | == Next steps 308 | 309 | === Packaging 310 | 311 | Packaging your application is as simple as `./mvnw clean package`. 312 | It can be run with `java -jar target/quarkus-app/quarkus-run.jar`. 313 | 314 | With GraalVM installed, you can also create a native executable binary: `./mvnw clean package -Dnative`. 315 | Depending on your system, that will take some time. 316 | 317 | === Connection Health Check 318 | 319 | If you are using the `quarkus-smallrye-health` extension, `quarkus-neo4j` will automatically add a readiness health check 320 | to validate the connection to Neo4j. 321 | 322 | So when you access the `/q/health/ready` endpoint of your application you will have information about the connection validation status. 323 | 324 | This behavior can be disabled by setting the `quarkus.neo4j.health.enabled` property to `false` in your `application.properties`. 325 | 326 | === Driver metrics 327 | 328 | If you are using a metrics extension and specify the config property `quarkus.neo4j.pool.metrics.enabled=true`, the Neo4j extension will 329 | expose several metrics related to the Neo4j connection pool. 330 | 331 | === Explore Cypher and the Graph 332 | 333 | There are tons of options to model your domain within a Graph. 334 | The https://neo4j.com/docs/[Neo4j docs], the sandboxes and more are a good starting point. 335 | 336 | [[reactive]] 337 | === Going reactive 338 | 339 | If you have access to Neo4j 4.0, you can go fully reactive. 340 | 341 | To make life a bit easier, we will use https://smallrye.io/smallrye-mutiny/[Mutiny] for this. 342 | 343 | [TIP] 344 | .Mutiny 345 | ==== 346 | The following example uses Mutiny reactive types. 347 | If you are not familiar with Mutiny, check {quarkus-guides-url}/mutiny-primer[Mutiny - an intuitive reactive programming library]. 348 | ==== 349 | 350 | The reactive fruit resources streams the name of all fruits: 351 | 352 | [source,java] 353 | .ReactiveFruitResource.java 354 | ---- 355 | include::{examples-dir}/ReactiveFruitResource.java[tag=get,indent=0,tabsize=2] 356 | ---- 357 | <.> A finalizer that will close a Neo4j driver session in a non-blocking fashion 358 | <.> The `Multi.createFrom().resource()` is used to defer the creation of session until the publisher is subscribed to 359 | <.> When the publisher is done, the finalizer will be called 360 | 361 | `driver.rxSession()` returns a reactive session. 362 | It exposes its API based on http://www.reactive-streams.org[Reactive Streams], most prominently, as `org.reactivestreams.Publisher`. 363 | Those can be used directly, but we found it easier and more expressive to wrap them in reactive types such as the one provided by Mutiny. 364 | Typically, in the previous code, the session is closed when the stream completes, fails or the subscriber cancels. 365 | 366 | If you want to return a Mutiny! `Uni` object, you need to be very careful before you convert a `Multi` into a `Uni`: 367 | The conversion works in such a way, that the first item is emitted and then a cancellation signal is sent to the publisher, 368 | that will propagate upto the drivers' session, indicating a cancellation of the transaction, thus doing a rollback. 369 | 370 | In most cases you are better off returning a `Multi` or just a generic `Publisher`. If you need a `Uni`, you can still realize 371 | this with an emitter: 372 | 373 | [source,java] 374 | .ReactiveFruitResource.java#create 375 | ---- 376 | include::{examples-dir}/ReactiveFruitResource.java[tag=create,indent=0,tabsize=2] 377 | ---- 378 | <.> Here we use `Uni.createFrom().emitter()` to retrieve an `UniEmitter` that we use in 2. 379 | <.> Notice how we subscribe to a `Multi` setup in a similar fashion as in the prior example. The subscription will emit the one 380 | and only item via the emitter, without a cancellation event. 381 | 382 | == Configuration Reference 383 | 384 | NOTE: Each of the neo4j and bolt URI schemes permit variants that contain extra encryption and trust information. 385 | The +s variants enable encryption with a full certificate check, and the +ssc variants enable encryption, 386 | but with no certificate check. This latter variant is designed specifically for use with self-signed certificates. 387 | The variants are basically shortcuts over explicit configuration. If you use one of them, Quarkus won't pass 388 | `quarkus.neo4j.encrypted` and related to the driver creation as the driver prohibits this. 389 | + 390 | The only check applied when Quarkus detects a secure url (either of `+s` or `+ssc`) is to ensure availability of 391 | SSL in native image and will throw `ConfigurationException` if it isn't available. 392 | 393 | include::includes/quarkus-neo4j.adoc[] 394 | -------------------------------------------------------------------------------- /docs/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.neo4j 6 | quarkus-neo4j-parent 7 | 5.4.1-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | quarkus-neo4j-docs 12 | Quarkus - Neo4j - Documentation 13 | 14 | 15 | 16 | 17 | io.quarkiverse.neo4j 18 | quarkus-neo4j-deployment 19 | ${project.version} 20 | 21 | 22 | 23 | 24 | 25 | 26 | it.ozimov 27 | yaml-properties-maven-plugin 28 | 29 | 30 | initialize 31 | 32 | read-project-properties 33 | 34 | 35 | 36 | ${project.basedir}/../.github/project.yml 37 | 38 | 39 | 40 | 41 | 42 | 43 | io.quarkus 44 | quarkus-config-doc-maven-plugin 45 | true 46 | 47 | ${project.basedir}/modules/ROOT/pages/includes/ 48 | 49 | 50 | 51 | maven-resources-plugin 52 | 53 | 54 | copy-resources 55 | generate-resources 56 | 57 | copy-resources 58 | 59 | 60 | ${project.basedir}/modules/ROOT/pages/includes/ 61 | 62 | 63 | ${project.basedir}/templates/includes 64 | attributes.adoc 65 | true 66 | 67 | 68 | 69 | 70 | 71 | copy-images 72 | prepare-package 73 | 74 | copy-resources 75 | 76 | 77 | ${project.build.directory}/generated-docs/_images/ 78 | 79 | 80 | ${project.basedir}/modules/ROOT/assets/images/ 81 | false 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.asciidoctor 90 | asciidoctor-maven-plugin 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/templates/includes/attributes.adoc: -------------------------------------------------------------------------------- 1 | :quarkus-version: ${quarkus.version} 2 | :quarkus-neo4j-version: ${release.current-version} 3 | :maven-version: 3.8.1+ 4 | 5 | :quarkus-org-url: https://github.com/quarkusio 6 | :quarkus-base-url: {quarkus-org-url}/quarkus 7 | :quarkus-clone-url: {quarkus-base-url}.git 8 | :quarkus-archive-url: {quarkus-base-url}/archive/master.zip 9 | :quarkus-tree-url: {quarkus-base-url}/tree/main 10 | :quarkus-issues-url: {quarkus-base-url}/issues 11 | :quarkus-guides-url: https://quarkus.io/guides 12 | :quickstarts-base-url: https://github.com/quarkusio/quarkus-quickstarts 13 | :quickstarts-clone-url: https://github.com/quarkusio/quarkus-quickstarts.git 14 | :quickstarts-archive-url: https://github.com/quarkusio/quarkus-quickstarts/archive/main.zip 15 | :quickstarts-blob-url: https://github.com/quarkusio/quarkus-quickstarts/blob/main 16 | :quickstarts-tree-url: https://github.com/quarkusio/quarkus-quickstarts/tree/main 17 | 18 | :examples-dir: ./../examples/ 19 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.neo4j 6 | quarkus-neo4j-parent 7 | 5.4.1-SNAPSHOT 8 | 9 | quarkus-neo4j-integration-tests 10 | Quarkus - Neo4j - Integration Tests 11 | Module that contains Neo4j related tests running with the Neo4j database 12 | 13 | 14 | bolt://localhost:60513 15 | neo4j 16 | verysecret 17 | 18 | 19 | 20 | 21 | 22 | io.quarkiverse.neo4j 23 | quarkus-neo4j 24 | ${project.version} 25 | 26 | 27 | 28 | 29 | io.quarkus 30 | quarkus-rest-jackson 31 | 32 | 33 | 34 | io.quarkus 35 | quarkus-smallrye-health 36 | 37 | 38 | io.quarkus 39 | quarkus-smallrye-metrics-deployment 40 | 41 | 42 | 43 | 44 | io.quarkus 45 | quarkus-junit5 46 | test 47 | 48 | 49 | io.rest-assured 50 | rest-assured 51 | test 52 | 53 | 54 | org.apache.commons 55 | commons-lang3 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | src/main/resources 66 | true 67 | 68 | 69 | 70 | 71 | io.quarkus 72 | quarkus-maven-plugin 73 | 74 | 75 | 76 | build 77 | 78 | 79 | 80 | 81 | 82 | io.fabric8 83 | docker-maven-plugin 84 | 0.46.0 85 | 86 | 87 | 88 | neo4j:5 89 | 90 | 91 | 60513:7687 92 | 93 | 94 | neo4j/${neo4j.password} 95 | 10M 96 | 10M 97 | 98 | 99 | Neo4j: 100 | default 101 | cyan 102 | 103 | 104 | .*Started\. 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | docker-start 114 | compile 115 | 116 | stop 117 | start 118 | 119 | 120 | 121 | docker-stop 122 | post-integration-test 123 | 124 | stop 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | native-image 135 | 136 | 137 | native 138 | 139 | 140 | 141 | 142 | 143 | maven-failsafe-plugin 144 | 145 | 146 | 147 | integration-test 148 | verify 149 | 150 | 151 | 152 | ${project.build.directory}/${project.build.finalName}-runner 153 | org.jboss.logmanager.LogManager 154 | ${maven.home} 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | true 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /integration-tests/src/main/java/io/quarkus/it/neo4j/Fruit.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.it.neo4j; 2 | 3 | import java.util.UUID; 4 | 5 | import org.neo4j.driver.types.Node; 6 | 7 | // tag::intro[] 8 | public class Fruit { 9 | // end::intro[] 10 | // tag::builder[] 11 | public static Fruit from(Node node) { 12 | return new Fruit(UUID.fromString(node.get("id").asString()), node.get("name").asString()); 13 | } 14 | // end::builder[] 15 | 16 | // tag::intro[] 17 | public UUID id; 18 | 19 | public String name; 20 | 21 | public Fruit() { 22 | // This is needed for the REST-Easy JSON Binding 23 | } 24 | 25 | public Fruit(String name) { 26 | this.name = name; 27 | } 28 | 29 | public Fruit(UUID id, String name) { 30 | this.id = id; 31 | this.name = name; 32 | } 33 | } 34 | // end::intro[] 35 | -------------------------------------------------------------------------------- /integration-tests/src/main/java/io/quarkus/it/neo4j/FruitResource.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.it.neo4j; 2 | 3 | // tag::skeleton[] 4 | import java.net.URI; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.CompletionException; 8 | import java.util.concurrent.CompletionStage; 9 | 10 | import jakarta.inject.Inject; 11 | import jakarta.ws.rs.Consumes; 12 | import jakarta.ws.rs.DELETE; 13 | import jakarta.ws.rs.GET; 14 | import jakarta.ws.rs.POST; 15 | import jakarta.ws.rs.Path; 16 | import jakarta.ws.rs.Produces; 17 | import jakarta.ws.rs.core.MediaType; 18 | import jakarta.ws.rs.core.Response; 19 | import jakarta.ws.rs.core.Response.ResponseBuilder; 20 | import jakarta.ws.rs.core.Response.Status; 21 | 22 | import org.eclipse.microprofile.context.ThreadContext; 23 | import org.neo4j.driver.Driver; 24 | import org.neo4j.driver.async.AsyncSession; 25 | import org.neo4j.driver.async.ResultCursor; 26 | import org.neo4j.driver.exceptions.NoSuchRecordException; 27 | 28 | @Path("/fruits") 29 | @Consumes(MediaType.APPLICATION_JSON) 30 | @Produces(MediaType.APPLICATION_JSON) 31 | public class FruitResource { 32 | 33 | @Inject 34 | Driver driver; 35 | 36 | @Inject 37 | ThreadContext threadContext; // <.> 38 | // end::skeleton[] 39 | 40 | // tag::reading[] 41 | @GET 42 | public CompletionStage get() { 43 | AsyncSession session = driver.session(AsyncSession.class); // <.> 44 | CompletionStage> cs = session 45 | .executeReadAsync(tx -> tx 46 | .runAsync("MATCH (f:Fruit) RETURN f ORDER BY f.name") // <.> 47 | .thenCompose(cursor -> cursor // <.> 48 | .listAsync(record -> Fruit.from(record.get("f").asNode())))); 49 | return threadContext.withContextCapture(cs) // <.> 50 | .thenCompose(fruits -> // <.> 51 | session.closeAsync().thenApply(signal -> fruits)) 52 | .thenApply(Response::ok) // <.> 53 | .thenApply(ResponseBuilder::build); 54 | } 55 | // end::reading[] 56 | 57 | // tag::create[] 58 | @POST 59 | public CompletionStage create(Fruit fruit) { 60 | AsyncSession session = driver.session(AsyncSession.class); 61 | CompletionStage cs = session 62 | .executeWriteAsync(tx -> tx 63 | .runAsync( 64 | "CREATE (f:Fruit {id: randomUUID(), name: $name}) RETURN f", 65 | Map.of("name", fruit.name)) 66 | .thenCompose(ResultCursor::singleAsync) 67 | .thenApply(record -> Fruit.from(record.get("f").asNode()))); 68 | return threadContext.withContextCapture(cs) 69 | .thenCompose(persistedFruit -> session 70 | .closeAsync().thenApply(signal -> persistedFruit)) 71 | .thenApply(persistedFruit -> Response 72 | .created(URI.create("/fruits/" + persistedFruit.id)) 73 | .build()); 74 | } 75 | // end::create[] 76 | 77 | // tag::getSingle[] 78 | @GET 79 | @Path("/{id}") 80 | public CompletionStage getSingle(String id) { 81 | AsyncSession session = driver.session(AsyncSession.class); 82 | return threadContext.withContextCapture(session 83 | .executeReadAsync(tx -> tx 84 | .runAsync("MATCH (f:Fruit) WHERE f.id = $id RETURN f", Map.of("id", id)) 85 | .thenCompose(ResultCursor::singleAsync)) 86 | .handle((record, exception) -> { 87 | if (exception != null) { 88 | Throwable source = exception; 89 | if (exception instanceof CompletionException) { 90 | source = exception.getCause(); 91 | } 92 | Status status = Status.INTERNAL_SERVER_ERROR; 93 | if (source instanceof NoSuchRecordException) { 94 | status = Status.NOT_FOUND; 95 | } 96 | return Response.status(status).build(); 97 | } else { 98 | return Response.ok(Fruit.from(record.get("f").asNode())).build(); 99 | } 100 | })) 101 | .thenCompose(response -> session.closeAsync().thenApply(signal -> response)); 102 | } 103 | // end::getSingle[] 104 | 105 | // tag::delete[] 106 | @DELETE 107 | @Path("{id}") 108 | public CompletionStage delete(String id) { 109 | AsyncSession session = driver.session(AsyncSession.class); 110 | return threadContext.withContextCapture(session 111 | .executeWriteAsync(tx -> tx 112 | .runAsync("MATCH (f:Fruit) WHERE f.id = $id DELETE f", Map.of("id", id)) 113 | .thenCompose(ResultCursor::consumeAsync) // <.> 114 | )) 115 | .thenCompose(response -> session.closeAsync()) 116 | .thenApply(signal -> Response.noContent().build()); 117 | } 118 | // end::delete[] 119 | 120 | // tag::skeleton[] 121 | } 122 | // end::skeleton[] 123 | -------------------------------------------------------------------------------- /integration-tests/src/main/java/io/quarkus/it/neo4j/Neo4jResource.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.it.neo4j; 2 | 3 | import static jakarta.ws.rs.core.MediaType.*; 4 | 5 | import java.io.PrintWriter; 6 | import java.io.StringWriter; 7 | import java.util.List; 8 | import java.util.concurrent.CompletionStage; 9 | 10 | import jakarta.inject.Inject; 11 | import jakarta.ws.rs.GET; 12 | import jakarta.ws.rs.Path; 13 | import jakarta.ws.rs.Produces; 14 | 15 | import org.eclipse.microprofile.context.ThreadContext; 16 | import org.neo4j.driver.Driver; 17 | import org.neo4j.driver.Result; 18 | import org.neo4j.driver.Session; 19 | import org.neo4j.driver.Transaction; 20 | import org.neo4j.driver.Values; 21 | import org.neo4j.driver.async.AsyncSession; 22 | import org.neo4j.driver.reactive.ReactiveResult; 23 | import org.neo4j.driver.reactive.ReactiveSession; 24 | 25 | import io.smallrye.mutiny.Multi; 26 | import io.smallrye.mutiny.Uni; 27 | 28 | @Path("/neo4j") 29 | public class Neo4jResource { 30 | 31 | @Inject 32 | Driver driver; 33 | 34 | @Inject 35 | ThreadContext threadContext; 36 | 37 | @GET 38 | @Path("/blocking") 39 | public String doStuffWithNeo4j() { 40 | try { 41 | createNodes(driver); 42 | 43 | readNodes(driver); 44 | } catch (Exception e) { 45 | StringWriter out = new StringWriter(); 46 | PrintWriter writer = new PrintWriter(out); 47 | reportException("An error occurred while performing Neo4j operations", e, writer); 48 | writer.flush(); 49 | writer.close(); 50 | return out.toString(); 51 | } 52 | return "OK"; 53 | } 54 | 55 | @GET 56 | @Path("/asynchronous") 57 | @Produces(APPLICATION_JSON) 58 | public CompletionStage> doStuffWithNeo4jAsynchronous() { 59 | AsyncSession session = driver.session(AsyncSession.class); 60 | return threadContext.withContextCapture(session 61 | .runAsync("UNWIND range(1, 3) AS x RETURN x")) 62 | .thenCompose(cursor -> cursor.listAsync(record -> record.get("x").asInt())) 63 | .whenComplete((records, error) -> { 64 | if (records != null) { 65 | System.out.println(records); 66 | } else { 67 | error.printStackTrace(); 68 | } 69 | }) 70 | .thenCompose(records -> session.closeAsync() 71 | .thenApply(ignore -> records)); 72 | } 73 | 74 | @GET 75 | @Path("/reactive") 76 | @Produces(SERVER_SENT_EVENTS) 77 | public Multi doStuffWithNeo4jReactive() { 78 | 79 | return Multi.createFrom().resource(() -> driver.session(ReactiveSession.class), 80 | session -> session.executeRead(tx -> { 81 | var result = tx.run("UNWIND range(1, 3) AS x RETURN x"); 82 | return Multi.createFrom().publisher(result) 83 | .flatMap(ReactiveResult::records) 84 | .map(record -> record.get("x").asInt()); 85 | })) 86 | .withFinalizer(session -> { 87 | return Uni.createFrom().publisher(session.close()); 88 | }); 89 | } 90 | 91 | private static void createNodes(Driver driver) { 92 | try (Session session = driver.session(); 93 | Transaction transaction = session.beginTransaction()) { 94 | transaction.run("CREATE (f:Framework {name: $name}) - [:CAN_USE] -> (n:Database {name: 'Neo4j'})", 95 | Values.parameters("name", "Quarkus")); 96 | transaction.commit(); 97 | } 98 | } 99 | 100 | private static void readNodes(Driver driver) { 101 | try (Session session = driver.session(); 102 | Transaction transaction = session.beginTransaction()) { 103 | Result result = transaction 104 | .run("MATCH (f:Framework {name: $name}) - [:CAN_USE] -> (n) RETURN f, n", 105 | Values.parameters("name", "Quarkus")); 106 | result.forEachRemaining( 107 | record -> System.out.printf("%s works with %s%n", record.get("n").get("name").asString(), 108 | record.get("f").get("name").asString())); 109 | transaction.commit(); 110 | } 111 | } 112 | 113 | private void reportException(String errorMessage, final Exception e, final PrintWriter writer) { 114 | if (errorMessage != null) { 115 | writer.write(errorMessage); 116 | writer.write(" "); 117 | } 118 | writer.write(e.toString()); 119 | writer.append("\n\t"); 120 | e.printStackTrace(writer); 121 | writer.append("\n\t"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /integration-tests/src/main/java/io/quarkus/it/neo4j/ReactiveFruitResource.java: -------------------------------------------------------------------------------- 1 | // tag::get[] 2 | package io.quarkus.it.neo4j; 3 | 4 | import java.util.Map; 5 | 6 | import jakarta.inject.Inject; 7 | import jakarta.ws.rs.Consumes; 8 | import jakarta.ws.rs.GET; 9 | import jakarta.ws.rs.POST; 10 | import jakarta.ws.rs.Path; 11 | import jakarta.ws.rs.Produces; 12 | import jakarta.ws.rs.core.MediaType; 13 | 14 | import org.jboss.resteasy.reactive.ResponseStatus; 15 | import org.neo4j.driver.Driver; 16 | import org.neo4j.driver.reactive.ReactiveResult; 17 | import org.neo4j.driver.reactive.ReactiveSession; 18 | 19 | import io.smallrye.mutiny.Multi; 20 | import io.smallrye.mutiny.Uni; 21 | 22 | @Path("reactivefruits") 23 | @Consumes(MediaType.APPLICATION_JSON) 24 | public class ReactiveFruitResource { 25 | 26 | @Inject 27 | Driver driver; 28 | 29 | static Uni sessionFinalizer(ReactiveSession session) { // <.> 30 | return Uni.createFrom().publisher(session.close()); 31 | } 32 | 33 | @GET 34 | @Produces(MediaType.SERVER_SENT_EVENTS) 35 | public Multi get() { 36 | // Create a stream from a resource we can close in a finalizer... 37 | return Multi.createFrom().resource(() -> driver.session(ReactiveSession.class), // <.> 38 | session -> session.executeRead(tx -> { 39 | var result = tx.run("MATCH (f:Fruit) RETURN f.name as name ORDER BY f.name"); 40 | return Multi.createFrom().publisher(result).flatMap(ReactiveResult::records); 41 | })) 42 | .withFinalizer(ReactiveFruitResource::sessionFinalizer) // <.> 43 | .map(record -> record.get("name").asString()); 44 | } 45 | // end::get[] 46 | 47 | // tag::create[] 48 | @POST 49 | @Produces(MediaType.TEXT_PLAIN) 50 | @ResponseStatus(201) 51 | public Uni create(Fruit fruit) { 52 | 53 | return Uni.createFrom().emitter(e -> Multi.createFrom().resource(() -> driver.session(ReactiveSession.class), // <.> 54 | session -> session.executeWrite(tx -> { 55 | var result = tx.run( 56 | "CREATE (f:Fruit {id: randomUUID(), name: $name}) RETURN f", 57 | Map.of("name", fruit.name)); 58 | return Multi.createFrom().publisher(result).flatMap(ReactiveResult::records); 59 | })) 60 | .withFinalizer(ReactiveFruitResource::sessionFinalizer) 61 | .map(record -> Fruit.from(record.get("f").asNode())) 62 | .toUni() 63 | .subscribe().with( // <.> 64 | persistedFruit -> e.complete("/fruits/" + persistedFruit.id))); 65 | } 66 | // end::create[] 67 | 68 | // tag::get[] 69 | } 70 | // end::get[] 71 | -------------------------------------------------------------------------------- /integration-tests/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.neo4j.uri = ${neo4j.uri} 2 | quarkus.neo4j.authentication.username = ${neo4j.username} 3 | quarkus.neo4j.authentication.password = ${neo4j.password} 4 | quarkus.neo4j.pool.metrics.enabled = true 5 | 6 | # Disabling SSL is not recommended in production and only needed for this integration test. 7 | # Disabling SSL will disable encrypted bolt communication. 8 | quarkus.ssl.native=false 9 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityInGraalITCase.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.it.neo4j; 2 | 3 | import io.quarkus.test.junit.QuarkusIntegrationTest; 4 | 5 | /** 6 | * Test various Neo4j operations running in native mode. 7 | */ 8 | @QuarkusIntegrationTest 9 | public class Neo4jFunctionalityInGraalITCase extends Neo4jFunctionalityTest { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/io/quarkus/it/neo4j/Neo4jFunctionalityTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.it.neo4j; 2 | 3 | import static org.hamcrest.Matchers.containsInAnyOrder; 4 | import static org.hamcrest.Matchers.containsString; 5 | import static org.hamcrest.Matchers.equalTo; 6 | import static org.hamcrest.Matchers.greaterThan; 7 | import static org.hamcrest.Matchers.is; 8 | import static org.hamcrest.Matchers.matchesRegex; 9 | import static org.hamcrest.Matchers.notNullValue; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | import java.io.IOException; 13 | import java.util.Properties; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.Stream; 16 | 17 | import jakarta.ws.rs.core.Response.Status; 18 | 19 | import org.hamcrest.Matcher; 20 | import org.junit.jupiter.api.AfterAll; 21 | import org.junit.jupiter.api.BeforeAll; 22 | import org.junit.jupiter.api.BeforeEach; 23 | import org.junit.jupiter.api.Test; 24 | import org.neo4j.driver.AuthTokens; 25 | import org.neo4j.driver.Driver; 26 | import org.neo4j.driver.GraphDatabase; 27 | 28 | import io.quarkus.test.junit.QuarkusTest; 29 | import io.restassured.RestAssured; 30 | import io.restassured.http.ContentType; 31 | 32 | /** 33 | * Test connecting via Neo4j Java-Driver to Neo4j. 34 | * Can quickly start a matching database with: 35 | * 36 | *

 37 |  *     docker run --publish=7474:7474 --publish=7687:7687 -e 'NEO4J_AUTH=neo4j/secret' neo4j:4.4
 38 |  * 
39 | */ 40 | @QuarkusTest 41 | public class Neo4jFunctionalityTest { 42 | 43 | static Driver driver; 44 | 45 | private String apfelId; 46 | 47 | @BeforeAll 48 | public static void connectDriver() throws IOException { 49 | 50 | var properties = new Properties(); 51 | properties.load(Neo4jFunctionalityTest.class.getResourceAsStream("/application.properties")); 52 | var defaultUri = "bolt://localhost:7687"; 53 | var uri = properties.getProperty("quarkus.neo4j.uri", defaultUri); 54 | if (uri.startsWith("$")) { 55 | uri = defaultUri; 56 | } 57 | var defaultPassword = "secret"; 58 | var password = properties.getProperty("quarkus.neo4j.authentication.password", defaultPassword); 59 | if (uri.startsWith("$")) { 60 | password = defaultPassword; 61 | } 62 | driver = GraphDatabase.driver(uri, AuthTokens.basic("neo4j", password)); 63 | } 64 | 65 | @AfterAll 66 | public static void closeDriver() { 67 | 68 | driver.close(); 69 | } 70 | 71 | @BeforeEach 72 | public void prepareFruits() { 73 | 74 | try (var session = driver.session()) { 75 | 76 | session.run("MATCH (f:Fruit) DETACH DELETE f").consume(); 77 | apfelId = session.run("MERGE (f:Fruit {name: 'Apfel'}) ON CREATE SET f.id = randomUUID() RETURN f.id") 78 | .single().get(0).asString(); 79 | } 80 | } 81 | 82 | @Test 83 | void getFruitsShouldWork() { 84 | 85 | var response = RestAssured.given() 86 | .when().get("/fruits/") 87 | .then().statusCode(Status.OK.getStatusCode()) 88 | .extract().jsonPath(); 89 | 90 | assertEquals(apfelId, response.getString("[0].id")); 91 | assertEquals("Apfel", response.getString("[0].name")); 92 | } 93 | 94 | @Test 95 | void getReactiveFruitsShouldWork() { 96 | 97 | RestAssured.given() 98 | .when().get("/reactivefruits/") 99 | .then().statusCode(Status.OK.getStatusCode()) 100 | .statusCode(200) 101 | .body(containsString("data:Apfel")); 102 | } 103 | 104 | @Test 105 | void createFruitsShouldWork() { 106 | 107 | RestAssured.given() 108 | .contentType(ContentType.JSON) 109 | .body(new Fruit("Kartoffel")) 110 | .when().post("/fruits/") 111 | .then().statusCode(Status.CREATED.getStatusCode()) 112 | .header("Location", matchesRegex("(?:https?://.+)?/fruits/[a-f\\d]{8}(?:-[a-f\\d]{4}){4}[a-f\\d]{8}")); 113 | } 114 | 115 | @Test 116 | void createReactiveFruitsShouldWork() { 117 | 118 | RestAssured.given() 119 | .contentType(ContentType.JSON) 120 | .body(new Fruit("Kartoffel")) 121 | .when().post("/reactivefruits/") 122 | .prettyPeek() 123 | .then().statusCode(Status.CREATED.getStatusCode()) 124 | .body(matchesRegex("/fruits/[a-f\\d]{8}(?:-[a-f\\d]{4}){4}[a-f\\d]{8}")); 125 | } 126 | 127 | @Test 128 | void getSingleFruitShouldWork() { 129 | 130 | var response = RestAssured.given() 131 | .when().get("/fruits/" + apfelId) 132 | .then().statusCode(Status.OK.getStatusCode()) 133 | .extract().jsonPath(); 134 | 135 | assertEquals(apfelId, response.getString("id")); 136 | assertEquals("Apfel", response.getString("name")); 137 | } 138 | 139 | @Test 140 | void deleteFruitShouldWork() { 141 | 142 | var response = RestAssured.given() 143 | .when().delete("/fruits/" + apfelId) 144 | .prettyPeek() 145 | .then().statusCode(Status.NO_CONTENT.getStatusCode()) 146 | .extract().jsonPath(); 147 | } 148 | 149 | @Test 150 | public void testBlockingNeo4jFunctionality() { 151 | RestAssured.given().when().get("/neo4j/blocking").then().body(is("OK")); 152 | } 153 | 154 | @Test 155 | public void testAsynchronousNeo4jFunctionality() { 156 | RestAssured.given() 157 | .when().get("/neo4j/asynchronous") 158 | .then().statusCode(200) 159 | .body(is(equalTo(Stream.of(1, 2, 3).map(Object::toString).collect(Collectors.joining(",", "[", "]"))))); 160 | } 161 | 162 | @Test 163 | public void testReactiveNeo4jFunctionality() { 164 | RestAssured.given() 165 | .when().get("/neo4j/reactive") 166 | .prettyPeek() 167 | .then().statusCode(200) 168 | .contentType("text/event-stream"); 169 | } 170 | 171 | @Test 172 | public void health() { 173 | RestAssured.when().get("/q/health/ready").then() 174 | .log().all() 175 | .body("status", is("UP"), 176 | "checks.status", containsInAnyOrder("UP"), 177 | "checks.name", containsInAnyOrder("Neo4j connection health check"), 178 | "checks.data.server", containsInAnyOrder(matchesRegex("Neo4j/.*@.*:\\d*")), 179 | "checks.data.edition", containsInAnyOrder(is(notNullValue()))); 180 | } 181 | 182 | @Test 183 | public void metrics() { 184 | RestAssured.given().when().get("/neo4j/blocking").then().body(is("OK")); 185 | assertMetricValue("neo4j.acquired", greaterThan(0)); 186 | assertMetricValue("neo4j.created", greaterThan(0)); 187 | assertMetricValue("neo4j.totalAcquisitionTime", greaterThan(0)); 188 | assertMetricValue("neo4j.totalConnectionTime", greaterThan(0)); 189 | assertMetricValue("neo4j.totalInUseTime", greaterThan(0)); 190 | } 191 | 192 | private void assertMetricValue(String name, Matcher valueMatcher) { 193 | RestAssured 194 | .given().accept(ContentType.JSON) 195 | .when().get("/q/metrics/vendor/" + name) 196 | .then() 197 | .body("'" + name + "'", valueMatcher); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse 6 | quarkiverse-parent 7 | 20 8 | 9 | io.quarkiverse.neo4j 10 | quarkus-neo4j-parent 11 | 5.4.1-SNAPSHOT 12 | pom 13 | Quarkus - Neo4j - Parent 14 | 15 | deployment 16 | runtime 17 | 18 | 19 | scm:git:git@github.com:quarkiverse/quarkus-neo4j.git 20 | scm:git:git@github.com:quarkiverse/quarkus-neo4j.git 21 | https://github.com/quarkiverse/quarkus-neo4j 22 | HEAD 23 | 24 | 25 | 3.8.1 26 | true 27 | 17 28 | 17 29 | UTF-8 30 | UTF-8 31 | 3.23.0 32 | 3.27.3 33 | 5.28.5 34 | 35 | 36 | 37 | 38 | io.quarkus 39 | quarkus-bom 40 | ${quarkus.version} 41 | pom 42 | import 43 | 44 | 45 | org.neo4j.driver 46 | neo4j-java-driver 47 | ${neo4j-java-driver.version} 48 | 49 | 50 | org.assertj 51 | assertj-core 52 | ${assertj.version} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | io.quarkus 61 | quarkus-maven-plugin 62 | ${quarkus.version} 63 | 64 | 65 | io.quarkus 66 | quarkus-config-doc-maven-plugin 67 | ${quarkus.version} 68 | 69 | 70 | maven-compiler-plugin 71 | ${compiler-plugin.version} 72 | 73 | 74 | 75 | maven-surefire-plugin 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | docs 88 | 89 | 90 | performRelease 91 | !true 92 | 93 | 94 | 95 | docs 96 | 97 | 98 | 99 | it 100 | 101 | 102 | performRelease 103 | !true 104 | 105 | 106 | 107 | integration-tests 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /runtime/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.neo4j 6 | quarkus-neo4j-parent 7 | 5.4.1-SNAPSHOT 8 | 9 | quarkus-neo4j 10 | Quarkus - Neo4j - Runtime 11 | Connect to Neo4j graph datastore 12 | 13 | 14 | io.quarkus 15 | quarkus-arc 16 | 17 | 18 | io.quarkus 19 | quarkus-smallrye-health 20 | true 21 | 22 | 23 | 24 | org.neo4j.driver 25 | neo4j-java-driver 26 | 27 | 28 | 29 | io.quarkus 30 | quarkus-junit5-internal 31 | test 32 | 33 | 34 | org.assertj 35 | assertj-core 36 | test 37 | 38 | 39 | org.mockito 40 | mockito-core 41 | test 42 | 43 | 44 | 45 | 46 | 47 | io.quarkus 48 | quarkus-extension-maven-plugin 49 | ${quarkus.version} 50 | 51 | 52 | compile 53 | 54 | extension-descriptor 55 | 56 | 57 | ${project.groupId}:${project.artifactId}-deployment:${project.version} 58 | 59 | 60 | 61 | 62 | 63 | maven-compiler-plugin 64 | 65 | 66 | 67 | io.quarkus 68 | quarkus-extension-processor 69 | ${quarkus.version} 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkus/neo4j/runtime/Neo4jConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.runtime; 2 | 3 | import java.io.File; 4 | import java.nio.file.Path; 5 | import java.time.Duration; 6 | import java.util.Optional; 7 | 8 | import org.neo4j.driver.Config; 9 | 10 | import io.quarkus.runtime.annotations.ConfigDocSection; 11 | import io.quarkus.runtime.annotations.ConfigGroup; 12 | import io.quarkus.runtime.annotations.ConfigPhase; 13 | import io.quarkus.runtime.annotations.ConfigRoot; 14 | import io.smallrye.config.ConfigMapping; 15 | import io.smallrye.config.WithDefault; 16 | import io.smallrye.config.WithName; 17 | 18 | @ConfigMapping(prefix = "quarkus.neo4j") 19 | @ConfigRoot(phase = ConfigPhase.RUN_TIME) 20 | public interface Neo4jConfiguration { 21 | 22 | String DEFAULT_SERVER_URI = "bolt://localhost:7687"; 23 | String DEFAULT_USERNAME = "neo4j"; 24 | String DEFAULT_PASSWORD = "neo4j"; 25 | 26 | /** 27 | * {@return the uri this driver should connect to. The driver supports bolt, bolt+routing or neo4j as schemes} 28 | */ 29 | @WithDefault(DEFAULT_SERVER_URI) 30 | String uri(); 31 | 32 | /** 33 | * {@return the authentication} 34 | */ 35 | @ConfigDocSection 36 | Authentication authentication(); 37 | 38 | /** 39 | * {@return if the driver should use encrypted traffic} 40 | */ 41 | @WithDefault("false") 42 | boolean encrypted(); 43 | 44 | /** 45 | * {@return the trust settings for encrypted traffic} 46 | */ 47 | @ConfigDocSection 48 | TrustSettings trustSettings(); 49 | 50 | /** 51 | * {@return the maximum time transactions are allowed to retry} 52 | */ 53 | @WithDefault("30S") 54 | Duration maxTransactionRetryTime(); 55 | 56 | /** 57 | * {@return the connection pool} 58 | */ 59 | @ConfigDocSection 60 | Pool pool(); 61 | 62 | @ConfigGroup 63 | interface Authentication { 64 | 65 | /** 66 | * {@return the login of the user connecting to the database} 67 | */ 68 | @WithDefault(DEFAULT_USERNAME) 69 | String username(); 70 | 71 | /** 72 | * {@return the password of the user connecting to the database} 73 | */ 74 | @WithDefault(DEFAULT_PASSWORD) 75 | String password(); 76 | 77 | /** 78 | * {@return whether disable authentication or not} 79 | */ 80 | @WithDefault("false") 81 | boolean disabled(); 82 | 83 | /** 84 | * An optional field that when is not empty has precedence over {@link #username} and {@link #password}. It behaves 85 | * the same way as {@literal NEO4J_AUTH} in the official docker image, containing both the username and password 86 | * separated via a single forward slash ({@code /}). 87 | * 88 | * @return a concrete value for the token, overriding all other settings 89 | */ 90 | Optional value(); 91 | } 92 | 93 | @ConfigGroup 94 | interface TrustSettings { 95 | 96 | enum Strategy { 97 | 98 | TRUST_ALL_CERTIFICATES, 99 | 100 | TRUST_CUSTOM_CA_SIGNED_CERTIFICATES, 101 | 102 | TRUST_SYSTEM_CA_SIGNED_CERTIFICATES 103 | } 104 | 105 | /** 106 | * {@return which trust strategy to apply when using encrypted traffic} 107 | */ 108 | @WithDefault("TRUST_SYSTEM_CA_SIGNED_CERTIFICATES") 109 | Strategy strategy(); 110 | 111 | /** 112 | * {@return the file of the certificate to use} 113 | */ 114 | Optional certFile(); 115 | 116 | /** 117 | * {@return whether hostname verification is used} 118 | */ 119 | @WithDefault("false") 120 | boolean hostnameVerificationEnabled(); 121 | 122 | default Config.TrustStrategy toInternalRepresentation() { 123 | 124 | Config.TrustStrategy internalRepresentation; 125 | Strategy nonNullStrategy = strategy() == null ? Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES : strategy(); 126 | internalRepresentation = switch (nonNullStrategy) { 127 | case TRUST_ALL_CERTIFICATES -> Config.TrustStrategy.trustAllCertificates(); 128 | case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES -> Config.TrustStrategy.trustSystemCertificates(); 129 | case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES -> { 130 | File certFile = certFile().map(Path::toFile).filter(File::isFile) 131 | .orElseThrow(() -> new RuntimeException("Configured trust strategy requires a certificate file.")); 132 | yield Config.TrustStrategy.trustCustomCertificateSignedBy(certFile); 133 | } 134 | }; 135 | 136 | if (hostnameVerificationEnabled()) { 137 | internalRepresentation.withHostnameVerification(); 138 | } else { 139 | internalRepresentation.withoutHostnameVerification(); 140 | } 141 | return internalRepresentation; 142 | } 143 | } 144 | 145 | @ConfigGroup 146 | interface Pool { 147 | 148 | /** 149 | * {@return lag, if metrics are enabled} 150 | */ 151 | @WithName("metrics.enabled") 152 | @WithDefault("false") 153 | boolean metricsEnabled(); 154 | 155 | /** 156 | * {@return if leaked sessions logging is enabled} 157 | */ 158 | @WithDefault("false") 159 | boolean logLeakedSessions(); 160 | 161 | /** 162 | * {@return the maximum amount of connections in the connection pool towards a single database} 163 | */ 164 | @WithDefault("100") 165 | int maxConnectionPoolSize(); 166 | 167 | /** 168 | * Pooled connections that have been idle in the pool for longer than this timeout will be tested before they are used 169 | * again. The value {@literal 0} means connections will always be tested for validity and negative values mean 170 | * connections 171 | * will never be tested. 172 | * 173 | * @return the maximum idle time before connections are tested again 174 | */ 175 | @WithDefault("-0.001S") 176 | Duration idleTimeBeforeConnectionTest(); 177 | 178 | /** 179 | * Pooled connections older than this threshold will be closed and removed from the pool. 180 | * 181 | * @return the lifetime of a connection 182 | */ 183 | @WithDefault("1H") 184 | Duration maxConnectionLifetime(); 185 | 186 | /** 187 | * Acquisition of new connections will be attempted for at most configured timeout. 188 | * 189 | * @return the acquisition timeout 190 | */ 191 | @WithDefault("1M") 192 | Duration connectionAcquisitionTimeout(); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkus/neo4j/runtime/Neo4jDriverRecorder.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.runtime; 2 | 3 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 4 | 5 | import java.net.URI; 6 | import java.util.Optional; 7 | import java.util.Set; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.function.Consumer; 10 | import java.util.logging.Level; 11 | 12 | import org.jboss.logging.Logger; 13 | import org.neo4j.driver.AuthToken; 14 | import org.neo4j.driver.AuthTokens; 15 | import org.neo4j.driver.Config; 16 | import org.neo4j.driver.ConnectionPoolMetrics; 17 | import org.neo4j.driver.Driver; 18 | import org.neo4j.driver.GraphDatabase; 19 | import org.neo4j.driver.Logging; 20 | import org.neo4j.driver.internal.Scheme; 21 | 22 | import io.quarkus.arc.Arc; 23 | import io.quarkus.bootstrap.graal.ImageInfo; 24 | import io.quarkus.runtime.RuntimeValue; 25 | import io.quarkus.runtime.ShutdownContext; 26 | import io.quarkus.runtime.annotations.Recorder; 27 | import io.quarkus.runtime.configuration.ConfigurationException; 28 | import io.quarkus.runtime.metrics.MetricsFactory; 29 | import io.quarkus.runtime.ssl.SslContextConfiguration; 30 | 31 | @Recorder 32 | public class Neo4jDriverRecorder { 33 | 34 | private static final Logger log = Logger.getLogger(Neo4jDriverRecorder.class); 35 | 36 | public RuntimeValue initializeDriver(Neo4jConfiguration configuration, ShutdownContext shutdownContext) { 37 | 38 | String uri = configuration.uri(); 39 | AuthToken authToken = getAuthToken(configuration); 40 | 41 | Config.ConfigBuilder configBuilder = createBaseConfig(); 42 | configureSsl(configBuilder, configuration); 43 | configurePoolSettings(configBuilder, configuration.pool()); 44 | configBuilder.withMaxTransactionRetryTime(configuration.maxTransactionRetryTime().toMillis(), TimeUnit.MILLISECONDS); 45 | 46 | Driver driver = GraphDatabase.driver(uri, authToken, configBuilder.build()); 47 | shutdownContext.addShutdownTask(driver::close); 48 | return new RuntimeValue<>(driver); 49 | } 50 | 51 | static AuthToken getAuthToken(Neo4jConfiguration configuration) { 52 | if (configuration.authentication().disabled()) { 53 | return AuthTokens.none(); 54 | } 55 | return configuration.authentication().value() 56 | .map(Neo4jDriverRecorder::toAuthToken) 57 | .orElseGet( 58 | () -> AuthTokens.basic(configuration.authentication().username(), 59 | configuration.authentication().password())); 60 | } 61 | 62 | static AuthToken toAuthToken(String value) { 63 | 64 | var idx = value.indexOf("/"); 65 | if (idx < 1 || idx == value.length() - 1) { 66 | throw new IllegalArgumentException( 67 | "Invalid value for NEO4J_AUTH, the only supported format is /, neither username nor password are optional"); 68 | } 69 | 70 | return AuthTokens.basic(value.substring(0, idx), value.substring(idx + 1)); 71 | } 72 | 73 | public Consumer registerMetrics(Neo4jConfiguration configuration) { 74 | if (configuration.pool() != null && configuration.pool().metricsEnabled()) { 75 | return metricsFactory -> { 76 | // if the pool hasn't been used yet, the ConnectionPoolMetrics object doesn't exist, so use zeros instead 77 | metricsFactory.builder("neo4j.acquired").buildCounter( 78 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::acquired).orElse(0L)); 79 | metricsFactory.builder("neo4j.acquiring").buildCounter( 80 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::acquiring).orElse(0)); 81 | metricsFactory.builder("neo4j.closed").buildCounter( 82 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::closed).orElse(0L)); 83 | metricsFactory.builder("neo4j.created").buildCounter( 84 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::created).orElse(0L)); 85 | metricsFactory.builder("neo4j.creating").buildCounter( 86 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::creating).orElse(0)); 87 | metricsFactory.builder("neo4j.failedToCreate").buildCounter( 88 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::failedToCreate).orElse(0L)); 89 | metricsFactory.builder("neo4j.timedOutToAcquire").buildCounter( 90 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::timedOutToAcquire).orElse(0L)); 91 | metricsFactory.builder("neo4j.totalAcquisitionTime").buildCounter( 92 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::totalAcquisitionTime).orElse(0L)); 93 | metricsFactory.builder("neo4j.totalConnectionTime").buildCounter( 94 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::totalConnectionTime).orElse(0L)); 95 | metricsFactory.builder("neo4j.totalInUseCount").buildCounter( 96 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::totalInUseCount).orElse(0L)); 97 | metricsFactory.builder("neo4j.totalInUseTime").buildCounter( 98 | () -> getConnectionPoolMetrics().map(ConnectionPoolMetrics::totalInUseCount).orElse(0L)); 99 | }; 100 | } else { 101 | return metricsFactory -> { 102 | 103 | }; 104 | } 105 | } 106 | 107 | // Until the pool is actually used for the first time, the ConnectionPoolMetrics object does not exist, 108 | // so this will be populated later 109 | private static Optional connectionPoolMetrics = Optional.empty(); 110 | 111 | private synchronized Optional getConnectionPoolMetrics() { 112 | if (!connectionPoolMetrics.isPresent()) { 113 | connectionPoolMetrics = Arc.container().instance(Driver.class) 114 | .get() 115 | .metrics() 116 | .connectionPoolMetrics() 117 | .stream() 118 | .findFirst(); 119 | } 120 | return connectionPoolMetrics; 121 | } 122 | 123 | private static Config.ConfigBuilder createBaseConfig() { 124 | Config.ConfigBuilder configBuilder = Config.builder(); 125 | Logging logging; 126 | try { 127 | logging = Logging.slf4j(); 128 | } catch (Exception e) { 129 | logging = Logging.javaUtilLogging(Level.INFO); 130 | } 131 | configBuilder.withLogging(logging); 132 | return configBuilder; 133 | } 134 | 135 | private static void configureSsl(Config.ConfigBuilder configBuilder, Neo4jConfiguration configuration) { 136 | 137 | var uri = URI.create(configuration.uri()); 138 | var scheme = uri.getScheme(); 139 | 140 | boolean isSecurityScheme = Scheme.isSecurityScheme(scheme); 141 | boolean isNativeWithoutSslSupport = ImageInfo.inImageRuntimeCode() && !SslContextConfiguration.isSslNativeEnabled(); 142 | 143 | // If the URL indicates a secure / security scheme 144 | // (either neo4j+s (encrypted with full cert checks) or neo4j+ssc (encrypted, but allows self signed certs) 145 | // we cannot configure security settings again, so we check only if Quarkus can provide the necessary runtime 146 | if (isSecurityScheme) { 147 | if (isNativeWithoutSslSupport) { 148 | throw new ConfigurationException("You cannot use " + scheme 149 | + " because SSL support is not available in your current native image setup.", 150 | Set.of("quarkus.neo4j.uri")); 151 | } 152 | 153 | // Nothing to configure 154 | return; 155 | } 156 | 157 | // Disable encryption regardless of user configuration when ssl is not natively enabled. 158 | if (isNativeWithoutSslSupport) { 159 | log.warn( 160 | "Native SSL is disabled, communication between this client and the Neo4j server cannot be encrypted."); 161 | configBuilder.withoutEncryption(); 162 | } else { 163 | if (configuration.encrypted()) { 164 | configBuilder.withEncryption(); 165 | configBuilder.withTrustStrategy(configuration.trustSettings().toInternalRepresentation()); 166 | } else { 167 | configBuilder.withoutEncryption(); 168 | } 169 | } 170 | } 171 | 172 | private static void configurePoolSettings(Config.ConfigBuilder configBuilder, Neo4jConfiguration.Pool pool) { 173 | 174 | if (log.isDebugEnabled()) { 175 | log.debug("Configuring Neo4j pool settings with " + pool); 176 | } 177 | 178 | if (pool.logLeakedSessions()) { 179 | configBuilder.withLeakedSessionsLogging(); 180 | } 181 | 182 | configBuilder.withMaxConnectionPoolSize(pool.maxConnectionPoolSize()); 183 | configBuilder.withConnectionLivenessCheckTimeout(pool.idleTimeBeforeConnectionTest().toMillis(), MILLISECONDS); 184 | configBuilder.withMaxConnectionLifetime(pool.maxConnectionLifetime().toMillis(), MILLISECONDS); 185 | configBuilder.withConnectionAcquisitionTimeout(pool.connectionAcquisitionTimeout().toMillis(), MILLISECONDS); 186 | 187 | if (pool.metricsEnabled()) { 188 | configBuilder.withDriverMetrics(); 189 | } else { 190 | configBuilder.withoutDriverMetrics(); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkus/neo4j/runtime/health/Neo4jHealthCheck.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.runtime.health; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | import jakarta.inject.Inject; 5 | 6 | import org.eclipse.microprofile.health.HealthCheck; 7 | import org.eclipse.microprofile.health.HealthCheckResponse; 8 | import org.eclipse.microprofile.health.HealthCheckResponseBuilder; 9 | import org.eclipse.microprofile.health.Readiness; 10 | import org.jboss.logging.Logger; 11 | import org.neo4j.driver.AccessMode; 12 | import org.neo4j.driver.Driver; 13 | import org.neo4j.driver.Record; 14 | import org.neo4j.driver.Session; 15 | import org.neo4j.driver.SessionConfig; 16 | import org.neo4j.driver.exceptions.RetryableException; 17 | import org.neo4j.driver.summary.ResultSummary; 18 | import org.neo4j.driver.summary.ServerInfo; 19 | 20 | @Readiness 21 | @ApplicationScoped 22 | public class Neo4jHealthCheck implements HealthCheck { 23 | 24 | private static final Logger log = Logger.getLogger(Neo4jHealthCheck.class); 25 | 26 | /** 27 | * The Cypher statement used to verify Neo4j is up. 28 | */ 29 | private static final String CYPHER = "CALL dbms.components() YIELD versions, name, edition WHERE name = 'Neo4j Kernel' RETURN edition, versions[0] as version"; 30 | /** 31 | * Message indicating that the health check failed. 32 | */ 33 | private static final String MESSAGE_HEALTH_CHECK_FAILED = "Neo4j health check failed"; 34 | /** 35 | * Message logged before retrying a health check. 36 | */ 37 | private static final String MESSAGE_SESSION_EXPIRED = "Neo4j session has expired, retrying one single time to retrieve server health."; 38 | /** 39 | * The default session config to use while connecting. 40 | */ 41 | private static final SessionConfig DEFAULT_SESSION_CONFIG = SessionConfig.builder() 42 | .withDefaultAccessMode(AccessMode.WRITE) 43 | .build(); 44 | 45 | @Inject 46 | Driver driver; 47 | 48 | @Override 49 | public HealthCheckResponse call() { 50 | 51 | HealthCheckResponseBuilder builder = HealthCheckResponse.named("Neo4j connection health check").up(); 52 | try { 53 | // Retry one time when the session has been expired 54 | try { 55 | return runHealthCheckQuery(builder); 56 | } catch (Throwable exception) { 57 | if (exception instanceof RetryableException) { 58 | log.warn(MESSAGE_SESSION_EXPIRED); 59 | return runHealthCheckQuery(builder); 60 | } 61 | throw exception; 62 | } 63 | } catch (Exception e) { 64 | return builder.down().withData("reason", e.getMessage()).build(); 65 | } 66 | } 67 | 68 | /** 69 | * Applies the given {@link ResultSummary} to the {@link HealthCheckResponseBuilder builder} and calls {@code build} 70 | * afterwards. 71 | * 72 | * @param editionAndVersion a single record containing edition and version 73 | * @param resultSummary the result summary returned by the server 74 | * @param builder the health builder to be modified 75 | * @return the final {@link HealthCheckResponse health check response} 76 | */ 77 | private static HealthCheckResponse buildStatusUp(Record editionAndVersion, ResultSummary resultSummary, 78 | HealthCheckResponseBuilder builder) { 79 | ServerInfo serverInfo = resultSummary.server(); 80 | 81 | builder.withData("server", "Neo4j/" + editionAndVersion.get("version").asString() + "@" + serverInfo.address()); 82 | 83 | String databaseName = resultSummary.database().name(); 84 | if (!(databaseName == null || databaseName.trim().isEmpty())) { 85 | builder.withData("database", databaseName.trim()); 86 | } 87 | builder.withData("edition", editionAndVersion.get("edition").asString()); 88 | 89 | return builder.build(); 90 | } 91 | 92 | private HealthCheckResponse runHealthCheckQuery(HealthCheckResponseBuilder builder) { 93 | // We use WRITE here to make sure UP is returned for a server that supports 94 | // all possible workloads 95 | try (Session session = this.driver.session(DEFAULT_SESSION_CONFIG)) { 96 | var result = session.run(CYPHER); 97 | var editionAndVersion = result.single(); 98 | ResultSummary resultSummary = result.consume(); 99 | return buildStatusUp(editionAndVersion, resultSummary, builder); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /runtime/src/main/resources/META-INF/quarkus-extension.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | artifact: ${project.groupId}:${project.artifactId}:${project.version} 3 | name: "Neo4j client" 4 | metadata: 5 | keywords: 6 | - "neo4j" 7 | - "graph" 8 | - "nosql" 9 | - "datastore" 10 | categories: 11 | - "data" 12 | guide: https://quarkiverse.github.io/quarkiverse-docs/quarkus-neo4j/dev/index.html 13 | config: 14 | - "quarkus.neo4j." 15 | -------------------------------------------------------------------------------- /runtime/src/test/java/io/quarkus/neo4j/runtime/Neo4jDriverRecorderTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkus.neo4j.runtime; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.when; 7 | 8 | import java.util.Optional; 9 | 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.params.ParameterizedTest; 12 | import org.junit.jupiter.params.provider.ValueSource; 13 | import org.neo4j.driver.AuthTokens; 14 | 15 | class Neo4jDriverRecorderTest { 16 | 17 | @Test // GH-168 18 | void authTokenShouldBeNoneWhenDisabled() { 19 | 20 | var configuration = mock(Neo4jConfiguration.class); 21 | var authentication = mock(Neo4jConfiguration.Authentication.class); 22 | when(authentication.disabled()).thenReturn(true); 23 | when(configuration.authentication()).thenReturn(authentication); 24 | var authToken = Neo4jDriverRecorder.getAuthToken(configuration); 25 | assertThat(authToken).isEqualTo(AuthTokens.none()); 26 | } 27 | 28 | @Test // GH-168 29 | void shouldUseUserNamePassword() { 30 | 31 | var configuration = mock(Neo4jConfiguration.class); 32 | var authentication = mock(Neo4jConfiguration.Authentication.class); 33 | when(authentication.value()).thenReturn(Optional.empty()); 34 | when(authentication.username()).thenReturn("foo"); 35 | when(authentication.password()).thenReturn("bar"); 36 | when(configuration.authentication()).thenReturn(authentication); 37 | var authToken = Neo4jDriverRecorder.getAuthToken(configuration); 38 | assertThat(authToken).isEqualTo(AuthTokens.basic("foo", "bar")); 39 | } 40 | 41 | @Test // GH-168 42 | void shouldUseValue() { 43 | 44 | var configuration = mock(Neo4jConfiguration.class); 45 | var authentication = mock(Neo4jConfiguration.Authentication.class); 46 | when(authentication.value()).thenReturn(Optional.of("foo/bar")); 47 | when(configuration.authentication()).thenReturn(authentication); 48 | var authToken = Neo4jDriverRecorder.getAuthToken(configuration); 49 | assertThat(authToken).isEqualTo(AuthTokens.basic("foo", "bar")); 50 | } 51 | 52 | @Test // GH-168 53 | void valueShouldHavePrecedence() { 54 | 55 | var configuration = mock(Neo4jConfiguration.class); 56 | var authentication = mock(Neo4jConfiguration.Authentication.class); 57 | when(authentication.value()).thenReturn(Optional.of("foo/bar")); 58 | when(authentication.username()).thenReturn("wurst"); 59 | when(authentication.password()).thenReturn("salat"); 60 | when(configuration.authentication()).thenReturn(authentication); 61 | var authToken = Neo4jDriverRecorder.getAuthToken(configuration); 62 | assertThat(authToken).isEqualTo(AuthTokens.basic("foo", "bar")); 63 | } 64 | 65 | @ParameterizedTest // GH-168 66 | @ValueSource(strings = { "foo", "/foo", "foo/" }) 67 | void invalidAuthValuesShouldBeCaught(String value) { 68 | 69 | assertThatIllegalArgumentException().isThrownBy(() -> Neo4jDriverRecorder.toAuthToken(value)) 70 | .withMessage( 71 | "Invalid value for NEO4J_AUTH, the only supported format is /, neither username nor password are optional"); 72 | } 73 | } 74 | --------------------------------------------------------------------------------