diff --git a/.codex-task/GLOBAL-TODO.md b/.codex-task/GLOBAL-TODO.md
new file mode 100644
index 000000000..581f0124f
--- /dev/null
+++ b/.codex-task/GLOBAL-TODO.md
@@ -0,0 +1,11 @@
+# 全局 TODO
+
+仅记录各 goal 完成后仍有价值、但不属于已完成 goal 的后续事项。
+
+## Hubble 2.0
+
+- 完成 Hubble UI/UX 重构:基于真实双模式与 Chrome 基线完成三方向选型,交付“理解图、准备数据、查询分析”三个核心旅程,并以能力映射、可访问性、性能和发布包回归收口。
+- 现代化 React、Ant Design、X6/Graphin/G6、Dagre 与构建链;先固化核心页面、图交互、性能和浏览器回归基线,再分批升级并清理兼容层。
+- HugeGraph Server 提供保留 Schema 的 data-only clear API 后,恢复 Hubble“仅清数据”能力及权限、默认图和失败合同测试。
+- 重构 Gremlin Basic 认证,移除 HTTP Session 中的短期明文凭据,并验证 token 生命周期。
+- 发布加固:建立逐 JAR license/native allowlist 与 NOTICE 审计,RC 使用正式发布密钥验证 `.sha512`/`.asc`。
diff --git a/.github/actions/get-hugegraph-commit/action.yml b/.github/actions/get-hugegraph-commit/action.yml
index f6cf438d7..b73b49451 100644
--- a/.github/actions/get-hugegraph-commit/action.yml
+++ b/.github/actions/get-hugegraph-commit/action.yml
@@ -1,22 +1,49 @@
-name: 'Get HugeGraph stable commit id'
-description: 'Fetch the latest HugeGraph release commit SHA and expose it as output and env variable'
+name: 'Get HugeGraph commit id'
+description: 'Fetch a HugeGraph PR or latest release commit SHA and expose it as output and env variable'
+
+inputs:
+ server-pr-number:
+ description: 'HugeGraph Server PR number to use as integration baseline'
+ required: false
+ default: ''
outputs:
commit_id:
- description: 'The commit SHA of the latest HugeGraph release'
+ description: 'The HugeGraph commit SHA selected as integration baseline'
value: ${{ steps.get-commit.outputs.commit_id }}
+ fetch_ref:
+ description: 'Fetchable HugeGraph git ref selected as integration baseline'
+ value: ${{ steps.get-commit.outputs.fetch_ref }}
runs:
using: composite
steps:
- name: Get HugeGraph stable commit id
id: get-commit
- uses: actions/github-script@v7
+ uses: actions/github-script@v9
with:
script: |
const owner = 'apache';
const repo = 'hugegraph';
+ const serverPrNumber = '${{ inputs.server-pr-number }}';
try {
+ if (serverPrNumber) {
+ const pull_number = Number(serverPrNumber);
+ if (!Number.isInteger(pull_number) || pull_number <= 0) {
+ throw new Error(`Invalid server PR number: ${serverPrNumber}`);
+ }
+ const { data: pull } = await github.rest.pulls.get({
+ owner, repo, pull_number
+ });
+ const sha = pull.head.sha;
+ const fetchRef = `refs/pull/${pull_number}/head`;
+ core.exportVariable('COMMIT_ID', sha);
+ core.exportVariable('COMMIT_REF', fetchRef);
+ core.setOutput('commit_id', sha);
+ core.setOutput('fetch_ref', fetchRef);
+ console.log(`Using HugeGraph Server PR #${pull_number} ${fetchRef} (${sha})`);
+ return;
+ }
const { data: release } = await github.rest.repos.getLatestRelease({ owner, repo });
const tagName = release.tag_name;
const { data: ref } = await github.rest.git.getRef({
@@ -30,7 +57,9 @@ runs:
throw new Error(`Unexpected ref type: ${ref.object.type}`);
}
core.exportVariable('COMMIT_ID', sha);
+ core.exportVariable('COMMIT_REF', `refs/tags/${tagName}`);
core.setOutput('commit_id', sha);
+ core.setOutput('fetch_ref', `refs/tags/${tagName}`);
console.log(`Using HugeGraph release ${tagName} (${sha})`);
} catch (error) {
core.setFailed(`Failed to get HugeGraph commit: ${error.message}`);
diff --git a/.github/actions/setup-java-env/action.yml b/.github/actions/setup-java-env/action.yml
index 18b136123..9d897bfd0 100644
--- a/.github/actions/setup-java-env/action.yml
+++ b/.github/actions/setup-java-env/action.yml
@@ -19,7 +19,7 @@ runs:
using: composite
steps:
- name: Install JDK ${{ inputs.java-version }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: ${{ inputs.java-version }}
distribution: ${{ inputs.distribution }}
diff --git a/.github/actions/upload-coverage/action.yml b/.github/actions/upload-coverage/action.yml
index 7b07b84f2..143da955b 100644
--- a/.github/actions/upload-coverage/action.yml
+++ b/.github/actions/upload-coverage/action.yml
@@ -14,7 +14,7 @@ runs:
using: composite
steps:
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v7
with:
token: ${{ inputs.token }}
- file: ${{ inputs.file }}
+ files: ${{ inputs.file }}
diff --git a/.github/labeler.yml b/.github/labeler.yml
index e1ee55ba0..62bb14990 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -18,16 +18,19 @@
#
# Pull Request Labeler Github Action Configuration: https://github.com/marketplace/actions/labeler
-name: Add Scope Label
-
client:
- - hugegraph-client*/**/*
-# - hugegraph-client-go/**/*
+ - changed-files:
+ - any-glob-to-any-file: 'hugegraph-client*/**/*'
+# - any-glob-to-any-file: 'hugegraph-client-go/**/*'
hubble:
- - hugegraph-hubble/**/*
+ - changed-files:
+ - any-glob-to-any-file: 'hugegraph-hubble/**/*'
loader:
- - hugegraph-loader/**/*
+ - changed-files:
+ - any-glob-to-any-file: 'hugegraph-loader/**/*'
tools:
- - hugegraph-tools/**/*
+ - changed-files:
+ - any-glob-to-any-file: 'hugegraph-tools/**/*'
spark:
- - hugegraph-spark-connector/**/*
+ - changed-files:
+ - any-glob-to-any-file: 'hugegraph-spark-connector/**/*'
diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml
index 9ed4e723f..e076dd256 100644
--- a/.github/workflows/client-ci.yml
+++ b/.github/workflows/client-ci.yml
@@ -5,7 +5,7 @@ on:
push:
branches:
- master
- - /^release-.*$/
+ - 'release-*'
paths:
- hugegraph-client/**
- hugegraph-dist/**
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Fetch code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 2
diff --git a/.github/workflows/client-go-ci.yml b/.github/workflows/client-go-ci.yml
index 9c14711e9..520d92d95 100644
--- a/.github/workflows/client-go-ci.yml
+++ b/.github/workflows/client-go-ci.yml
@@ -4,7 +4,7 @@ on:
push:
branches:
- master
- - /^release-.*$/
+ - 'release-*'
paths:
- hugegraph-client-go/**
- hugegraph-dist/**
@@ -30,7 +30,7 @@ jobs:
steps:
- name: Fetch Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 2
@@ -52,9 +52,10 @@ jobs:
commit-id: ${{ steps.get-commit.outputs.commit_id }}
- name: Init Go env
- uses: actions/setup-go@v5
+ uses: actions/setup-go@v6
with:
go-version: '1.x'
+ cache-dependency-path: hugegraph-client-go/go.sum
- name: Go test
run: |
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 314b54df7..83c3e4d9d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -38,10 +38,10 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v7
- name: Setup Java JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '11'
@@ -53,12 +53,12 @@ jobs:
mv -vf .github/configs/settings.xml $HOME/.m2/settings.xml
- name: Use Node.js 16
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: '16'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -73,7 +73,7 @@ jobs:
# If this step fails, then you should remove it and run the build manually (see below)
- if: matrix.language == 'python' || matrix.language == 'javascript'
name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@v4
- if: matrix.language == 'java'
name: Build Java
@@ -91,12 +91,12 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v4
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
- uses: actions/checkout@v3
+ uses: actions/checkout@v7
- name: 'Dependency Review'
- uses: actions/dependency-review-action@v2
+ uses: actions/dependency-review-action@v5.0.0
diff --git a/.github/workflows/hubble-ci.yml b/.github/workflows/hubble-ci.yml
index 2b09e74ff..5445dbb0b 100644
--- a/.github/workflows/hubble-ci.yml
+++ b/.github/workflows/hubble-ci.yml
@@ -5,7 +5,7 @@ on:
push:
branches:
- master
- - /^release-.*$/
+ - 'release-*'
paths:
- hugegraph-hubble/**
- hugegraph-dist/**
@@ -23,6 +23,8 @@ on:
env:
TRAVIS_DIR: hugegraph-hubble/hubble-dist/assembly/travis
+ HUGEGRAPH_SERVER_COMMIT: 99936be5f41fccd193f120e01206e3cf3c73a050
+ HUGEGRAPH_SERVER_FETCH_REF: refs/heads/master
jobs:
hubble-ci:
@@ -37,27 +39,74 @@ jobs:
steps:
- name: Fetch Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 2
- - name: Get HugeGraph stable commit id
- id: get-commit
- uses: ./.github/actions/get-hugegraph-commit
-
- name: Setup Java environment
uses: ./.github/actions/setup-java-env
with:
java-version: ${{ matrix.JAVA_VERSION }}
- distribution: 'adopt'
+ distribution: 'temurin'
use-stage: ${{ env.USE_STAGE }}
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
+ - name: Set up Node.js 18.20.8
+ uses: actions/setup-node@v6
+ with:
+ node-version: '18.20.8'
+
+ # we also should cache python & yarn & downloads to avoid useless work
+ - name: Cache Maven packages
+ uses: actions/cache@v6
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Cache Node modules
+ uses: actions/cache@v6
+ with:
+ path: |
+ hugegraph-hubble/hubble-fe/node_modules
+ hugegraph-hubble/hubble-fe/node
+ ~/.cache/yarn
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package.json', '**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Cache Playwright browsers
+ uses: actions/cache@v6
+ with:
+ path: ~/.cache/ms-playwright
+ key: ${{ runner.os }}-playwright-${{ hashFiles('hugegraph-hubble/hubble-fe/yarn.lock') }}
+
+ - name: Cache HugeGraph Server
+ uses: actions/cache@v6
+ with:
+ path: ~/hugegraph-cache-${{ env.HUGEGRAPH_SERVER_COMMIT }}
+ key: ${{ runner.os }}-hugegraph-server-${{ env.HUGEGRAPH_SERVER_COMMIT }}
+
+ - name: Frontend i18n check
+ working-directory: hugegraph-hubble/hubble-fe
+ env:
+ NODE_OPTIONS: --max-old-space-size=4096
+ run: |
+ yarn install --frozen-lockfile --network-timeout 600000 --prefer-offline --ignore-engines
+ yarn lint
+ yarn i18n:check
+ node --test ../hubble-dist/assembly/travis/ui_auth.test.js
+
+ - name: Install Playwright Chromium
+ working-directory: hugegraph-hubble/hubble-fe
+ run: yarn playwright install --with-deps chromium
+
- name: Compile
run: |
mvn install -pl hugegraph-client,hugegraph-loader -am -Dmaven.javadoc.skip=true -DskipTests -ntp
@@ -66,8 +115,19 @@ jobs:
- name: Prepare env and service
env:
- COMMIT_ID: ${{ steps.get-commit.outputs.commit_id }}
+ COMMIT_ID: ${{ env.HUGEGRAPH_SERVER_COMMIT }}
+ COMMIT_REF: ${{ env.HUGEGRAPH_SERVER_FETCH_REF }}
+ NODE_OPTIONS: --max-old-space-size=4096
run: |
+ echo "=== Environment Info ==="
+ node -v
+ yarn -v
+ echo "NODE_OPTIONS: $NODE_OPTIONS"
+ echo "COMMIT_ID: $COMMIT_ID"
+ echo "COMMIT_REF: $COMMIT_REF"
+ free -h || true
+ echo "======================="
+
python -m pip install -r ${TRAVIS_DIR}/requirements.txt
cd hugegraph-hubble
mvn package -Dmaven.test.skip=true
@@ -77,17 +137,61 @@ jobs:
./stop-hubble.sh
cd ../../../
pwd
- $TRAVIS_DIR/install-hugegraph.sh $COMMIT_ID
+ $TRAVIS_DIR/install-hugegraph.sh $COMMIT_ID $COMMIT_REF
+
+ - name: Release package audit
+ env:
+ GNUPGHOME: ${{ runner.temp }}/hubble-release-gate-gpghome
+ run: |
+ HUBBLE_TARBALL="$(ls hugegraph-hubble/target/apache-hugegraph-hubble-*.tar.gz | head -n 1)"
+ if [[ -z "${HUBBLE_TARBALL}" ]]; then
+ echo "Hubble tarball not found" >&2
+ exit 1
+ fi
+
+ mkdir -p "${GNUPGHOME}"
+ chmod 700 "${GNUPGHOME}"
+ shasum -a 512 "${HUBBLE_TARBALL}" > "${HUBBLE_TARBALL}.sha512"
+ printf '%s\n' \
+ '%no-protection' \
+ 'Key-Type: RSA' \
+ 'Key-Length: 2048' \
+ 'Subkey-Type: RSA' \
+ 'Subkey-Length: 2048' \
+ 'Name-Real: Hubble CI Release Gate' \
+ 'Name-Email: hubble-ci-release-gate@example.invalid' \
+ 'Expire-Date: 0' \
+ '%commit' \
+ > "${RUNNER_TEMP}/hubble-release-gate-key.conf"
+ gpg --batch --generate-key "${RUNNER_TEMP}/hubble-release-gate-key.conf"
+ gpg --batch --yes --armor --detach-sign "${HUBBLE_TARBALL}"
+ bash "${TRAVIS_DIR}/check-hubble-dist.sh" "${HUBBLE_TARBALL}" \
+ --require-sidecars \
+ --json-output hugegraph-hubble/target/hubble-dist-check.json
- name: Unit test
run: mvn test -P unit-test -pl hugegraph-hubble/hubble-be -ntp
- name: API test
- env:
- CI: false
run: |
- cd hugegraph-hubble && ls
- hubble-dist/assembly/travis/run-api-test.sh
+ cd hugegraph-hubble
+ HUBBLE_TARBALL="$(ls target/apache-hugegraph-hubble-*.tar.gz | head -n 1)"
+ HUBBLE_694_EVIDENCE_DIR=target/hubble-live-acceptance \
+ hubble-dist/assembly/travis/verify-hubble-issue-694.sh \
+ "${HUBBLE_TARBALL}" http://127.0.0.1:8080
+
+ - name: Upload Hubble smoke evidence
+ if: ${{ always() }}
+ uses: actions/upload-artifact@v7
+ with:
+ name: hubble-smoke-java-${{ matrix.JAVA_VERSION }}-${{ github.run_attempt }}
+ path: |
+ hugegraph-hubble/target/hubble-dist-check.json
+ hugegraph-hubble/target/hubble-live-acceptance/**/*.json
+ hugegraph-hubble/target/hubble-live-acceptance/**/*.png
+ hugegraph-hubble/target/hubble-live-acceptance/**/*.log
+ if-no-files-found: warn
+ retention-days: 14
- name: Upload coverage to Codecov
uses: ./.github/actions/upload-coverage
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 18cb05b4a..26285abb5 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -28,7 +28,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@v4
+ - uses: actions/labeler@v6
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
diff --git a/.github/workflows/license-checker.yml b/.github/workflows/license-checker.yml
index ef51a265e..3b2d3d701 100644
--- a/.github/workflows/license-checker.yml
+++ b/.github/workflows/license-checker.yml
@@ -23,7 +23,7 @@ on:
push:
branches:
- master
- - /^release-.*$/
+ - 'release-*'
pull_request:
jobs:
@@ -31,10 +31,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Fetch Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
# More info could refer to: https://github.com/apache/skywalking-eyes
- name: Check License Header
- uses: apache/skywalking-eyes@main
+ uses: apache/skywalking-eyes@29bd646002b63bb4c0ed1648d1654c35fb8ff027 # main 2026-07-12
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -54,14 +54,14 @@ jobs:
USE_STAGE: 'true' # Whether to include the stage repository.
steps:
- name: Checkout source
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- name: Set up JDK 11
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v5
with:
java-version: '11'
- distribution: 'adopt'
+ distribution: 'temurin'
- name: Use Node.js 16
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: '16'
diff --git a/.github/workflows/loader-ci.yml b/.github/workflows/loader-ci.yml
index a7b085ab3..f8eee02d3 100644
--- a/.github/workflows/loader-ci.yml
+++ b/.github/workflows/loader-ci.yml
@@ -5,7 +5,7 @@ on:
push:
branches:
- master
- - /^release-.*$/
+ - 'release-*'
paths:
- hugegraph-loader/**
- hugegraph-dist/**
@@ -43,7 +43,7 @@ jobs:
steps:
- name: Fetch Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 2
@@ -55,17 +55,17 @@ jobs:
uses: ./.github/actions/setup-java-env
with:
java-version: ${{ matrix.JAVA_VERSION }}
- distribution: 'adopt'
+ distribution: 'temurin'
use-stage: ${{ env.USE_STAGE }}
- name: Cache Hadoop
- uses: actions/cache@v4
+ uses: actions/cache@v6
with:
path: ~/hadoop-3.3.6.tar.gz
key: ${{ runner.os }}-hadoop-3.3.6
- name: Cache HugeGraph Server
- uses: actions/cache@v4
+ uses: actions/cache@v6
with:
path: ~/hugegraph-cache-${{ steps.get-commit.outputs.commit_id }}
key: ${{ runner.os }}-hugegraph-server-${{ steps.get-commit.outputs.commit_id }}
diff --git a/.github/workflows/spark-connector-ci.yml b/.github/workflows/spark-connector-ci.yml
index b61b56e2f..50693024a 100644
--- a/.github/workflows/spark-connector-ci.yml
+++ b/.github/workflows/spark-connector-ci.yml
@@ -5,7 +5,7 @@ on:
push:
branches:
- master
- - /^release-.*$/
+ - 'release-*'
paths:
- hugegraph-spark-connector/**
- hugegraph-dist/**
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Fetch Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 2
@@ -43,7 +43,7 @@ jobs:
uses: ./.github/actions/setup-java-env
with:
java-version: ${{ matrix.JAVA_VERSION }}
- distribution: 'adopt'
+ distribution: 'temurin'
use-stage: ${{ env.USE_STAGE }}
- name: Compile
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 64b62dc5a..c46000e85 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -12,7 +12,7 @@ jobs:
pull-requests: write
steps:
- - uses: actions/stale@v8
+ - uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Due to the lack of activity, the current issue is marked as stale and will be closed after 20 days, any update will remove the stale label'
diff --git a/.github/workflows/tools-ci.yml b/.github/workflows/tools-ci.yml
index dda37f3e3..340f71aa1 100644
--- a/.github/workflows/tools-ci.yml
+++ b/.github/workflows/tools-ci.yml
@@ -5,7 +5,7 @@ on:
push:
branches:
- master
- - /^release-.*$/
+ - 'release-*'
paths:
- hugegraph-tools/**
- hugegraph-dist/**
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Fetch Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 2
@@ -43,7 +43,7 @@ jobs:
uses: ./.github/actions/setup-java-env
with:
java-version: ${{ matrix.JAVA_VERSION }}
- distribution: 'adopt'
+ distribution: 'temurin'
use-stage: ${{ env.USE_STAGE }}
- name: Compile
diff --git a/.gitignore b/.gitignore
index 528ddad73..1467dcafc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,6 +103,25 @@ Thumbs.db
go.env
# AI-IDE prompt files (We only keep AGENTS.md, other files could soft-linked it when needed)
+# Local AI-agent workspaces and run evidence
+.codex-task/*
+!.codex-task/GLOBAL-TODO.md
+.autonomous/
+.claude/
+superpowers/
+
+# Local Hubble runtime / extracted distributions
+/db*.db
+/db*.trace.db
+/logs/
+/hugegraph-hubble/apache-hugegraph-hubble-*/
+/hugegraph-hubble/hubble-dist/apache-hugegraph-hubble-*/
+/hugegraph-loader/apache-hugegraph-loader-*/
+
+# Local dependency analysis reports
+/hugegraph-dist/scripts/dependency/current-dependencies.txt
+/hugegraph-dist/scripts/dependency/result.txt
+
# Claude Projects
CLAUDE.md
CLAUDE_*.md
@@ -124,5 +143,4 @@ codeium-instructions.md
.ai-instructions.md
*.ai-prompt.md
WARP.md
-
-
+/.agents
diff --git a/.serena/project.yml b/.serena/project.yml
index edbab5745..4fec60fbc 100644
--- a/.serena/project.yml
+++ b/.serena/project.yml
@@ -1,22 +1,29 @@
-
-# list of languages for which language servers are started; choose from:
-# al bash clojure cpp csharp
-# csharp_omnisharp dart elixir elm erlang
-# fortran fsharp go groovy haskell
-# haxe java julia kotlin lua
-# markdown
-# matlab nix pascal perl php
-# php_phpactor powershell python python_jedi r
+# list of languages for which language servers are started (LSP backend only); choose from:
+# ada al angular ansible bash
+# bsl clojure cpp cpp_ccls crystal
+# csharp csharp_omnisharp cue dart elixir
+# elm erlang fortran fsharp gdscript
+# go groovy haskell haxe hlsl
+# html java json julia kotlin
+# latex lean4 lua luau markdown
+# matlab msl nix ocaml pascal
+# perl php php_phpactor php_phpantom powershell
+# python python_jedi python_pyrefly python_ty r
# rego ruby ruby_solargraph rust scala
-# swift terraform toml typescript typescript_vts
-# vue yaml zig
-# (This list may be outdated. For the current list, see values of Language enum here:
-# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
-# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
+# scss solidity svelte swift systemverilog
+# terraform toml typescript typescript_vts vue
+# yaml zig
+# (This list may be outdated; generated with scripts/print_language_list.py;
+# For the current list, see values of Language enum here:
+# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
+# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
+# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
+# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
+# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
@@ -46,56 +53,18 @@ read_only: false
# list of tool names to exclude.
# This extends the existing exclusions (e.g. from the global configuration)
-#
-# Below is the complete list of tools for convenience.
-# To make sure you have the latest list of tools, and to view their descriptions,
-# execute `uv run scripts/print_tool_overview.py`.
-#
-# * `activate_project`: Activates a project based on the project name or path.
-# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
-# * `create_text_file`: Creates/overwrites a file in the project directory.
-# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
-# for example by saying that the information retrieved from a memory file is no longer correct
-# or no longer relevant for the project.
-# * `edit_memory`: Replaces content matching a regular expression in a memory.
-# * `execute_shell_command`: Executes a shell command.
-# * `find_file`: Finds files in the given relative paths
-# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
-# * `find_symbol`: Performs a global (or local) search using the language server backend.
-# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
-# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
-# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
-# for clients that do not read the initial instructions when the MCP server is connected.
-# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
-# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
-# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
-# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
-# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
-# * `read_file`: Reads a file within the project directory.
-# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
-# is relevant to the current task. You can infer whether the information
-# is relevant from the memory file name.
-# You should not read the same memory file multiple times in the same conversation.
-# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
-# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
-# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
-# For JB, we use a separate tool.
-# * `replace_content`: Replaces content in a file (optionally using regular expressions).
-# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
-# * `safe_delete_symbol`:
-# * `search_for_pattern`: Performs a search for a pattern in the project.
-# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
-# The memory name should be meaningful.
+# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
excluded_tools: []
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
-# the name by which the project can be referenced within Serena
+# the name by which the project can be referenced within Serena/when chatting with the LLM.
project_name: "toolchain"
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
# This extends the existing inclusions (e.g. from the global configuration).
+# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
included_optional_tools: []
# list of mode names to that are always to be included in the set of active modes
@@ -106,15 +75,19 @@ included_optional_tools: []
# Set this to a list of mode names to always include the respective modes for this project.
base_modes: []
-# list of mode names that are to be activated by default.
-# The full set of modes to be activated is base_modes + default_modes.
-# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
+# list of mode names that are to be activated by default, overriding the setting in the global configuration.
+# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
+# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
+# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
+# for this project.
# This setting can, in turn, be overridden by CLI parameters (--mode).
+# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
default_modes: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
+# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
fixed_tools: []
# time budget (seconds) per tool call for the retrieval of additional symbol information
@@ -149,6 +122,47 @@ ignored_memory_patterns: []
# advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the options.
-# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
-# No documentation on options means no options are available.
+# The settings are considered only if the project is trusted (see global configuration to define trusted projects).
+# See https://oraios.github.io/serena/02-usage/050_configuration.html#language-server-specific-settings
ls_specific_settings: {}
+
+# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
+# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
+# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
+added_modes:
+
+# list of workspace folder paths (LSP backend only).
+# These folders will be used to build up Serena's symbol index.
+# Paths must be within the project root and should thus be relative to the project root.
+# Furthermore, the paths should not be filtered by ignore settings.
+# Default setting: The entire project root folder (".") is considered.
+# In (large) monorepos, this can be used to index only subfolders of the project root, e.g.
+# ls_workspace_folders:
+# - "./subproject1"
+# - "./subproject2"
+ls_workspace_folders:
+- .
+
+# list of additional workspace folder paths for cross-package reference support.
+# Paths can be absolute or relative to the project root.
+# Each folder is registered as an LSP workspace folder, enabling language servers to discover
+# symbols and references across package boundaries, but these folders are not indexed by Serena,
+# i.e. the respective symbols will not be found using Serena's symbol search tools.
+# Example:
+# additional_workspace_folders:
+# - ../sibling-package
+# - ../shared-lib
+ls_additional_workspace_folders: []
+
+# optional shell command to run before the language backend (LSP or JetBrains) is initialised.
+# the command runs in the project root directory and is only executed if the project is trusted
+# (see trusted_project_path_patterns in the global configuration).
+# serena waits for the command to exit: a non-zero exit code is logged as an error but does not
+# abort activation. a per-project timeout (activation_command_timeout, default 180s) is the safety
+# backstop for non-terminating commands; on expiry the process is killed and activation continues.
+# example: activation_command: "npx nx run-many -t build"
+activation_command:
+
+# maximum time in seconds to wait for activation_command to complete before killing it (default 180s).
+# must be a positive number.
+activation_command_timeout: 180.0
diff --git a/AGENTS.md b/AGENTS.md
index f398d6ad6..4aa6cceaa 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -4,7 +4,7 @@ This file provides guidance to AI coding assistants (Claude Code, Cursor, GitHub
## Project Overview
-Apache HugeGraph Toolchain - a multi-module Maven project providing utilities for the HugeGraph graph database. Current version: 1.7.0.
+Apache HugeGraph Toolchain - a multi-module Maven project providing utilities for the HugeGraph graph database. It relies on the Java Client to power loaders, CLI tools, and the modernized Hubble platform.
## Build Commands
@@ -130,22 +130,6 @@ hugegraph-loader, hugegraph-tools, hugegraph-hubble, hugegraph-spark-connector
| tools | `hugegraph-tools/src/main/java` | `org.apache.hugegraph` |
| spark | `hugegraph-spark-connector/src/main/scala` | `org.apache.hugegraph.spark` |
-## Running Applications
-
-### Hubble (Web UI on port 8088)
-```bash
-cd hugegraph-hubble/apache-hugegraph-hubble-*/bin
-./start-hubble.sh # Background
-./start-hubble.sh -f # Foreground
-./stop-hubble.sh # Stop
-```
-
-### Loader
-```bash
-cd hugegraph-loader/apache-hugegraph-loader-*
-./bin/hugegraph-loader.sh [options]
-```
-
## Docker
```bash
diff --git a/README.md b/README.md
index 3d848a2ec..322e6bda5 100644
--- a/README.md
+++ b/README.md
@@ -234,12 +234,12 @@ client := hugegraph.NewClient("http://localhost:8080", "hugegraph")
**Purpose**: Web-based graph management and visualization platform
**Key Features**:
-- Schema management with visual editor
-- Data loading interface
-- Graph visualization and exploration
-- Gremlin query console
-- Multi-graph workspace management
-- User authentication and authorization
+- Multi-graph workspace & connection management
+- Interactive schema management with graphical editor
+- Comprehensive data loading dashboard
+- Dynamic graph visualization with path and topology canvas
+- Built-in Gremlin query console & algorithm explorer
+- Fine-grained user authentication & multi-language localization (i18n)
**Technology Stack**: Spring Boot + React + TypeScript + MobX + Ant Design
diff --git a/hugegraph-client/pom.xml b/hugegraph-client/pom.xml
index 745a37370..09895cc0d 100644
--- a/hugegraph-client/pom.xml
+++ b/hugegraph-client/pom.xml
@@ -61,12 +61,10 @@
org.apache.hugegraph
hg-pd-client
- 1.5.0
org.apache.hugegraph
hg-pd-grpc
- 1.5.0
@@ -78,10 +76,9 @@
${compiler.source}
${compiler.target}
-
- 500
-
+ -Xmaxerrs
+ 500
-Xlint:unchecked
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java b/hugegraph-client/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java
index 018c8dbe6..ff43a01c9 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/api/auth/UserAPI.java
@@ -69,6 +69,12 @@ public List list(int limit) {
return result.readList(this.type(), User.class);
}
+ public User getByName(String name) {
+ Map params = ImmutableMap.of("name", name);
+ RestResult result = this.client.get(this.path(), params);
+ return result.readObject(User.class);
+ }
+
public User update(User user) {
String id = formatEntityId(user.id());
RestResult result = this.client.put(this.path(), id, user);
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/api/graphs/GraphsAPI.java b/hugegraph-client/src/main/java/org/apache/hugegraph/api/graphs/GraphsAPI.java
index 030cf1260..073adaf23 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/api/graphs/GraphsAPI.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/api/graphs/GraphsAPI.java
@@ -18,6 +18,7 @@
package org.apache.hugegraph.api.graphs;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -109,19 +110,20 @@ public List> listProfile(String prefix) {
return profiles;
}
- public Map setDefault(String name) {
+ public Map setDefault(String name) {
String defaultPath = joinPath(this.path(), name, "default");
- RestResult result = this.client.get(defaultPath);
+ RestResult result = this.client.post(defaultPath, Collections.emptyMap());
return result.readObject(Map.class);
}
- public Map unSetDefault(String name) {
- String unDefaultPath = joinPath(this.path(), name, "undefault");
- RestResult result = this.client.get(unDefaultPath);
+ public Map unSetDefault(String name) {
+ String unDefaultPath = joinPath(this.path(), name, "default");
+ RestResult result = this.client.delete(unDefaultPath,
+ Collections.emptyMap());
return result.readObject(Map.class);
}
- public Map getDefault() {
+ public Map getDefault() {
String defaultPath = joinPath(this.path(), "default");
RestResult result = this.client.get(defaultPath);
return result.readObject(Map.class);
@@ -170,6 +172,11 @@ public void drop(String graph, String message) {
ImmutableMap.of(CONFIRM_MESSAGE, message));
}
+ /**
+ * @deprecated Current Server per-graph management accepts only update.
+ * Use {@link #reload()} for a whole-server graph reload.
+ */
+ @Deprecated
public Map reload(String name) {
RestResult result = this.client.put(this.path(), name,
ImmutableMap.of("action", "reload"));
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java b/hugegraph-client/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
index b04408fda..ab611a3f2 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
@@ -131,8 +131,16 @@ public Map deleteDefaultRole(String name, String user,
if (StringUtils.isNotEmpty(graph)) {
params.put("graph", graph);
}
- RestResult result = this.client.delete(path, params);
- return result.readObject(Map.class);
+ this.client.delete(path, params);
+
+ Map result = new HashMap<>();
+ result.put("user", user);
+ result.put("role", role);
+ result.put("graphSpace", name);
+ if (StringUtils.isNotEmpty(graph)) {
+ result.put("graph", graph);
+ }
+ return result;
}
public void delete(String name) {
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/client/RestClient.java b/hugegraph-client/src/main/java/org/apache/hugegraph/client/RestClient.java
index f8c57ec18..ca1dcfad3 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/client/RestClient.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/client/RestClient.java
@@ -183,7 +183,12 @@ public RestResult get(String path, Map params) {
@Override
protected void checkStatus(okhttp3.Response response, int... statuses) {
- boolean match = false;
+ // HugeGraph Server returns 200 with the updated resource for some
+ // successful DELETE operations, while hugegraph-common only passes
+ // 202/204 as the expected DELETE statuses.
+ boolean deleteOk = response.code() == 200 &&
+ "DELETE".equals(response.request().method());
+ boolean match = deleteOk;
for (int status : statuses) {
if (status == response.code()) {
match = true;
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java
index 9f85ec4a4..6d84fdb44 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/AuthManager.java
@@ -132,6 +132,10 @@ public User getUser(Object id) {
return this.userAPI.get(id);
}
+ public User getUserByName(String name) {
+ return this.userAPI.getByName(name);
+ }
+
public User.UserRole getUserRole(Object id) {
return this.userAPI.getUserRole(id);
}
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/GraphsManager.java b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/GraphsManager.java
index aacf261f8..920dfb684 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/GraphsManager.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/GraphsManager.java
@@ -77,15 +77,15 @@ public List> listProfile(String prefix) {
return this.graphsAPI.listProfile(prefix);
}
- public Map setDefault(String name) {
+ public Map setDefault(String name) {
return this.graphsAPI.setDefault(name);
}
- public Map unSetDefault(String name) {
+ public Map unSetDefault(String name) {
return this.graphsAPI.unSetDefault(name);
}
- public Map getDefault() {
+ public Map getDefault() {
return this.graphsAPI.getDefault();
}
@@ -103,6 +103,11 @@ public void dropGraph(String graph, String message) {
this.graphsAPI.drop(graph, message);
}
+ /**
+ * @deprecated Current Server per-graph management accepts only update.
+ * Use {@link #reload()} for a whole-server graph reload.
+ */
+ @Deprecated
public void reload(String graph) {
this.graphsAPI.reload(graph);
}
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
index c681a76a9..6327b0023 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClient.java
@@ -108,7 +108,10 @@ public HugeClient(HugeClientBuilder builder) {
public HugeClient(HugeClient client, String graphSpace, String graph) {
this.borrowedClient = true;
this.client = client.client;
- this.initManagers(this.client, graphSpace, graph);
+ this.graphSpaceName = (graphSpace == null || graphSpace.isEmpty()) ?
+ HugeClientBuilder.DEFAULT_GRAPHSPACE : graphSpace;
+ this.graphName = graph;
+ this.initManagers(this.client, this.graphSpaceName, this.graphName);
}
public static HugeClientBuilder builder(String url, String graphSpace, String graph) {
@@ -119,8 +122,14 @@ public static HugeClientBuilder builder(String url, String graph) {
return new HugeClientBuilder(url, HugeClientBuilder.DEFAULT_GRAPHSPACE, graph);
}
+ public static HugeClientBuilder builder(String url, String graphSpace, String graph,
+ boolean skipRequiredChecks) {
+ return new HugeClientBuilder(url, graphSpace, graph).graphRequired(!skipRequiredChecks);
+ }
+
public HugeClient assignGraph(String graphSpace, String graph) {
- this.graphSpaceName = graphSpace;
+ this.graphSpaceName = (graphSpace == null || graphSpace.isEmpty()) ?
+ HugeClientBuilder.DEFAULT_GRAPHSPACE : graphSpace;
this.graphName = graph;
this.initManagers(this.client, this.graphSpaceName, this.graphName);
return this;
diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClientBuilder.java b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClientBuilder.java
index 2b5d547b3..545566243 100644
--- a/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClientBuilder.java
+++ b/hugegraph-client/src/main/java/org/apache/hugegraph/driver/HugeClientBuilder.java
@@ -54,7 +54,8 @@ public class HugeClientBuilder {
public HugeClientBuilder(String url, String graphSpace, String graph) {
this.url = url;
- this.graphSpace = graphSpace;
+ this.graphSpace = (graphSpace == null || graphSpace.isEmpty()) ?
+ DEFAULT_GRAPHSPACE : graphSpace;
this.graph = graph;
this.username = "";
this.password = "";
@@ -77,8 +78,8 @@ public HugeClient build() {
"Expect a string value as the url parameter argument, but got: %s",
this.url);
E.checkArgument(this.graph != null && !this.graph.isEmpty(),
- "Expect a string value as the graph name parameter argument, but got: %s",
- this.graph);
+ "Expect a string value as the graph name " +
+ "parameter argument, but got: %s", this.graph);
}
return new HugeClient(this);
}
@@ -89,7 +90,8 @@ public HugeClientBuilder graphRequired(boolean graphRequired) {
}
public HugeClientBuilder configGraphSpace(String graphSpace) {
- this.graphSpace = graphSpace;
+ this.graphSpace = (graphSpace == null || graphSpace.isEmpty()) ?
+ DEFAULT_GRAPHSPACE : graphSpace;
return this;
}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/BaseClientTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/BaseClientTest.java
index 2fda81d66..50cedf646 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/BaseClientTest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/BaseClientTest.java
@@ -55,6 +55,7 @@ public class BaseClientTest {
protected static final String GRAPH = "hugegraph";
protected static final String USERNAME = "admin";
protected static final String PASSWORD = "pa";
+ //protected static final String PASSWORD = "admin";
protected static final int TIMEOUT = 10;
private static HugeClient client;
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/api/TaskApiTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/api/TaskApiTest.java
index 27da38211..9e7b07201 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/api/TaskApiTest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/api/TaskApiTest.java
@@ -263,20 +263,14 @@ public void testCancel() {
long taskId = gremlin().executeAsTask(request);
// Wait for task to start
- try {
- Thread.sleep(300);
- } catch (InterruptedException ignored) {
- }
-
+ sleep(300L);
+
// Cancel async task
Task task = taskAPI.cancel(taskId);
Assert.assertTrue(task.cancelling());
// Wait for cancellation to complete
- try {
- Thread.sleep(500);
- } catch (InterruptedException ignored) {
- }
+ sleep(500L);
task = taskAPI.get(taskId);
Assert.assertTrue(task.cancelled());
@@ -304,4 +298,13 @@ public void testTaskAsMap() {
Assert.assertEquals("rebuild_index", taskMap.get(Task.P.TYPE));
Assert.assertEquals("success", taskMap.get(Task.P.STATUS));
}
+
+ private static void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Assert.fail(e.getMessage());
+ }
+ }
}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LoginApiTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LoginApiTest.java
index e1dc90d7d..ae1f17e03 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LoginApiTest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LoginApiTest.java
@@ -26,6 +26,7 @@
import org.apache.hugegraph.testutil.Assert;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
public class LoginApiTest extends AuthApiTest {
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LogoutApiTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LogoutApiTest.java
index 24c777807..de4549d04 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LogoutApiTest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/LogoutApiTest.java
@@ -28,6 +28,7 @@
import org.apache.hugegraph.testutil.Whitebox;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
public class LogoutApiTest extends AuthApiTest {
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/TokenApiTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/TokenApiTest.java
index 9dcec5a30..b0d8407cb 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/TokenApiTest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/api/auth/TokenApiTest.java
@@ -29,6 +29,7 @@
import org.apache.hugegraph.testutil.Whitebox;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
public class TokenApiTest extends AuthApiTest {
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/functional/AuthManagerTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/functional/AuthManagerTest.java
index 243cb1f17..6b4f82860 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/functional/AuthManagerTest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/functional/AuthManagerTest.java
@@ -41,6 +41,7 @@
import org.apache.hugegraph.testutil.Assert;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import com.google.common.collect.ImmutableSet;
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/GraphSpaceAPITest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/GraphSpaceAPITest.java
new file mode 100644
index 000000000..1ffc87a0e
--- /dev/null
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/GraphSpaceAPITest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.unit;
+
+import java.util.Map;
+
+import org.apache.hugegraph.api.space.GraphSpaceAPI;
+import org.apache.hugegraph.client.RestClient;
+import org.apache.hugegraph.rest.RestResult;
+import org.apache.hugegraph.testutil.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+public class GraphSpaceAPITest extends BaseUnitTest {
+
+ private RestClient mockClient;
+ private GraphSpaceAPI graphSpaceAPI;
+
+ @Before
+ public void setup() {
+ this.mockClient = Mockito.mock(RestClient.class);
+ this.graphSpaceAPI = new GraphSpaceAPI(this.mockClient);
+ }
+
+ @Test
+ public void testDeleteDefaultRoleDoesNotReadEmptyResponseBody() {
+ RestResult mockResult = Mockito.mock(RestResult.class);
+ Mockito.when(mockResult.readObject(Map.class))
+ .thenThrow(new AssertionError("204 response has no body"));
+
+ ArgumentCaptor pathCaptor =
+ ArgumentCaptor.forClass(String.class);
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor> paramsCaptor =
+ ArgumentCaptor.forClass(Map.class);
+
+ Mockito.when(this.mockClient.delete(pathCaptor.capture(),
+ paramsCaptor.capture()))
+ .thenReturn(mockResult);
+
+ Map response = this.graphSpaceAPI.deleteDefaultRole(
+ "DEFAULT", "admin", "SPACE", "");
+
+ Assert.assertEquals("graphspaces/DEFAULT/role", pathCaptor.getValue());
+ Assert.assertEquals("admin", paramsCaptor.getValue().get("user"));
+ Assert.assertEquals("SPACE", paramsCaptor.getValue().get("role"));
+ Assert.assertEquals("admin", response.get("user"));
+ Assert.assertEquals("SPACE", response.get("role"));
+ Assert.assertEquals("DEFAULT", response.get("graphSpace"));
+ }
+}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/GraphsAPITest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/GraphsAPITest.java
index d10f84f45..ac99cd9fb 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/GraphsAPITest.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/GraphsAPITest.java
@@ -21,6 +21,7 @@
import org.apache.hugegraph.api.graphs.GraphsAPI;
import org.apache.hugegraph.client.RestClient;
+import org.apache.hugegraph.driver.GraphsManager;
import org.apache.hugegraph.rest.RestHeaders;
import org.apache.hugegraph.rest.RestResult;
import org.apache.hugegraph.testutil.Assert;
@@ -107,4 +108,63 @@ public void testCloneGraphUsesJsonContentTypeAndParams() {
Assert.assertEquals("source-graph",
capturedParams.get("clone_graph_name"));
}
+
+ @Test
+ public void testSetDefaultGraphUsesCanonicalPost() {
+ RestResult mockResult = Mockito.mock(RestResult.class);
+ Mockito.when(mockResult.readObject(Map.class))
+ .thenReturn(null);
+
+ ArgumentCaptor pathCaptor =
+ ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor bodyCaptor =
+ ArgumentCaptor.forClass(Object.class);
+
+ Mockito.when(this.mockClient.post(pathCaptor.capture(),
+ bodyCaptor.capture()))
+ .thenReturn(mockResult);
+
+ this.graphsAPI.setDefault("test-graph");
+
+ Assert.assertEquals("graphspaces/DEFAULT/graphs/test-graph/default",
+ pathCaptor.getValue());
+ Assert.assertTrue(((Map, ?>) bodyCaptor.getValue()).isEmpty());
+ Mockito.verify(mockResult).readObject(Map.class);
+ }
+
+ @Test
+ public void testUnsetDefaultGraphUsesCanonicalDelete() {
+ RestResult mockResult = Mockito.mock(RestResult.class);
+ Mockito.when(mockResult.readObject(Map.class))
+ .thenReturn(null);
+
+ ArgumentCaptor pathCaptor =
+ ArgumentCaptor.forClass(String.class);
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor> paramsCaptor =
+ ArgumentCaptor.forClass(Map.class);
+
+ Mockito.when(this.mockClient.delete(pathCaptor.capture(),
+ paramsCaptor.capture()))
+ .thenReturn(mockResult);
+
+ this.graphsAPI.unSetDefault("test-graph");
+
+ Assert.assertEquals("graphspaces/DEFAULT/graphs/test-graph/default",
+ pathCaptor.getValue());
+ Assert.assertTrue(paramsCaptor.getValue().isEmpty());
+ Mockito.verify(mockResult).readObject(Map.class);
+ }
+
+ @Test
+ public void testPerGraphReloadIsDeprecated() throws NoSuchMethodException {
+ Assert.assertNotNull(GraphsAPI.class.getMethod("reload", String.class)
+ .getAnnotation(Deprecated.class));
+ Assert.assertNotNull(GraphsManager.class.getMethod("reload", String.class)
+ .getAnnotation(Deprecated.class));
+ Assert.assertNull(GraphsAPI.class.getMethod("reload")
+ .getAnnotation(Deprecated.class));
+ Assert.assertNull(GraphsManager.class.getMethod("reload")
+ .getAnnotation(Deprecated.class));
+ }
}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/RestClientStatusTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/RestClientStatusTest.java
new file mode 100644
index 000000000..a3f8fa807
--- /dev/null
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/RestClientStatusTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.unit;
+
+import org.apache.hugegraph.client.RestClient;
+import org.apache.hugegraph.exception.ServerException;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import okhttp3.MediaType;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.ResponseBody;
+
+public class RestClientStatusTest extends BaseUnitTest {
+
+ @Test
+ public void testOkIsAcceptedForDeleteSuccessStatuses() {
+ okhttp3.Response response = Mockito.mock(okhttp3.Response.class);
+ Request request = new Request.Builder().url("http://127.0.0.1:1")
+ .delete()
+ .build();
+ Mockito.when(response.code()).thenReturn(200);
+ Mockito.when(response.request()).thenReturn(request);
+
+ new TestRestClient().check(response, 202, 204);
+ }
+
+ @Test(expected = ServerException.class)
+ public void testOkIsRejectedForNonDeleteExpectedStatus() {
+ Request request = new Request.Builder().url("http://127.0.0.1:1")
+ .post(okhttp3.RequestBody.create(null,
+ new byte[0]))
+ .build();
+ okhttp3.Response response = new okhttp3.Response.Builder()
+ .request(request)
+ .protocol(Protocol.HTTP_1_1)
+ .code(200)
+ .message("OK")
+ .body(ResponseBody.create(
+ MediaType.parse("application/json"), "{}"))
+ .build();
+
+ new TestRestClient().check(response, 201, 202);
+ }
+
+ private static class TestRestClient extends RestClient {
+
+ TestRestClient() {
+ super("http://127.0.0.1:1", "", "", 1);
+ }
+
+ void check(okhttp3.Response response, int... statuses) {
+ this.checkStatus(response, statuses);
+ }
+ }
+}
diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
index 5ddb23a50..45670a92f 100644
--- a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
+++ b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/UnitTestSuite.java
@@ -25,9 +25,11 @@
VertexSerializerTest.class,
PathSerializerTest.class,
RestResultTest.class,
+ RestClientStatusTest.class,
BatchElementRequestTest.class,
PropertyKeyTest.class,
IndexLabelTest.class,
+ GraphSpaceAPITest.class,
GraphsAPITest.class,
CommonUtilTest.class,
IdUtilTest.class,
diff --git a/hugegraph-dist/scripts/dependency/known-dependencies.txt b/hugegraph-dist/scripts/dependency/known-dependencies.txt
index 0b38c41e2..ba38b94c2 100644
--- a/hugegraph-dist/scripts/dependency/known-dependencies.txt
+++ b/hugegraph-dist/scripts/dependency/known-dependencies.txt
@@ -1,6 +1,5 @@
-HdrHistogram-2.1.9.jar
HikariCP-3.2.0.jar
-LatencyUtils-2.0.3.jar
+HikariCP-java7-2.4.12.jar
ST4-4.0.4.jar
accessors-smart-1.2.jar
accessors-smart-2.4.2.jar
@@ -9,10 +8,14 @@ aircompressor-0.21.jar
animal-sniffer-annotations-1.19.jar
annotations-13.0.jar
annotations-17.0.0.jar
+annotations-24.0.1.jar
annotations-4.1.1.4.jar
+ansj_seg-5.1.6.jar
ant-1.9.1.jar
ant-launcher-1.9.1.jar
antlr-runtime-3.5.2.jar
+aopalliance-1.0.jar
+aopalliance-repackaged-2.5.0-b42.jar
apache-curator-2.12.0.pom
arrow-format-0.8.0.jar
arrow-format-2.0.0.jar
@@ -20,19 +23,30 @@ arrow-memory-0.8.0.jar
arrow-memory-core-2.0.0.jar
arrow-vector-0.8.0.jar
arrow-vector-2.0.0.jar
-asm-5.0.4.jar
+asm-6.0.jar
asm-8.0.1.jar
audience-annotations-0.5.0.jar
+auto-service-annotations-1.0.jar
+automaton-1.11-8.jar
+avatica-1.11.0.jar
avro-1.10.2.jar
avro-1.7.7.jar
+bolt-1.6.4.jar
caffeine-2.6.2.jar
+caffeine-2.8.0.jar
+calcite-core-1.16.0.jar
+calcite-druid-1.16.0.jar
+calcite-linq4j-1.16.0.jar
+checker-qual-2.10.0.jar
checker-qual-3.5.0.jar
classmate-1.4.0.jar
commons-beanutils-1.9.4.jar
commons-cli-1.3.1.jar
commons-codec-1.15.jar
commons-collections-3.2.2.jar
+commons-collections4-4.2.jar
commons-collections4-4.4.jar
+commons-compiler-2.7.6.jar
commons-compress-1.21.jar
commons-configuration-1.10.jar
commons-configuration2-2.1.1.jar
@@ -40,6 +54,8 @@ commons-configuration2-2.8.0.jar
commons-crypto-1.0.0.jar
commons-crypto-1.1.0.jar
commons-daemon-1.0.13.jar
+commons-dbcp-1.4.jar
+commons-fileupload-1.4.jar
commons-fileupload-1.5.jar
commons-io-2.8.0.jar
commons-lang-2.6.jar
@@ -47,6 +63,7 @@ commons-lang3-3.9.jar
commons-logging-1.1.3.jar
commons-math3-3.4.1.jar
commons-net-3.9.0.jar
+commons-pool-1.6.jar
commons-text-1.10.0.jar
curator-client-4.2.0.jar
curator-framework-2.12.0.jar
@@ -55,23 +72,56 @@ curator-recipes-2.13.0.jar
curator-recipes-4.2.0.jar
datanucleus-core-4.1.17.jar
disruptor-3.3.6.jar
+disruptor-3.3.7.jar
dnsjava-2.1.7.jar
dropwizard-metrics-hadoop-metrics2-reporter-0.1.2.jar
+eclipse-collections-11.1.0.jar
+eclipse-collections-api-11.1.0.jar
+ehcache-3.6.3.jar
+elasticsearch-java-7.16.1.jar
+elasticsearch-rest-client-6.4.3.jar
+error_prone_annotations-2.3.3.jar
error_prone_annotations-2.3.4.jar
+esri-geometry-api-2.0.0.jar
+exp4j-0.4.8.jar
+failsafe-2.4.1.jar
failureaccess-1.0.1.jar
+fastutil-8.5.9.jar
findbugs-annotations-1.3.9-1.jar
flatbuffers-1.2.0-3f79e055.jar
flatbuffers-java-1.9.0.jar
+fst-2.50.jar
+fury-core-0.9.0.jar
+generex-1.0.2.jar
+geronimo-jcache_1.0_spec-1.0-alpha-1.jar
+gremlin-core-3.5.1.jar
+gremlin-driver-3.5.1.jar
+gremlin-groovy-3.5.1.jar
+gremlin-shaded-3.5.1.jar
+gremlin-test-3.5.1.jar
+groovy-2.5.14-indy.jar
groovy-all-2.4.21.jar
+groovy-cli-picocli-2.5.8.jar
+groovy-console-2.5.8.jar
+groovy-groovysh-2.5.14-indy.jar
+groovy-json-2.5.14-indy.jar
+groovy-jsr223-2.5.14-indy.jar
+groovy-swing-2.5.8.jar
+groovy-templates-2.5.8.jar
+groovy-xml-2.5.8.jar
grpc-api-1.39.0.jar
grpc-context-1.39.0.jar
grpc-core-1.39.0.jar
+grpc-grpclb-1.39.0.jar
+grpc-netty-1.39.0.jar
grpc-netty-shaded-1.39.0.jar
grpc-protobuf-1.39.0.jar
grpc-protobuf-lite-1.39.0.jar
grpc-stub-1.39.0.jar
gson-2.8.9.jar
guava-30.0-jre.jar
+guice-4.0.jar
+guice-servlet-4.0.jar
h2-1.4.199.jar
hadoop-annotations-3.3.1.jar
hadoop-auth-3.3.1.jar
@@ -84,9 +134,18 @@ hadoop-mapreduce-client-core-3.3.1.jar
hadoop-mapreduce-client-jobclient-3.3.1.jar
hadoop-shaded-guava-1.1.1.jar
hadoop-shaded-protobuf_3_7-1.1.1.jar
+hadoop-yarn-api-3.1.0.jar
hadoop-yarn-api-3.3.1.jar
hadoop-yarn-client-3.3.1.jar
+hadoop-yarn-common-3.1.0.jar
hadoop-yarn-common-3.3.1.jar
+hadoop-yarn-registry-3.1.0.jar
+hadoop-yarn-server-applicationhistoryservice-3.1.0.jar
+hadoop-yarn-server-common-3.1.0.jar
+hadoop-yarn-server-resourcemanager-3.1.0.jar
+hadoop-yarn-server-web-proxy-3.1.0.jar
+hamcrest-2.2.jar
+hanlp-portable-1.8.3.jar
hbase-client-2.2.3.jar
hbase-common-2.2.3.jar
hbase-hadoop-compat-2.2.3.jar
@@ -105,12 +164,14 @@ hbase-shaded-miscellaneous-2.2.1.jar
hbase-shaded-netty-2.2.1.jar
hbase-shaded-protobuf-2.2.1.jar
hbase-zookeeper-2.2.3.jar
-hg-pd-client-1.5.0.jar
-hg-pd-common-1.5.0.jar
-hg-pd-grpc-1.5.0.jar
+hessian-3.3.6.jar
+hg-pd-client-1.7.0.jar
+hg-pd-common-1.7.0.jar
+hg-pd-grpc-1.7.0.jar
hibernate-validator-6.0.17.Final.jar
hive-classification-3.1.3.jar
hive-common-3.1.3.jar
+hive-exec-3.1.3-core.jar
hive-exec-3.1.3.jar
hive-llap-client-3.1.3.jar
hive-llap-common-3.1.3.jar
@@ -125,13 +186,20 @@ hive-storage-api-2.7.0.jar
hive-storage-api-2.7.2.jar
hive-upgrade-acid-3.1.3.jar
hive-vector-code-gen-3.1.3.jar
+hk2-api-2.5.0-b42.jar
+hk2-locator-2.5.0-b42.jar
+hk2-utils-2.5.0-b42.jar
+hppc-0.7.1.jar
hppc-0.7.2.jar
htrace-core4-4.1.0-incubating.jar
htrace-core4-4.2.0-incubating.jar
+httpasyncclient-4.1.4.jar
httpclient-4.5.13.jar
httpclient-4.5.9.jar
httpcore-4.4.12.jar
httpcore-4.4.13.jar
+httpcore-nio-4.4.12.jar
+ikanalyzer-2012_u6.jar
ivy-2.4.0.jar
ivy-2.5.0.jar
j2objc-annotations-1.3.jar
@@ -139,24 +207,35 @@ jackson-annotations-2.12.3.jar
jackson-core-2.12.3.jar
jackson-core-asl-1.9.13.jar
jackson-databind-2.12.3.jar
+jackson-dataformat-yaml-2.12.3.jar
jackson-datatype-jdk8-2.12.3.jar
jackson-datatype-jsr310-2.12.3.jar
+jackson-jaxrs-1.9.2.jar
jackson-jaxrs-base-2.12.3.jar
jackson-jaxrs-json-provider-2.12.3.jar
jackson-mapper-asl-1.9.13.jar
jackson-module-jaxb-annotations-2.12.3.jar
jackson-module-parameter-names-2.12.3.jar
+jackson-xc-1.9.2.jar
jakarta.activation-2.0.1.jar
jakarta.activation-api-1.2.1.jar
+jakarta.json-2.0.1.jar
jakarta.xml.bind-api-2.3.2.jar
jamon-runtime-2.4.1.jar
+janino-3.0.15.jar
+java-util-1.9.0.jar
+javacsv-2.0.jar
+javapoet-1.8.0.jar
+javassist-3.22.0-CR2.jar
javassist-3.24.0-GA.jar
javassist-3.25.0-GA.jar
javassist-3.28.0-GA.jar
+javatuples-1.2.jar
javax.activation-api-1.2.0.jar
javax.annotation-api-1.3.2.jar
javax.el-3.0.0.jar
javax.el-3.0.1-b12.jar
+javax.inject-1.jar
javax.inject-2.5.0-b32.jar
javax.inject-2.5.0-b42.jar
javax.json-1.0.jar
@@ -164,18 +243,38 @@ javax.servlet-api-3.1.0.jar
javax.servlet-api-4.0.1.jar
javax.servlet.jsp-2.3.2.jar
javax.servlet.jsp-api-2.3.1.jar
+javax.ws.rs-api-2.1.jar
javolution-5.5.1.jar
jaxb-api-2.2.11.jar
jaxb-api-2.3.1.jar
jaxb-core-3.0.2.jar
jaxb-impl-3.0.2.jar
+jbcrypt-0.4.jar
jboss-logging-3.3.3.Final.jar
+jcabi-log-0.14.jar
+jcabi-manifests-1.1.jar
jcip-annotations-1.0-1.jar
+jcl-over-slf4j-1.7.28.jar
jcodings-1.0.18.jar
jcommander-1.72.jar
jcommander-1.78.jar
+jcseg-core-2.6.2.jar
+jctools-core-2.1.1.jar
+jersey-apache-connector-2.27.jar
+jersey-client-1.19.jar
+jersey-client-2.27.jar
+jersey-common-2.27.jar
jersey-container-servlet-core-2.25.1.jar
jersey-container-servlet-core-2.27.jar
+jersey-core-1.19.jar
+jersey-guice-1.19.jar
+jersey-hk2-2.27.jar
+jersey-json-1.19.jar
+jersey-server-1.19.jar
+jersey-servlet-1.19.jar
+jetcd-common-0.5.9.jar
+jetcd-core-0.5.9.jar
+jettison-1.1.jar
jetty-client-9.4.33.v20201020.jar
jetty-client-9.4.40.v20210413.jar
jetty-http-9.4.19.v20190610.jar
@@ -198,14 +297,22 @@ jetty-webapp-9.4.19.v20190610.jar
jetty-webapp-9.4.40.v20210413.jar
jetty-xml-9.4.19.v20190610.jar
jetty-xml-9.4.40.v20210413.jar
+jieba-analysis-1.0.2.jar
+jjwt-api-0.11.5.jar
+jjwt-impl-0.11.5.jar
+jjwt-jackson-0.11.5.jar
jline-2.12.jar
+jline-2.14.6.jar
jline-3.9.0.jar
+jna-4.5.2.jar
joda-time-2.10.3.jar
joda-time-2.10.8.jar
joni-2.1.11.jar
jpam-1.1.jar
+jraft-core-1.3.11.jar
jsch-0.1.55.jar
json-1.8.jar
+json-io-2.5.1.jar
json-smart-2.3.jar
json-smart-2.4.2.jar
jsp-api-2.1.jar
@@ -213,28 +320,61 @@ jsqlparser-3.1.jar
jsr305-3.0.0.jar
jsr305-3.0.1.jar
jsr305-3.0.2.jar
+jsr311-api-1.1.1.jar
jul-to-slf4j-1.7.28.jar
kerb-admin-1.0.1.jar
+kerb-admin-2.0.0.jar
kerb-client-1.0.1.jar
+kerb-client-2.0.0.jar
kerb-common-1.0.1.jar
+kerb-common-2.0.0.jar
kerb-core-1.0.1.jar
kerb-crypto-1.0.1.jar
+kerb-crypto-2.0.0.jar
kerb-identity-1.0.1.jar
+kerb-identity-2.0.0.jar
kerb-server-1.0.1.jar
+kerb-server-2.0.0.jar
kerb-simplekdc-1.0.1.jar
+kerb-simplekdc-2.0.0.jar
kerb-util-1.0.1.jar
+kerb-util-2.0.0.jar
kerby-asn1-1.0.1.jar
kerby-config-1.0.1.jar
+kerby-config-2.0.0.jar
kerby-pkix-1.0.1.jar
kerby-util-1.0.1.jar
kerby-xdr-1.0.1.jar
+kerby-xdr-2.0.0.jar
kotlin-stdlib-1.6.20.jar
kotlin-stdlib-common-1.5.31.jar
kotlin-stdlib-jdk7-1.2.71.jar
kotlin-stdlib-jdk7-1.6.10.jar
kotlin-stdlib-jdk8-1.2.71.jar
kotlin-stdlib-jdk8-1.6.10.jar
+kubernetes-client-5.6.0.jar
+kubernetes-model-admissionregistration-5.6.0.jar
+kubernetes-model-apiextensions-5.6.0.jar
+kubernetes-model-apps-5.6.0.jar
+kubernetes-model-autoscaling-5.6.0.jar
+kubernetes-model-batch-5.6.0.jar
+kubernetes-model-certificates-5.6.0.jar
+kubernetes-model-common-5.6.0.jar
+kubernetes-model-coordination-5.6.0.jar
+kubernetes-model-core-5.6.0.jar
+kubernetes-model-discovery-5.6.0.jar
+kubernetes-model-events-5.6.0.jar
+kubernetes-model-extensions-5.6.0.jar
+kubernetes-model-flowcontrol-5.6.0.jar
+kubernetes-model-metrics-5.6.0.jar
+kubernetes-model-networking-5.6.0.jar
+kubernetes-model-node-5.6.0.jar
+kubernetes-model-policy-5.6.0.jar
+kubernetes-model-rbac-5.6.0.jar
+kubernetes-model-scheduling-5.6.0.jar
+kubernetes-model-storageclass-5.6.0.jar
leveldbjni-all-1.8.jar
+libfb303-0.9.3.jar
libthrift-0.9.3.jar
lightning-csv-8.2.1.jar
listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
@@ -243,15 +383,25 @@ log4j-1.2-api-2.17.1.jar
log4j-1.2.17.jar
log4j-api-2.18.0.jar
log4j-core-2.18.0.jar
-log4j-jul-2.11.2.jar
log4j-slf4j-impl-2.18.0.jar
+log4j-to-slf4j-2.11.2.jar
+log4j-web-2.11.2.jar
+logback-classic-1.2.3.jar
+logback-core-1.2.3.jar
logging-interceptor-4.10.0.jar
-lombok-1.18.8.jar
+lombok-1.18.32.jar
+lucene-analyzers-common-8.11.2.jar
+lucene-analyzers-smartcn-8.11.2.jar
+lucene-core-8.11.2.jar
+lucene-queries-4.7.2.jar
+lucene-queryparser-4.7.2.jar
+lucene-sandbox-4.7.2.jar
lz4-java-1.4.0.jar
+memory-0.9.0.jar
metrics-core-4.2.0.jar
metrics-json-4.2.0.jar
metrics-jvm-4.2.0.jar
-micrometer-core-1.1.6.jar
+mmseg4j-core-1.10.0.jar
mssql-jdbc-6.4.0.jre8.jar
mssql-jdbc-7.2.0.jre8.jar
mybatis-3.5.2.jar
@@ -267,29 +417,44 @@ netty-3.10.6.Final.jar
netty-all-4.1.65.Final.jar
netty-buffer-4.1.65.Final.jar
netty-codec-4.1.65.Final.jar
+netty-codec-http-4.1.65.Final.jar
+netty-codec-http2-4.1.65.Final.jar
+netty-codec-socks-4.1.65.Final.jar
netty-common-4.1.65.Final.jar
netty-handler-4.1.65.Final.jar
+netty-handler-proxy-4.1.65.Final.jar
netty-resolver-4.1.65.Final.jar
netty-transport-4.1.65.Final.jar
netty-transport-native-epoll-4.1.65.Final.jar
netty-transport-native-unix-common-4.1.65.Final.jar
nimbus-jose-jwt-9.8.1.jar
+nlp-lang-1.7.7.jar
+ohc-core-0.7.4.jar
okhttp-4.10.0.jar
+okhttp-4.12.0.jar
+okio-3.6.0.jar
okio-jvm-3.0.0.jar
+okio-jvm-3.6.0.jar
opencsv-2.3.jar
orc-core-1.5.8.jar
orc-core-1.6.14.jar
orc-shims-1.5.8.jar
orc-shims-1.6.14.jar
+osgi-resource-locator-1.0.1.jar
ow2-asm-6.2.jar
paranamer-2.3.jar
parboiled-core-1.1.8.jar
+parboiled-core-1.2.0.jar
+parquet-hadoop-bundle-1.10.0.jar
perfmark-api-0.23.0.jar
+picocli-4.0.1.jar
postgresql-42.2.6.jar
postgresql-42.4.1.jar
proto-google-common-protos-2.0.1.jar
protobuf-java-2.5.0.jar
protobuf-java-3.17.2.jar
+protobuf-java-3.17.3.jar
+protobuf-java-util-3.17.2.jar
re2j-1.1.jar
sfm-converter-8.2.1.jar
sfm-csv-8.2.1.jar
@@ -297,25 +462,25 @@ sfm-map-8.2.1.jar
sfm-reflect-8.2.1.jar
sfm-tuples-8.2.1.jar
sfm-util-8.2.1.jar
+sketches-core-0.9.0.jar
slf4j-api-1.7.25.jar
slf4j-api-1.7.28.jar
slf4j-api-1.7.30.jar
slf4j-log4j12-1.7.30.jar
+smiley-http-proxy-servlet-1.12.1.jar
snakeyaml-1.23.jar
snappy-java-1.1.8.2.jar
snappy-java-1.1.8.4.jar
+sofa-common-tools-1.0.12.jar
spring-aop-5.1.9.RELEASE.jar
spring-beans-5.1.9.RELEASE.jar
spring-boot-2.1.8.RELEASE.jar
-spring-boot-actuator-2.1.8.RELEASE.jar
-spring-boot-actuator-autoconfigure-2.1.8.RELEASE.jar
spring-boot-autoconfigure-2.1.8.RELEASE.jar
spring-boot-starter-2.1.8.RELEASE.jar
-spring-boot-starter-actuator-2.1.8.RELEASE.jar
spring-boot-starter-cache-2.1.8.RELEASE.jar
spring-boot-starter-jdbc-2.1.8.RELEASE.jar
spring-boot-starter-json-2.1.8.RELEASE.jar
-spring-boot-starter-log4j2-2.1.8.RELEASE.jar
+spring-boot-starter-logging-2.1.8.RELEASE.jar
spring-boot-starter-tomcat-2.1.8.RELEASE.jar
spring-boot-starter-web-2.1.8.RELEASE.jar
spring-context-5.1.9.RELEASE.jar
@@ -330,7 +495,9 @@ spring-webmvc-5.1.9.RELEASE.jar
stax-api-1.0.1.jar
stax2-api-4.2.1.jar
threeten-extra-1.5.0.jar
+tinkergraph-gremlin-3.5.1.jar
token-provider-1.0.1.jar
+token-provider-2.0.0.jar
tomcat-embed-core-9.0.24.jar
tomcat-embed-el-9.0.24.jar
tomcat-embed-websocket-9.0.24.jar
@@ -341,5 +508,6 @@ websocket-common-9.4.40.v20210413.jar
woodstox-core-5.0.3.jar
woodstox-core-5.3.0.jar
xz-1.8.jar
+zjsonpatch-0.3.0.jar
zookeeper-3.6.2.jar
zookeeper-jute-3.6.2.jar
diff --git a/hugegraph-hubble/Dockerfile b/hugegraph-hubble/Dockerfile
index 377b6fb41..4591715d7 100644
--- a/hugegraph-hubble/Dockerfile
+++ b/hugegraph-hubble/Dockerfile
@@ -40,5 +40,7 @@ FROM eclipse-temurin:11-jre-jammy
COPY --from=build /pkg/hugegraph-hubble/apache-hugegraph-hubble-*/ /hubble
WORKDIR /hubble/
+# SECURITY: This is a plain HTTP port. Do not publish it to an untrusted network;
+# production/public deployments must put Hubble behind HTTPS and block bypasses.
EXPOSE 8088
-ENTRYPOINT ["./bin/start-hubble.sh", "-f true"]
+ENTRYPOINT ["./bin/start-hubble.sh", "-f"]
diff --git a/hugegraph-hubble/README.md b/hugegraph-hubble/README.md
index 6b03d5aff..d9196a168 100644
--- a/hugegraph-hubble/README.md
+++ b/hugegraph-hubble/README.md
@@ -7,6 +7,49 @@
hugegraph-hubble is a graph management and analysis platform that provides features:
graph data load, schema management, graph relationship analysis, and graphical display.
+## Functional Modules Overview
+
+```mermaid
+graph TD
+ Hubble["HugeGraph-Hubble Platform"]
+
+ Hubble --> Conn["1. Workspace Management (Multi-Graph Connections)"]
+ Hubble --> Schema["2. Visual Schema Designer (Vertex, Edge & Index Types)"]
+ Hubble --> Load["3. Guided Data Importer (Source Mapping & Task Monitor)"]
+ Hubble --> Analyze["4. Graph Analysis & Visualization (Gremlin Console & Visual Exploration)"]
+ Hubble --> System["5. System Administration (Async Tasks & Access Control)"]
+```
+
+
+ASCII diagram (for terminals/editors)
+
+```
+ ┌──────────────────────────────────────────┐
+ │ HugeGraph-Hubble Platform │
+ └────────────────────┬─────────────────────┘
+ │
+ ┌───────────────────────────┼───────────────────────────┐
+ ▼ ▼ ▼
+┌──────────────┐ ┌──────────────┐ ┌──────────────┐
+│ Workspace │ │Visual Schema │ │ Guided Data │
+│ Management │ │ Designer │ │ Importer │
+│ - Connect │ │ - Vertex │ │ - Sources │
+│ - Switch │ │ - Edge │ │ - Mapping │
+│ - Card View │ │ - Index │ │ - Monitor │
+└──────────────┘ └──────────────┘ └──────────────┘
+ │ │
+ └───────────────────────────┬───────────────────────────┘
+ │
+ ▼
+ ┌──────────────────────────┐
+ │Graph Analysis & Explorer │
+ │ - Gremlin Console │
+ │ - Algorithm Execution │
+ │ - Topology Exploration │
+ └──────────────────────────┘
+```
+
+
## Features
- Graph connection management, supporting to easily switch graph to operate
@@ -16,13 +59,13 @@ graph data load, schema management, graph relationship analysis, and graphical d
## Quick Start
-There are three ways to get HugeGraph-Loader:
+There are three ways to get HugeGraph-Hubble:
- Download the Toolchain binary package
- Source code compilation
- Use Docker image (Convenient for Test/Dev)
-And you can find more details in the [doc](https://hugegraph.apache.org/docs/quickstart/hugegraph-loader/#2-get-hugegraph-loader)
+And you can find more details in the [doc](https://hugegraph.apache.org/docs/quickstart/hugegraph-hubble/#2-deploy)
### 1. Download the Toolchain binary package
@@ -44,16 +87,16 @@ Then use a web browser to access `ip:8088` and you can see the `Hubble` page. Yo
### 2. Clone source code then compile and install
-> Note: Compiling Hubble requires the user’s local environment to have Node.js V16.x and yarn installed.
+> Note: Compiling Hubble requires the user's local environment to have Node.js V18.20.8 and yarn installed.
```bash
apt install curl build-essential
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
source ~/.bashrc
-nvm install 16
+nvm install 18.20.8
```
-Then, verify that the installed Node.js version is 16.x (please note that higher Node version may cause conflicts).
+Then, verify that the installed Node.js version is 18.20.8.
```bash
node -v
diff --git a/hugegraph-hubble/hubble-be/pom.xml b/hugegraph-hubble/hubble-be/pom.xml
index 6e81c1691..ff96b6031 100644
--- a/hugegraph-hubble/hubble-be/pom.xml
+++ b/hugegraph-hubble/hubble-be/pom.xml
@@ -1,5 +1,6 @@
+
+ co.elastic.clients
+ elasticsearch-java
+ ${es.version}
+
org.mybatis.spring.boot
mybatis-spring-boot-starter
- ${mybatis.starter.version}
+ 2.1.0
com.baomidou
mybatis-plus-boot-starter
- ${mybatis.plus.starter.version}
+ 3.3.0
com.h2database
@@ -90,12 +96,16 @@
com.github.ben-manes.caffeine
caffeine
+ 2.8.0
+
+
+ net.sourceforge.javacsv
+ javacsv
+ 2.0
-
org.apache.hugegraph
- hugegraph-loader
- ${revision}
+ hugegraph-common
org.apache.logging.log4j
@@ -117,29 +127,97 @@
javassist
org.javassist
+
+
+
+ org.apache.hugegraph
+ hg-pd-client
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+
+
+
+
+ org.apache.hugegraph
+ hugegraph-loader
+ ${revision}
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+
org.eclipse.jetty
jetty-runner
- com.oracle
- ojdbc8
+ hive-exec
+ org.apache.hive
- slf4j-log4j12
- org.slf4j
+ com.google.protobuf
+ protobuf-java
+
+
+
+
+ org.apache.hugegraph
+ hugegraph-client
+ ${revision}
+
+
+ org.apache.logging.log4j
+ log4j-api
- log4j
- log4j
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
- org.jetbrains.kotlin
- kotlin-stdlib
+ org.eclipse.jetty
+ jetty-runner
- com.squareup.okhttp
- okhttp
+ hive-exec
+ org.apache.hive
+
+
+ org.glassfish.jersey.containers
+ jersey-container-servlet-core
+
+
+ org.glassfish.jersey.core
+ jersey-common
+
+
+ org.glassfish.jersey.core
+ jersey-client
+
+
+ org.glassfish.jersey.core
+ jersey-server
@@ -147,20 +225,73 @@
commons-fileupload
commons-fileupload
+ 1.4
-
-
- com.squareup.okhttp3
- okhttp
+ org.parboiled
+ parboiled-core
+ 1.2.0
- org.jetbrains.kotlin
- kotlin-stdlib
+ org.apache.hugegraph
+ hugegraph-core
+ compile
+
+
+
+ org.apache.hugegraph
+ hg-store-common
+
+
+
+
+ org.apache.commons
+ commons-collections4
+ 4.2
+ compile
+
+
+ commons-configuration
+ commons-configuration
+ 1.10
+
+
+ com.google.protobuf
+ protobuf-java
+ 3.17.3
+
+
+
+ org.apache.hive
+ hive-exec
+ 3.1.3
+ core
+ compile
+
+
+ com.google.guava
+ guava
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+
+
+
- org.jetbrains.kotlin
- kotlin-stdlib-common
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
@@ -178,6 +309,70 @@
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+ info
+ true
+
+
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+ ${java.version}
+ ${java.version}
+
+ -Xmaxerrs
+ 500
+ -Xlint:unchecked
+ -Xlint:deprecation
+ --add-modules=java.base
+
+
+
+
+ maven-clean-plugin
+ 3.0.0
+
+
+
+ ${top.level.dir}
+
+ *.tar
+ *.tar.gz
+ *.zip
+ ${final.name}/**
+
+ false
+
+
+ ${final.name}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.20
+
org.apache.maven.plugins
maven-jar-plugin
@@ -231,6 +426,7 @@
+
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/HugeGraphHubble.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/HugeGraphHubble.java
index e0c208279..dad629fdf 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/HugeGraphHubble.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/HugeGraphHubble.java
@@ -21,6 +21,8 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.util.Ex;
import org.mybatis.spring.annotation.MapperScan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@@ -30,13 +32,20 @@
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.time.ZoneOffset;
+import java.util.TimeZone;
+
@SpringBootApplication
@EnableScheduling
@MapperScan("org.apache.hugegraph.mapper")
public class HugeGraphHubble extends SpringBootServletInitializer {
+ private static final Logger LOG = LoggerFactory.getLogger(HugeGraphHubble.class);
+
public static void main(String[] args) {
+ LOG.info("user.dir ==> {}", System.getProperty("user.dir"));
initEnv();
+ TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.of("+8")));
SpringApplication.run(HugeGraphHubble.class, args);
}
@@ -53,7 +62,8 @@ public static void initEnv() {
}
@Override
- protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
+ protected SpringApplicationBuilder configure(
+ SpringApplicationBuilder builder) {
return builder.sources(this.getClass());
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/AppName.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/AppName.java
new file mode 100644
index 000000000..ba0ac39af
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/AppName.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.common;
+
+import org.apache.hugegraph.type.define.SerialEnum;
+
+public enum AppName implements SerialEnum {
+ GREMLIN(1, "GREMLIN"),
+
+ VERTEX_UPDATE(2, "VERTEX_UPDATE"),
+
+ EDGE_UPDATE(3, "EDGE_UPDATE")
+ ;
+
+ private final byte code;
+ private final String name;
+
+ static {
+ SerialEnum.register(AppName.class);
+ }
+
+ AppName(int code, String name) {
+ assert code < 256;
+ this.code = (byte) code;
+ this.name = name;
+ }
+
+ @Override
+ public byte code() {
+ return this.code;
+ }
+
+ public String string() {
+ return this.name;
+ }
+
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/AppType.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/AppType.java
new file mode 100644
index 000000000..046805201
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/AppType.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.common;
+
+import org.apache.hugegraph.type.define.SerialEnum;
+
+public enum AppType implements SerialEnum {
+ // 通用功能
+ GENERAL(1, "GENERAL"),
+
+ // 定制功能
+ CUSTOMIZED(2, "CUSTOMIZED"),
+
+ ;
+
+ private final byte code;
+ private final String name;
+
+ static {
+ SerialEnum.register(AppType.class);
+ }
+
+ AppType(int code, String name) {
+ assert code < 256;
+ this.code = (byte) code;
+ this.name = name;
+ }
+
+ @Override
+ public byte code() {
+ return this.code;
+ }
+
+ public String string() {
+ return this.name;
+ }
+
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
index 11afe6a46..d649513f9 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Constant.java
@@ -18,13 +18,13 @@
package org.apache.hugegraph.common;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.collect.ImmutableSet;
import java.nio.charset.Charset;
import java.util.Set;
import java.util.regex.Pattern;
-import com.google.common.collect.ImmutableSet;
+import static java.nio.charset.StandardCharsets.UTF_8;
public final class Constant {
@@ -39,15 +39,13 @@ public final class Constant {
public static final String CONFIG_FILE = "hugegraph-hubble.properties";
public static final String CONTROLLER_PACKAGE =
- "org.apache.hugegraph.controller";
+ "org.apache.hugegraph.controller";
public static final String COOKIE_USER = "user";
public static final String API_V1_1 = "/api/v1.1/";
public static final String API_V1_2 = "/api/v1.2/";
- public static final String API_VERSION = API_V1_2;
-
- public static final String EDITION_COMMUNITY = "community";
- public static final String EDITION_COMMERCIAL = "commercial";
+ public static final String API_V1_3 = "/api/v1.3/";
+ public static final String API_VERSION = API_V1_3;
public static final String MAPPING_FILE_NAME = "mapping.json";
@@ -58,6 +56,12 @@ public final class Constant {
public static final int STATUS_ILLEGAL_GREMLIN = 460;
public static final int STATUS_INTERNAL_ERROR = 500;
+ public static final String TOKEN_KEY = "auth_token";
+ public static final String USERNAME_KEY = "username";
+ public static final String CREDENTIAL_PASSWORD_KEY = "auth_password";
+ public static final String CREDENTIAL_EXPIRES_AT_KEY = "auth_password_expire_at";
+ public static final long CREDENTIAL_TTL_MILLIS = 10 * 60 * 1000L;
+
public static final int NO_LIMIT = -1;
public static final Pattern COMMON_NAME_PATTERN = Pattern.compile(
@@ -73,4 +77,6 @@ public final class Constant {
);
public static final String[] LIKE_WILDCARDS = {"%", "_", "^", "[", "]"};
+
+ public static final String BUILT_IN = "neizhianli";
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Mergeable.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Mergeable.java
index 3fd638bc3..1d9e3e477 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Mergeable.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/Mergeable.java
@@ -19,5 +19,4 @@
package org.apache.hugegraph.common;
public interface Mergeable {
-
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/OptionType.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/OptionType.java
new file mode 100644
index 000000000..a1d1e294a
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/common/OptionType.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.common;
+
+import org.apache.hugegraph.type.define.SerialEnum;
+
+public enum OptionType implements SerialEnum {
+
+
+ DELETE(1, "DELETE"),
+
+ ADD(2, "ADD"),
+
+ UPDATE(3, "UPDATE"),
+
+ ;
+
+ private final byte code;
+ private final String name;
+
+ static {
+ SerialEnum.register(OptionType.class);
+ }
+
+ OptionType(int code, String name) {
+ assert code < 256;
+ this.code = (byte) code;
+ this.name = name;
+ }
+
+ @Override
+ public byte code() {
+ return this.code;
+ }
+
+ public String string() {
+ return this.name;
+ }
+
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/CacheConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/CacheConfig.java
index d41b1e55b..8b367d307 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/CacheConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/CacheConfig.java
@@ -43,7 +43,10 @@ public class CacheConfig {
public enum Caches {
// No used
- GREMLIN_QUERY;
+ GREMLIN_QUERY,
+
+ // es query cached
+ ES_QUERY(DEFAULT_MAXSIZE, 60);
private int maxSize = DEFAULT_MAXSIZE;
private int ttl = DEFAULT_TTL;
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/DatabaseSchemaMigrator.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/DatabaseSchemaMigrator.java
new file mode 100644
index 000000000..13e717bf6
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/DatabaseSchemaMigrator.java
@@ -0,0 +1,155 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.config;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Locale;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Component
+public class DatabaseSchemaMigrator implements ApplicationRunner {
+
+ private static final int FILE_MAPPING_PATH_LENGTH = 2048;
+ private static final String FILE_MAPPING_TABLE = "file_mapping";
+ private static final String FILE_MAPPING_PATH_COLUMN = "path";
+ private static final String EXECUTE_HISTORY_TABLE = "execute_history";
+ private static final String FAILURE_REASON_COLUMN = "failure_reason";
+
+ @Autowired
+ private DataSource dataSource;
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ try (Connection conn = this.dataSource.getConnection()) {
+ this.migrate(conn);
+ }
+ }
+
+ public void migrate(Connection conn) throws SQLException {
+ int currentLength = this.columnSize(conn, FILE_MAPPING_TABLE,
+ FILE_MAPPING_PATH_COLUMN);
+ if (currentLength > 0 && currentLength < FILE_MAPPING_PATH_LENGTH) {
+ this.migrateFileMappingPath(conn, currentLength);
+ }
+
+ this.migrateExecuteHistoryFailureReason(conn);
+ }
+
+ private void migrateFileMappingPath(Connection conn, int currentLength)
+ throws SQLException {
+ String sql = this.alterFileMappingPathSql(
+ conn.getMetaData().getDatabaseProductName());
+ if (sql == null) {
+ log.warn("Skip file_mapping.path migration for unsupported " +
+ "database product {}",
+ conn.getMetaData().getDatabaseProductName());
+ return;
+ }
+
+ try (Statement statement = conn.createStatement()) {
+ statement.execute(sql);
+ }
+ log.info("Migrated file_mapping.path from {} to VARCHAR({})",
+ currentLength, FILE_MAPPING_PATH_LENGTH);
+ }
+
+ private void migrateExecuteHistoryFailureReason(Connection conn)
+ throws SQLException {
+ if (!this.tableExists(conn, EXECUTE_HISTORY_TABLE) ||
+ this.columnSize(conn, EXECUTE_HISTORY_TABLE,
+ FAILURE_REASON_COLUMN) > 0) {
+ return;
+ }
+
+ String product = conn.getMetaData().getDatabaseProductName()
+ .toLowerCase(Locale.ROOT);
+ if (!product.contains("h2") && !product.contains("mysql") &&
+ !product.contains("mariadb")) {
+ log.warn("Skip execute_history.failure_reason migration for " +
+ "unsupported database product {}",
+ conn.getMetaData().getDatabaseProductName());
+ return;
+ }
+
+ try (Statement statement = conn.createStatement()) {
+ statement.execute("ALTER TABLE `execute_history` ADD COLUMN " +
+ "`failure_reason` VARCHAR(64) DEFAULT NULL");
+ }
+ log.info("Added execute_history.failure_reason VARCHAR(64)");
+ }
+
+ private boolean tableExists(Connection conn, String table)
+ throws SQLException {
+ DatabaseMetaData metaData = conn.getMetaData();
+ String[] tables = {table, table.toUpperCase(Locale.ROOT)};
+ for (String tableName : tables) {
+ try (ResultSet rs = metaData.getTables(null, null, tableName,
+ new String[]{"TABLE"})) {
+ if (rs.next()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private int columnSize(Connection conn, String table, String column)
+ throws SQLException {
+ DatabaseMetaData metaData = conn.getMetaData();
+ String[] tables = {table, table.toUpperCase(Locale.ROOT)};
+ String[] columns = {column, column.toUpperCase(Locale.ROOT)};
+ for (String tableName : tables) {
+ for (String columnName : columns) {
+ try (ResultSet rs = metaData.getColumns(null, null, tableName,
+ columnName)) {
+ if (rs.next()) {
+ return rs.getInt("COLUMN_SIZE");
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ private String alterFileMappingPathSql(String productName) {
+ String product = productName.toLowerCase(Locale.ROOT);
+ if (product.contains("h2")) {
+ return "ALTER TABLE `file_mapping` ALTER COLUMN `path` " +
+ "VARCHAR(2048) NOT NULL";
+ }
+ if (product.contains("mysql") || product.contains("mariadb")) {
+ return "ALTER TABLE `file_mapping` MODIFY COLUMN `path` " +
+ "VARCHAR(2048) NOT NULL";
+ }
+ return null;
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/GlobalCorsConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/GlobalCorsConfig.java
index 565f94aad..8706c8de0 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/GlobalCorsConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/GlobalCorsConfig.java
@@ -30,11 +30,11 @@ public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
- config.addAllowedOrigin("*");
- config.setAllowCredentials(true);
+ // Same-origin is the secure default. Deployments that need cross-origin
+ // access must add an explicit trusted-origin allowlist.
+ config.setAllowCredentials(false);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
- config.addExposedHeader("/api/**");
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
index f5e2ae136..8caad2787 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/HubbleConfig.java
@@ -18,23 +18,20 @@
package org.apache.hugegraph.config;
-import java.io.File;
-
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.exception.ExternalException;
import org.apache.hugegraph.options.HubbleOptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import java.io.File;
+import java.net.URL;
+
@Configuration
public class HubbleConfig {
- private static final Logger LOG = LoggerFactory.getLogger(HubbleConfig.class);
-
@Autowired
private ApplicationArguments arguments;
@@ -43,7 +40,7 @@ public HugeConfig hugeConfig() {
String[] args = this.arguments.getSourceArgs();
if (args.length > 1) {
throw new ExternalException(
- "HugeGraphHubble accept up to one param as config file");
+ "HugeGraphHubble accept up to one param as config file");
} else if (args.length == 0) {
args = new String[]{Constant.CONFIG_FILE};
}
@@ -51,15 +48,13 @@ public HugeConfig hugeConfig() {
// Register hubble config options
OptionSpace.register(Constant.MODULE_NAME, HubbleOptions.instance());
String conf = args[0];
- try {
- String path = HubbleConfig.class.getClassLoader()
- .getResource(conf).getPath();
+ URL resource = HubbleConfig.class.getClassLoader().getResource(conf);
+ if (resource != null) {
+ String path = resource.getPath();
File file = new File(path);
if (file.exists() && file.isFile()) {
conf = path;
}
- } catch (Exception ignored) {
- LOG.error("hugeConfig exception");
}
return new HugeConfig(conf);
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/IngestionProxyServlet.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/IngestionProxyServlet.java
new file mode 100644
index 000000000..228c3588f
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/IngestionProxyServlet.java
@@ -0,0 +1,87 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.hugegraph.common.Constant;
+import org.mitre.dsmiley.httpproxy.ProxyServlet;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.nio.charset.StandardCharsets;
+import java.io.IOException;
+
+public class IngestionProxyServlet extends ProxyServlet {
+
+ @Override
+ protected String rewriteQueryStringFromRequest(
+ HttpServletRequest servletRequest, String queryString) {
+ String username = this.sessionString(servletRequest,
+ Constant.USERNAME_KEY);
+
+ String requestQueryString = servletRequest.getQueryString();
+
+ if (StringUtils.isEmpty(requestQueryString)) {
+ requestQueryString = String.format("user=%s", username);
+ } else {
+ requestQueryString += String.format("&user=%s", username);
+ }
+
+ return requestQueryString;
+ }
+
+ @Override
+ protected HttpResponse doExecute(HttpServletRequest servletRequest,
+ HttpServletResponse servletResponse,
+ HttpRequest proxyRequest) throws IOException {
+ if (!this.authenticated(servletRequest)) {
+ // check user login
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
+ Constant.STATUS_UNAUTHORIZED,
+ "Unauthorized");
+
+ response.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
+ response.setEntity(new StringEntity("{\"status\": 401}",
+ StandardCharsets.UTF_8));
+
+ return response;
+ }
+
+ return super.doExecute(servletRequest, servletResponse, proxyRequest);
+ }
+
+ private boolean authenticated(HttpServletRequest servletRequest) {
+ return StringUtils.isNotBlank(this.sessionString(servletRequest,
+ Constant.USERNAME_KEY)) &&
+ StringUtils.isNotBlank(this.sessionString(servletRequest,
+ Constant.TOKEN_KEY));
+ }
+
+ private String sessionString(HttpServletRequest servletRequest,
+ String key) {
+ Object value = servletRequest.getSession().getAttribute(key);
+ return value instanceof String ? (String) value : null;
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/JacksonConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/JacksonConfig.java
index 4fc2ab300..c8ea6603e 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/JacksonConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/JacksonConfig.java
@@ -18,21 +18,21 @@
package org.apache.hugegraph.config;
-import java.io.IOException;
-import java.util.Map;
-
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.hugegraph.common.Response;
+import org.apache.hugegraph.entity.graph.VertexQueryEntity;
import org.apache.hugegraph.structure.graph.Edge;
import org.apache.hugegraph.structure.graph.Vertex;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.module.SimpleModule;
+import java.io.IOException;
+import java.util.Map;
@JsonComponent
public class JacksonConfig {
@@ -75,6 +75,11 @@ public void serialize(Vertex vertex, JsonGenerator generator,
writeIdField("id", vertex.id(), generator);
generator.writeStringField("label", vertex.label());
writePropertiesField(vertex.properties(), generator, provider);
+ if (vertex instanceof VertexQueryEntity) {
+ writeStatisticsField(
+ ((VertexQueryEntity) vertex).getStatistics(),
+ generator, provider);
+ }
generator.writeEndObject();
}
}
@@ -98,7 +103,7 @@ public void serialize(Edge edge, JsonGenerator generator,
private static void writeIdField(String fieldName, Object id,
JsonGenerator generator)
- throws IOException {
+ throws IOException {
// Serialize id to string
generator.writeStringField(fieldName, id.toString());
}
@@ -106,7 +111,7 @@ private static void writeIdField(String fieldName, Object id,
private static void writePropertiesField(Map properties,
JsonGenerator generator,
SerializerProvider provider)
- throws IOException {
+ throws IOException {
// Start write properties
generator.writeFieldName("properties");
generator.writeStartObject();
@@ -130,4 +135,32 @@ private static void writePropertiesField(Map properties,
// End wirte properties
generator.writeEndObject();
}
+
+ private static void writeStatisticsField(Map statistics,
+ JsonGenerator generator,
+ SerializerProvider provider)
+ throws IOException {
+ // Start write statistics
+ generator.writeFieldName("statistics");
+ generator.writeStartObject();
+ for (Map.Entry entry : statistics.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ generator.writeFieldName(key);
+ if (value != null) {
+ if (value instanceof Long) {
+ // To avoid javascript loss of long precision
+ generator.writeString(String.valueOf(value));
+ } else {
+ JsonSerializer serializer;
+ serializer = provider.findValueSerializer(value.getClass());
+ serializer.serialize(value, generator, provider);
+ }
+ } else {
+ generator.writeNull();
+ }
+ }
+ // End wirte statistics
+ generator.writeEndObject();
+ }
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/MetaConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/MetaConfig.java
new file mode 100644
index 000000000..2bf5370a6
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/MetaConfig.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.config;
+
+import org.apache.hugegraph.driver.factory.PDHugeClientFactory;
+import org.apache.hugegraph.options.HubbleOptions;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@Configuration
+public class MetaConfig {
+
+ @Autowired
+ private HugeConfig config;
+
+ @Bean("pdEnabled")
+ public Boolean pdEnabled() {
+ return this.config.get(HubbleOptions.PD_ENABLED);
+ }
+
+ @Bean("cluster")
+ public String getCluster() {
+ return this.config.get(HubbleOptions.PD_CLUSTER);
+ }
+
+ @Bean
+ PDHugeClientFactory pdHugeClientFactory() {
+ boolean pdEnabled = this.config.get(HubbleOptions.PD_ENABLED);
+ if (!pdEnabled) {
+ log.info("PD mode is disabled, skip creating PDHugeClientFactory");
+ return null;
+ }
+
+ String pdAddrs = this.config.get(HubbleOptions.PD_PEERS);
+ String routeType = this.config.get(HubbleOptions.ROUTE_TYPE);
+ return new PDHugeClientFactory(pdAddrs, routeType);
+ }
+
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/ProxyServletConfiguration.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/ProxyServletConfiguration.java
new file mode 100644
index 000000000..e3f5a7162
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/ProxyServletConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.config;
+
+import org.apache.hugegraph.options.HubbleOptions;
+
+import org.mitre.dsmiley.httpproxy.ProxyServlet;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ProxyServletConfiguration {
+ @Autowired
+ private HugeConfig config;
+
+ /**
+ * Only register the proxy servlet when proxy.servlet_url is configured.
+ * When not configured, this bean won't be created, preventing the proxy
+ * servlet from intercepting all requests on root path.
+ */
+ @Bean
+ @ConditionalOnProperty(name = "proxy.servlet_url", matchIfMissing = false)
+ public ServletRegistrationBean
+ servletRegistrationBean() {
+ String servletUrl = config.get(HubbleOptions.PROXY_SERVLET_URL);
+ String targetUrl = config.get(HubbleOptions.PROXY_TARGET_URL);
+
+ // Additional safety check
+ if (StringUtils.isBlank(servletUrl) ||
+ StringUtils.isBlank(targetUrl) ||
+ "WhatURLAtHere".equals(targetUrl)) {
+ return null;
+ }
+
+ ServletRegistrationBean servletRegistrationBean =
+ new ServletRegistrationBean<>(new IngestionProxyServlet(),
+ servletUrl);
+ servletRegistrationBean.addInitParameter("targetUri", targetUrl);
+ servletRegistrationBean.addInitParameter(ProxyServlet.P_LOG, "true");
+ return servletRegistrationBean;
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/TomcatServletConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/TomcatServletConfig.java
index 9d0de09b1..f19e86695 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/TomcatServletConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/TomcatServletConfig.java
@@ -18,9 +18,6 @@
package org.apache.hugegraph.config;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
import org.apache.hugegraph.exception.ExternalException;
import org.apache.hugegraph.options.HubbleOptions;
import org.apache.tomcat.util.http.LegacyCookieProcessor;
@@ -29,10 +26,15 @@
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
-// TODO: remove this class if we don't need it anymore
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Reference http://www.zizhixiaoshe.com/article/invalidcookie.html
+ */
@Component
-public class TomcatServletConfig implements
- WebServerFactoryCustomizer {
+public class TomcatServletConfig
+ implements WebServerFactoryCustomizer {
@Autowired
private HugeConfig config;
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java
index 25ebcf6f5..ec0fb77e8 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/config/WebMvcConfig.java
@@ -18,30 +18,65 @@
package org.apache.hugegraph.config;
-import org.apache.hugegraph.handler.CustomInterceptor;
+import org.apache.hugegraph.handler.LoginInterceptor;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
-import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.resource.PathResourceResolver;
+
+import org.apache.hugegraph.handler.CustomInterceptor;
+
+import java.io.IOException;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
- public void addViewControllers(ViewControllerRegistry registry) {
- registry.addViewController("/{spring:[\\w-]+}")
- .setViewName("forward:/");
- registry.addViewController("/**/{spring:[\\w-]+}")
- .setViewName("forward:/");
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/api/**");
+ registry.addResourceHandler("/**")
+ .addResourceLocations("classpath:/ui/")
+ .resourceChain(true)
+ .addResolver(new PathResourceResolver() {
+ @Override
+ protected Resource getResource(String resourcePath,
+ Resource location)
+ throws IOException {
+ Resource requested = location.createRelative(
+ resourcePath);
+ // If the requested resource exists and is readable,
+ // serve it; otherwise fall back to index.html
+ // for SPA routing
+ if (requested.exists() && requested.isReadable()) {
+ return requested;
+ }
+ return new ClassPathResource("/ui/index.html");
+ }
+ });
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.customInterceptor())
- .addPathPatterns("/**");
+ .addPathPatterns("/api/**");
+ registry.addInterceptor(this.loginInterceptor())
+ .addPathPatterns("/api/**")
+ .excludePathPatterns("/api/**/auth/login")
+ .excludePathPatterns("/logout")
+ .excludePathPatterns("/api/**/auth/logout");
}
+ @Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor();
}
+
+ @Bean
+ public LoginInterceptor loginInterceptor() {
+ return new LoginInterceptor();
+ }
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/AboutController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/AboutController.java
index 044268b8d..57418ed02 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/AboutController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/AboutController.java
@@ -18,9 +18,6 @@
package org.apache.hugegraph.controller;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.handler.MessageSourceHandler;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,9 +25,12 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import java.util.LinkedHashMap;
+import java.util.Map;
+//import org.apache.hugegraph.license.LicenseVerifier; // TODO C Remove Licence
+
@RestController
@RequestMapping("about")
-// TODO: delete the class or keep it?
public class AboutController extends BaseController {
@Autowired
@@ -38,11 +38,20 @@ public class AboutController extends BaseController {
@GetMapping
public Map about() {
+ //LicenseVerifier verifier = LicenseVerifier.instance();// TODO C Remove Licence
Map about = new LinkedHashMap<>();
about.put("name", Constant.SERVER_NAME);
- about.put("version", "1.5.0");
- about.put("allowed_datasize",
- this.messageHandler.getMessage("license.datasize.no-limit"));
+ about.put("version", "3.0.0");
+ //about.put("edition", verifier.edition());// TODO C Remove Licence
+ //about.put("allowed_graphs", verifier.allowedGraphs());
+ //long datasize = verifier.allowedDataSize();
+ //if (datasize == -1) {
+ // about.put("allowed_datasize",
+ // this.messageHandler.getMessage("license.datasize.no-limit"));
+ //} else {
+ // about.put("allowed_datasize",
+ // FileUtils.byteCountToDisplaySize(datasize * Bytes.MB));
+ //}
return about;
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
index bdcd4cd2a..a5767e92c 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/BaseController.java
@@ -19,42 +19,280 @@
package org.apache.hugegraph.controller;
import java.util.List;
+import java.util.function.Function;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.driver.factory.PDHugeClientFactory;
+import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.service.auth.UserService;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.hugegraph.config.HugeConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.exception.ParameterizedException;
+import org.apache.hugegraph.service.HugeClientPoolService;
import org.apache.hugegraph.common.Identifiable;
import org.apache.hugegraph.common.Mergeable;
import org.apache.hugegraph.util.EntityUtil;
import org.apache.hugegraph.util.Ex;
-import org.springframework.util.StringUtils;
+@Component
public abstract class BaseController {
+ @Autowired
+ protected String cluster;
+ @Autowired
+ protected HugeClientPoolService hugeClientPoolService;
+ @Autowired(required = false)
+ protected PDHugeClientFactory pdHugeClientFactory;
+
+ @Autowired
+ private HugeConfig config;
+
+ @Autowired
+ UserService userService;
+
public static final String ORDER_ASC = "asc";
public static final String ORDER_DESC = "desc";
public void checkIdSameAsBody(Object id, Identifiable newEntity) {
Ex.check(newEntity.getId() != null, () -> id.equals(newEntity.getId()),
- "common.param.path-id-should-same-as-body",
- id, newEntity.getId());
+ "common.param.path-id-should-same-as-body",
+ id, newEntity.getId());
}
public void checkParamsNotEmpty(String name, String value,
- boolean creating) {
+ boolean creating) {
if (creating) {
Ex.check(!StringUtils.isEmpty(value),
- "common.param.cannot-be-null-or-empty", name);
+ "common.param.cannot-be-null-or-empty", name);
} else {
// The default null and user-passed null indicate no update
Ex.check(value == null || !value.isEmpty(),
- "common.param.cannot-be-empty", name);
+ "common.param.cannot-be-empty", name);
}
}
public void checkParamsNotEmpty(String name, List> values) {
Ex.check(values != null && !values.isEmpty(),
- "common.param.cannot-be-null-or-empty", name);
+ "common.param.cannot-be-null-or-empty", name);
}
public T mergeEntity(T oldEntity, T newEntity) {
return EntityUtil.merge(oldEntity, newEntity);
}
+
+ protected void setSession(String key, Object value) {
+ HttpServletRequest request = getRequest();
+ request.getSession().setAttribute(key, value);
+ }
+
+ protected Object getSession(String key) {
+ HttpServletRequest request = getRequest();
+ return request.getSession().getAttribute(key);
+
+ }
+
+ protected String getUser() {
+ return (String) getSession(Constant.USERNAME_KEY);
+ }
+
+ protected void setUser(String username) {
+ setSession(Constant.USERNAME_KEY, username);
+ }
+
+ protected void setCredentialPassword(String password) {
+ // TODO: Stop retaining the plaintext login password after Vermeer migrates to
+ // token/service credentials and Loader/Ingest token-only paths are verified.
+ setSession(Constant.CREDENTIAL_PASSWORD_KEY, password);
+ setSession(Constant.CREDENTIAL_EXPIRES_AT_KEY,
+ System.currentTimeMillis() + Constant.CREDENTIAL_TTL_MILLIS);
+ }
+
+ protected String getCredentialPassword() {
+ Long expiresAt = (Long) getSession(Constant.CREDENTIAL_EXPIRES_AT_KEY);
+ if (expiresAt == null || expiresAt <= System.currentTimeMillis()) {
+ delSession(Constant.CREDENTIAL_PASSWORD_KEY);
+ delSession(Constant.CREDENTIAL_EXPIRES_AT_KEY);
+ return null;
+ }
+ return (String) getSession(Constant.CREDENTIAL_PASSWORD_KEY);
+ }
+
+ protected void delSession(String key) {
+ HttpServletRequest request = getRequest();
+ request.getSession().removeAttribute(key);
+ }
+
+ protected HttpServletRequest getRequest() {
+ return ((ServletRequestAttributes) RequestContextHolder
+ .getRequestAttributes()).getRequest();
+ }
+
+ protected String getToken() {
+ return (String) getSession(Constant.TOKEN_KEY);
+ }
+
+ protected void setToken(String token) {
+ this.setSession(Constant.TOKEN_KEY, token);
+ }
+
+ protected void delToken() {
+ this.delSession(Constant.TOKEN_KEY);
+ }
+
+ protected void clearAuthSession() {
+ this.delSession(Constant.TOKEN_KEY);
+ this.delSession(Constant.USERNAME_KEY);
+ this.delSession(Constant.CREDENTIAL_PASSWORD_KEY);
+ this.delSession(Constant.CREDENTIAL_EXPIRES_AT_KEY);
+ }
+
+ protected HugeClient authClient(String graphSpace, String graph) {
+ HttpServletRequest request = getRequest();
+ if (request.getAttribute("hugeClient") != null) {
+ HugeClient client = (HugeClient) request.getAttribute("hugeClient");
+ client.assignGraph(graphSpace, graph);
+ return client;
+ }
+ HugeClient client = this.hugeClientPoolService.createAuthClient(
+ graphSpace, graph, this.getToken());
+ request.setAttribute("hugeClient", client);
+ return client;
+ }
+
+ protected HugeClient authGremlinClient(String graphSpace, String graph) {
+ String username = this.getUser();
+ String password = this.getCredentialPassword();
+ if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
+ return this.authClient(graphSpace, graph);
+ }
+
+ HttpServletRequest request = getRequest();
+ if (request.getAttribute("hugeClient") != null) {
+ HugeClient client = (HugeClient) request.getAttribute("hugeClient");
+ client.close();
+ }
+ HugeClient client = this.createBasicClient(graphSpace, graph, username,
+ password);
+ request.setAttribute("hugeClient", client);
+ return client;
+ }
+
+ protected HugeClient createBasicClient(String graphSpace, String graph,
+ String username, String password) {
+ return this.hugeClientPoolService.createBasicClient(graphSpace, graph,
+ username, password);
+ }
+
+ protected HugeClient unauthClient() {
+ HttpServletRequest request = getRequest();
+ if (request.getAttribute("hugeClient") != null) {
+ HugeClient client = (HugeClient) request.getAttribute("hugeClient");
+ return client;
+ }
+ HugeClient client = this.hugeClientPoolService.createUnauthClient();
+ request.setAttribute("hugeClient", client);
+ return client;
+ }
+
+ protected HugeClient tempTokenClient() {
+ HttpServletRequest request = getRequest();
+ if (request.getAttribute("hugeClient") != null) {
+ HugeClient client = (HugeClient) request.getAttribute("hugeClient");
+ client.setAuthContext("Basic " + this.getToken());
+ return client;
+ }
+ HugeClient client = this.hugeClientPoolService.createTempTokenClient(this.getToken());
+ request.setAttribute("hugeClient", client);
+ return client;
+ }
+
+ protected void clearRequestHugeClient() {
+ HttpServletRequest request = getRequest();
+ if (request.getAttribute("hugeClient") != null) {
+ HugeClient client = (HugeClient) request.getAttribute("hugeClient");
+ client.close();
+ }
+ request.setAttribute("hugeClient", null);
+ }
+
+ protected HugeClient createAuthClient(String graphSpace, String graph) {
+ return this.hugeClientPoolService.create(null, graphSpace, graph,
+ this.getToken());
+ }
+
+ protected HugeClient createUnauthClient(String graphSpace, String graph) {
+ return this.hugeClientPoolService.create(null, graphSpace, graph, null);
+ }
+
+ public T doAuthRequest(Function func) {
+ try (HugeClient client = createAuthClient(null, null)) {
+ return func.apply(client);
+ } catch (Throwable t) {
+ throw t;
+ }
+ }
+
+ public T doUnauthRequest(Function func) {
+ try (HugeClient client = createUnauthClient(null, null)) {
+ return func.apply(client);
+ } catch (Throwable t) {
+ throw t;
+ }
+ }
+
+ protected HugeClient defaultClient(String graphSpace, String graph) {
+ boolean pdEnabled = config.get(HubbleOptions.PD_ENABLED);
+ if (!pdEnabled) {
+ String url = config.get(HubbleOptions.SERVER_URL);
+ return hugeClientPoolService.create(url, graphSpace, graph,
+ this.getToken());
+ }
+
+ // PD mode: get URL from service discovery
+ List urls = pdHugeClientFactory.getURLs(this.cluster,
+ PDHugeClientFactory.DEFAULT_GRAPHSPACE,
+ PDHugeClientFactory.DEFAULT_SERVICE);
+
+ if (CollectionUtils.isEmpty(urls)) {
+ throw new ParameterizedException("No url in service(%s/%s)",
+ PDHugeClientFactory.DEFAULT_GRAPHSPACE,
+ PDHugeClientFactory.DEFAULT_SERVICE);
+ }
+
+ String url = urls.get((int) (Math.random() * urls.size()));
+
+ HugeClient client = hugeClientPoolService.create(url, graphSpace, graph,
+ this.getToken());
+
+ return client;
+ }
+
+ public String getUrl() {
+ boolean pdEnabled = config.get(HubbleOptions.PD_ENABLED);
+ if (!pdEnabled) {
+ return config.get(HubbleOptions.SERVER_URL);
+ }
+
+ List urls = pdHugeClientFactory.getURLs(this.cluster,
+ PDHugeClientFactory.DEFAULT_GRAPHSPACE,
+ PDHugeClientFactory.DEFAULT_SERVICE);
+
+ if (CollectionUtils.isEmpty(urls)) {
+ throw new ParameterizedException("No url in service(%s/%s)",
+ PDHugeClientFactory.DEFAULT_GRAPHSPACE,
+ PDHugeClientFactory.DEFAULT_SERVICE);
+ }
+
+ String url = urls.get((int) (Math.random() * urls.size()));
+ return url;
+ }
+
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
new file mode 100644
index 000000000..5b3795d8a
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ConfigController.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.options.HubbleOptions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "config")
+public class ConfigController {
+
+ @Autowired
+ private HugeConfig config;
+
+ @GetMapping
+ public Map getConfig() {
+ Map result = new HashMap<>();
+ boolean pdEnabled = config.get(HubbleOptions.PD_ENABLED);
+ result.put("pd_enabled", pdEnabled);
+ if (!pdEnabled) {
+ result.put("server_url", config.get(HubbleOptions.SERVER_URL));
+ }
+ return result;
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/GraphConnectionController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/GraphConnectionController.java
deleted file mode 100644
index 4dfa07a52..000000000
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/GraphConnectionController.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with this
- * work for additional information regarding copyright ownership. The ASF
- * licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.hugegraph.controller;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.List;
-import java.util.regex.Pattern;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.hugegraph.common.Constant;
-import org.apache.hugegraph.common.Response;
-import org.apache.hugegraph.config.HugeConfig;
-import org.apache.hugegraph.driver.HugeClient;
-import org.apache.hugegraph.entity.GraphConnection;
-import org.apache.hugegraph.exception.ExternalException;
-import org.apache.hugegraph.options.HubbleOptions;
-import org.apache.hugegraph.service.GraphConnectionService;
-import org.apache.hugegraph.service.HugeClientPoolService;
-import org.apache.hugegraph.service.SettingSSLService;
-import org.apache.hugegraph.util.Ex;
-import org.apache.hugegraph.util.HubbleUtil;
-import org.apache.hugegraph.util.HugeClientUtil;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-import com.baomidou.mybatisplus.core.metadata.IPage;
-
-import lombok.extern.log4j.Log4j2;
-
-@Log4j2
-@RestController
-@RequestMapping(Constant.API_VERSION + "graph-connections")
-public class GraphConnectionController extends BaseController {
-
- private static final Pattern GRAPH_PATTERN = Pattern.compile(
- "^[A-Za-z][A-Za-z0-9_]{0,47}$"
- );
-
- @Autowired
- private HugeConfig config;
- @Autowired
- private GraphConnectionService connService;
- @Autowired
- private HugeClientPoolService poolService;
- @Autowired
- private SettingSSLService sslService;
-
- @GetMapping
- public Response list(@RequestParam(name = "content", required = false)
- String content,
- @RequestParam(name = "page_no", required = false,
- defaultValue = "1")
- int pageNo,
- @RequestParam(name = "page_size", required = false,
- defaultValue = "10")
- int pageSize) {
- IPage conns = this.connService.list(content, pageNo,
- pageSize);
- return Response.builder().status(Constant.STATUS_OK).data(conns)
- .build();
- }
-
- @GetMapping("{id}")
- public GraphConnection get(@PathVariable("id") int id) {
- GraphConnection entity = this.connService.get(id);
- if (entity == null) {
- throw new ExternalException("graph-connection.not-exist.id", id);
- }
- if (!this.poolService.containsKey(id)) {
- this.sslService.configSSL(this.config, entity);
- HugeClient client = HugeClientUtil.tryConnect(entity);
- this.poolService.put(entity, client);
- }
- return entity;
- }
-
- @PostMapping
- public GraphConnection create(@RequestBody GraphConnection newEntity) {
- this.checkParamsValid(newEntity, true);
- this.checkAddressSecurity(newEntity);
- // Make sure the new entity doesn't conflict with exists
- this.checkEntityUnique(newEntity, true);
-
- newEntity.setTimeout(this.config.get(HubbleOptions.CLIENT_REQUEST_TIMEOUT));
- // Do connect test, failure will throw an exception
- this.sslService.configSSL(this.config, newEntity);
- HugeClient client = HugeClientUtil.tryConnect(newEntity);
- newEntity.setCreateTime(HubbleUtil.nowDate());
-
- this.connService.save(newEntity);
- this.poolService.put(newEntity, client);
- return newEntity;
- }
-
- @PutMapping("{id}")
- public GraphConnection update(@PathVariable("id") int id,
- @RequestBody GraphConnection newEntity) {
- this.checkIdSameAsBody(id, newEntity);
- this.checkParamsValid(newEntity, false);
- this.checkAddressSecurity(newEntity);
-
- // Check exist connection with this id
- GraphConnection oldEntity = this.connService.get(id);
- if (oldEntity == null) {
- throw new ExternalException("graph-connection.not-exist.id", id);
- }
- GraphConnection entity = this.mergeEntity(oldEntity, newEntity);
- // Make sure the updated connection doesn't conflict with exists
- this.checkEntityUnique(entity, false);
- this.sslService.configSSL(this.config, entity);
- HugeClient client = HugeClientUtil.tryConnect(entity);
-
- this.connService.update(entity);
- this.poolService.put(entity, client);
- return entity;
- }
-
- @DeleteMapping("{id}")
- public GraphConnection delete(@PathVariable("id") int id) {
- GraphConnection oldEntity = this.connService.get(id);
- if (oldEntity == null) {
- throw new ExternalException("graph-connection.not-exist.id", id);
- }
- this.connService.remove(id);
- this.poolService.remove(oldEntity);
- return oldEntity;
- }
-
- private void checkParamsValid(GraphConnection newEntity, boolean creating) {
- Ex.check(creating, () -> newEntity.getId() == null,
- "common.param.must-be-null", "id");
-
- String name = newEntity.getName();
- this.checkParamsNotEmpty("name", name, creating);
- Ex.check(name != null, () -> Constant.COMMON_NAME_PATTERN.matcher(name)
- .matches(),
- "graph-connection.name.unmatch-regex");
-
- String graph = newEntity.getGraph();
- this.checkParamsNotEmpty("graph", graph, creating);
- Ex.check(graph != null, () -> GRAPH_PATTERN.matcher(graph).matches(),
- "graph-connection.graph.unmatch-regex");
-
- String host = newEntity.getHost();
- this.checkParamsNotEmpty("host", host, creating);
- Ex.check(host != null, () -> HubbleUtil.HOST_PATTERN.matcher(host)
- .matches(),
- "graph-connection.host.unmatch-regex");
-
- Integer port = newEntity.getPort();
- Ex.check(creating, () -> port != null,
- "common.param.cannot-be-null", "port");
- Ex.check(port != null, () -> 0 < port && port <= 65535,
- "graph-connection.port.must-be-in-range", "[1, 65535]", port);
-
- Ex.check((StringUtils.isEmpty(newEntity.getUsername()) &&
- StringUtils.isEmpty(newEntity.getPassword())) ||
- (!StringUtils.isEmpty(newEntity.getUsername()) &&
- !StringUtils.isEmpty(newEntity.getPassword())),
- "graph-connection.username-or-password.must-be-same-status");
-
- Ex.check(newEntity.getCreateTime() == null,
- "common.param.must-be-null", "create_time");
- }
-
- private void checkAddressSecurity(GraphConnection newEntity) {
- String host = newEntity.getHost();
- Integer port = newEntity.getPort();
- InetAddress address;
- try {
- address = InetAddress.getByName(host);
- } catch (UnknownHostException e) {
- throw new ExternalException("graph-connection.host.unresolved");
- }
- String ip = address.getHostAddress();
- log.debug("The host: {}, ip: {}", address.getHostName(), ip);
-
- List ipWhiteList = this.config.get(
- HubbleOptions.CONNECTION_IP_WHITE_LIST);
- if (!ipWhiteList.contains("*")) {
- Ex.check(ipWhiteList.contains(host) || ipWhiteList.contains(ip),
- "graph-connection.host.unauthorized");
- }
-
- List portWhiteList = this.config.get(
- HubbleOptions.CONNECTION_PORT_WHITE_LIST);
- if (!portWhiteList.contains(-1)) {
- Ex.check(portWhiteList.contains(port),
- "graph-connection.port.unauthorized");
- }
- }
-
- private void checkEntityUnique(GraphConnection newEntity,
- boolean creating) {
- List oldEntities = this.connService.listAll();
- for (GraphConnection oldEntity : oldEntities) {
- // NOTE: create should check all, update check others
- if (!creating && oldEntity.getId().equals(newEntity.getId())) {
- continue;
- }
- Ex.check(!oldEntity.getName().equals(newEntity.getName()),
- "graph-connection.exist.name", oldEntity.getName());
- Ex.check(!(oldEntity.getGraph().equals(newEntity.getGraph()) &&
- oldEntity.getHost().equals(newEntity.getHost()) &&
- oldEntity.getPort().equals(newEntity.getPort())),
- "graph-connection.exist.graph-host-port",
- oldEntity.getGraph(), oldEntity.getHost(),
- oldEntity.getPort());
- }
- }
-}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/SettingController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/SettingController.java
index 1a00a1afa..4f9a746cb 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/SettingController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/SettingController.java
@@ -18,12 +18,6 @@
package org.apache.hugegraph.controller;
-import java.util.concurrent.TimeUnit;
-
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.entity.UserInfo;
import org.apache.hugegraph.service.UserInfoService;
@@ -34,6 +28,11 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.concurrent.TimeUnit;
+
@RestController
@RequestMapping(Constant.API_VERSION + "setting")
public class SettingController {
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OlapAlgoController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OlapAlgoController.java
new file mode 100644
index 000000000..e20e716f9
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OlapAlgoController.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.algorithm;
+
+import lombok.extern.log4j.Log4j2;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.algorithm.OlapEntity;
+import org.apache.hugegraph.entity.query.OlapView;
+import org.apache.hugegraph.service.algorithm.OlapAlgoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Log4j2
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}/algorithms/olap")
+public class OlapAlgoController extends BaseController {
+ @Autowired
+ private OlapAlgoService service;
+
+ @PostMapping
+ public OlapView olapView(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph,
+ @RequestBody OlapEntity body) {
+ HugeClient client = this.authClient(graphspace, graph);
+ return this.service.olapView(client, graphspace, body);
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
index 28a265968..c50d77ab0 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/OltpAlgoController.java
@@ -18,10 +18,46 @@
package org.apache.hugegraph.controller.algorithm;
+import lombok.extern.log4j.Log4j2;
+import org.apache.hugegraph.api.traverser.NeighborRankAPI;
+import org.apache.hugegraph.api.traverser.PersonalRankAPI;
+// TODO fix import
+//import org.apache.hugegraph.client.api.traverser.NeighborRankAPI;
+//import org.apache.hugegraph.client.api.traverser.PersonalRankAPI;
import org.apache.hugegraph.common.Constant;
-import org.apache.hugegraph.entity.algorithm.ShortestPath;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.algorithm.AdamicadarEntity;
+import org.apache.hugegraph.entity.algorithm.AllShortestPathsEntity;
+import org.apache.hugegraph.entity.algorithm.CrossPointsEntity;
+import org.apache.hugegraph.entity.algorithm.JaccardSimilarityEntity;
+import org.apache.hugegraph.entity.algorithm.KneighborEntity;
+import org.apache.hugegraph.entity.algorithm.KoutEntity;
+import org.apache.hugegraph.entity.algorithm.PathsEntity;
+import org.apache.hugegraph.entity.algorithm.RaysEntity;
+import org.apache.hugegraph.entity.algorithm.ResourceallocationEntity;
+import org.apache.hugegraph.entity.algorithm.RingsEntity;
+import org.apache.hugegraph.entity.algorithm.SameNeighborsEntity;
+import org.apache.hugegraph.entity.algorithm.ShortestPathEntity;
+import org.apache.hugegraph.entity.algorithm.SingleSourceShortestPathEntity;
+import org.apache.hugegraph.entity.algorithm.WeightedShortestPathEntity;
+import org.apache.hugegraph.entity.query.EgonetView;
+import org.apache.hugegraph.entity.query.FusiformsimilarityView;
import org.apache.hugegraph.entity.query.GremlinResult;
+import org.apache.hugegraph.entity.query.JaccardsimilarityView;
+import org.apache.hugegraph.entity.query.RanksView;
import org.apache.hugegraph.service.algorithm.OltpAlgoService;
+import org.apache.hugegraph.structure.traverser.CrosspointsRequest;
+import org.apache.hugegraph.structure.traverser.CustomizedPathsRequest;
+import org.apache.hugegraph.structure.traverser.EgonetRequest;
+import org.apache.hugegraph.structure.traverser.FusiformSimilarityRequest;
+import org.apache.hugegraph.structure.traverser.KneighborRequest;
+import org.apache.hugegraph.structure.traverser.KoutRequest;
+import org.apache.hugegraph.structure.traverser.MultiNodeShortestPathRequest;
+import org.apache.hugegraph.structure.traverser.PathsRequest;
+import org.apache.hugegraph.structure.traverser.SameNeighborsBatchRequest;
+import org.apache.hugegraph.structure.traverser.SingleSourceJaccardSimilarityRequest;
+import org.apache.hugegraph.structure.traverser.TemplatePathsRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -29,19 +65,246 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import lombok.extern.log4j.Log4j2;
+import java.util.Map;
@Log4j2
@RestController
-@RequestMapping(Constant.API_VERSION + "graph-connections/{connId}/algorithms")
-public class OltpAlgoController {
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}/algorithms/oltp")
+public class OltpAlgoController extends BaseController {
@Autowired
private OltpAlgoService service;
@PostMapping("shortestPath")
- public GremlinResult shortPath(@PathVariable("connId") int connId,
- @RequestBody ShortestPath body) {
- return this.service.shortestPath(connId, body);
+ public GremlinResult shortPath(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody ShortestPathEntity body) {
+ HugeClient client = this.authGremlinClient(graphSpace, graph);
+ return this.service.shortestPath(client, body);
+ }
+
+ @PostMapping("shortpath")
+ public GremlinResult shortPathAlias(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody ShortestPathEntity body) {
+ return this.shortPath(graphSpace, graph, body);
+ }
+
+ @PostMapping("rings")
+ public GremlinResult rings(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody RingsEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.rings(client, body);
+ }
+
+ @PostMapping("advancedPaths")
+ public GremlinResult advancedPaths(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody PathsRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.advancedpaths(client, body);
+ }
+
+ @PostMapping("sameNeighbors")
+ public GremlinResult sameNeighbors(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody SameNeighborsEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.sameNeighbors(client, body);
+ }
+
+ @PostMapping("kout")
+ public GremlinResult kout(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody KoutEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.kout(client, body);
+ }
+
+ @PostMapping("kout_post")
+ public GremlinResult koutPost(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody KoutRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.koutPost(client, body);
+ }
+
+ @PostMapping("kneighbor")
+ public GremlinResult kneighbor(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody KneighborEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.kneighbor(client, body);
+ }
+
+ @PostMapping("kneighbor_post")
+ public GremlinResult kneighborPost(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody KneighborRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.kneighborPost(client, body);
+ }
+
+ @PostMapping("jaccardSimilarity")
+ public JaccardsimilarityView jaccardSimilarity(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody JaccardSimilarityEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.jaccardSimilarity(client, body);
+ }
+
+ @PostMapping("jaccardSimilarity_post")
+ public JaccardsimilarityView jaccardSimilarityPost(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody SingleSourceJaccardSimilarityRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.jaccardSimilarityPost(client, body);
+ }
+
+ @PostMapping("personalrank")
+ public RanksView personalRank(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody PersonalRankAPI.Request body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.personalRank(client, body);
+ }
+
+ @PostMapping("neighborrank")
+ public RanksView neighborRank(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody NeighborRankAPI.Request body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.neighborRank(client, body);
+ }
+
+ @PostMapping("allshortestpaths")
+ public GremlinResult allShortPaths(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody AllShortestPathsEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.allShortestPaths(client, body);
+ }
+
+ @PostMapping("allshortpath")
+ public GremlinResult allShortPathAlias(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody AllShortestPathsEntity body) {
+ return this.allShortPaths(graphSpace, graph, body);
+ }
+
+ @PostMapping("weightedshortestpath")
+ public GremlinResult weightedShortestPath(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody WeightedShortestPathEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.weightedShortestPath(client, body);
+ }
+
+ @PostMapping("singlesourceshortestpath")
+ public GremlinResult singleSourceShortestPath(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody SingleSourceShortestPathEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.singleSourceShortestPath(client, body);
+ }
+
+ @PostMapping("multinodeshortestpath")
+ public GremlinResult multiNodeShortestPath(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody MultiNodeShortestPathRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.multiNodeShortestPath(client, body);
+ }
+
+ @PostMapping("paths")
+ public GremlinResult paths(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody PathsEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.paths(client, body);
+ }
+
+ @PostMapping("customizedpaths")
+ public GremlinResult customizedPaths(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody CustomizedPathsRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.customizedPaths(client, body);
+ }
+
+ @PostMapping("templatepaths")
+ public GremlinResult templatePaths(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody TemplatePathsRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.templatePaths(client, body);
+ }
+
+ @PostMapping("crosspoints")
+ public GremlinResult crosspoints(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody CrossPointsEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.crosspoints(client, body);
+ }
+
+ @PostMapping("customizedcrosspoints")
+ public GremlinResult customizedcrosspoints(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody CrosspointsRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.customizedcrosspoints(client, body);
+ }
+
+ @PostMapping("rays")
+ public GremlinResult rays(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody RaysEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.rays(client, body);
+ }
+
+ @PostMapping("fusiformsimilarity")
+ public FusiformsimilarityView fusiformsimilarity(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody FusiformSimilarityRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.fusiformsimilarity(client, body);
+ }
+
+ @PostMapping("adamicadar")
+ public Map adamicadar(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody AdamicadarEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.adamicadar(client, body);
+ }
+
+ @PostMapping("resourceallocation")
+ public Map resourceallocation(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody ResourceallocationEntity body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.resourceallocation(client, body);
+ }
+
+ @PostMapping("sameneighborsbatch")
+ public GremlinResult sameneighborsbatch(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody SameNeighborsBatchRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.sameneighborsbatch(client, body);
+ }
+
+ @PostMapping("egonet")
+ public EgonetView egonet(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody EgonetRequest body) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.service.egonet(client, body);
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/VermeerAlgoController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/VermeerAlgoController.java
new file mode 100644
index 000000000..c9eca817c
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/algorithm/VermeerAlgoController.java
@@ -0,0 +1,92 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.algorithm;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.loader.util.JsonUtil;
+import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.service.space.VermeerService;
+import org.apache.hugegraph.util.E;
+import org.apache.hugegraph.util.HubbleUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}/algorithms/vermeer")
+public class VermeerAlgoController extends BaseController {
+
+ @Autowired
+ VermeerService vermeerService;
+ @Autowired
+ private HugeConfig config;
+
+ @PostMapping
+ public Map olapView(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph,
+ @RequestBody VParams body) {
+ String vGraph = vermeerService.convert2VG(graphspace, graph);
+ HugeClient client = this.authClient(null, null);
+
+ Map graphInfo = HubbleUtil.uncheckedCast(
+ client.vermeer().getGraphInfoByName(vGraph).get("graph"));
+ E.checkArgument(graphInfo != null && !graphInfo.isEmpty(),
+ "graph not loaded");
+
+ Map params = new HashMap<>();
+ // default params
+ String pdPeers = config.get(HubbleOptions.PD_PEERS);
+ String pdJson = JsonUtil.toJson(Arrays.asList(pdPeers.split(",")));
+ params.put("output.parallel", "10");
+ params.put("output.type", "hugegraph");
+ params.put("output.hg_pd_peers", pdJson);
+ params.put("output.hugegraph_name", graphspace + "/" + graph + "/g");
+ params.put("output.hugegraph_username", this.getUser());
+ params.put("output.hugegraph_password", this.getCredentialPassword());
+ params.put("output.hugegraph_property", body.params.get("compute" +
+ ".algorithm"));
+ // input params
+ params.putAll(body.analyze());
+ return vermeerService.compute(client, graphspace, graph, params);
+ }
+
+ private static class VParams {
+ @JsonProperty("params")
+ public Map params;
+
+ public Map analyze() {
+ for (Map.Entry entry : params.entrySet()) {
+ entry.setValue(entry.getValue().toString());
+ }
+ return params;
+ }
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
new file mode 100644
index 000000000..dbd723be6
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AccessController.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import java.util.List;
+
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.auth.AccessEntity;
+import org.apache.hugegraph.service.auth.AccessService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/auth/accesses")
+public class AccessController extends AuthController {
+
+ @Autowired
+ private AccessService accessService;
+
+ @GetMapping
+ public List list(
+ @PathVariable("graphspace") String graphSpace,
+ @RequestParam(value = "role_id", required = false) String roleId,
+ @RequestParam(value = "target_id", required = false) String targetId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.accessService.list(client, graphSpace, roleId, targetId);
+ }
+
+ @GetMapping("{id}")
+ public AccessEntity get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String accessId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.accessService.get(client, graphSpace, accessId);
+ }
+
+ @PostMapping
+ public AccessEntity add(@PathVariable("graphspace") String graphSpace,
+ @RequestBody AccessEntity accessEntity) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.accessService.addOrUpdate(client, graphSpace, accessEntity);
+ }
+
+ @PutMapping
+ public AccessEntity update(@PathVariable("graphspace") String graphSpace,
+ @RequestBody AccessEntity accessEntity) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.accessService.addOrUpdate(client, graphSpace, accessEntity);
+ }
+
+ @DeleteMapping
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @RequestParam("role_id") String roleId,
+ @RequestParam("target_id") String targetId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ this.accessService.delete(client, roleId, targetId);
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AuthController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AuthController.java
new file mode 100644
index 000000000..cf6ebb6f5
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/AuthController.java
@@ -0,0 +1,24 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import org.apache.hugegraph.controller.BaseController;
+
+public class AuthController extends BaseController {
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
new file mode 100644
index 000000000..16b0b9201
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/BelongController.java
@@ -0,0 +1,122 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.auth.BelongEntity;
+import org.apache.hugegraph.service.auth.BelongService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/auth/belongs")
+public class BelongController extends AuthController {
+
+ @Autowired
+ private BelongService belongService;
+
+ public List list(
+ @PathVariable("graphspace") String graphSpace,
+ @RequestParam(value = "role_id", required = false) String roleId,
+ @RequestParam(value = "user_id", required = false) String userId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.belongService.list(client, roleId, userId);
+ }
+
+ @GetMapping
+ public IPage listPage(
+ @PathVariable("graphspace") String graphSpace,
+ @RequestParam(value = "role_id", required = false) String roleId,
+ @RequestParam(value = "user_id", required = false) String userId,
+ @RequestParam(name = "page_no", required = false,
+ defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false,
+ defaultValue = "10") int pageSize) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.belongService.listPage(client, roleId, userId, pageNo,
+ pageSize);
+ }
+
+ @GetMapping("{id}")
+ public BelongEntity get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String belongId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.belongService.get(client, belongId);
+ }
+
+ @PostMapping
+ public void create(@PathVariable("graphspace") String graphSpace,
+ @RequestBody BelongEntity belongEntity) {
+ HugeClient client = this.authClient(graphSpace, null);
+ this.belongService.add(client, belongEntity.getRoleId(),
+ belongEntity.getUserId());
+ }
+
+ @PostMapping("ids")
+ public void createMany(@PathVariable("graphspace") String graphSpace,
+ @RequestBody BelongService.BelongsReq belongsReq) {
+ HugeClient client = this.authClient(graphSpace, null);
+ for (String userId : belongsReq.getUserIds()) {
+ this.belongService.add(client, belongsReq.getRoleId(), userId);
+ }
+ }
+
+ @DeleteMapping("{id}")
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String belongId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ this.belongService.delete(client, belongId);
+ }
+
+ @DeleteMapping
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @RequestParam("role_id") String roleId,
+ @RequestParam("user_id") String userId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ if (StringUtils.isNotEmpty(roleId) && StringUtils.isNotEmpty(userId)) {
+ this.belongService.delete(client, roleId, userId);
+ }
+ }
+
+ @PostMapping("delids")
+ public void deleteMany(@PathVariable("graphspace") String graphSpace,
+ @RequestBody DelIdsReq delIdsReq) {
+ HugeClient client = this.authClient(graphSpace, null);
+ this.belongService.deleteMany(client,
+ delIdsReq.ids.toArray(new String[0]));
+ }
+
+ public static class DelIdsReq {
+
+ public List ids = new ArrayList<>();
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
new file mode 100644
index 000000000..4464bc2f8
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GraphSpaceUserController.java
@@ -0,0 +1,118 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.auth.UserView;
+import org.apache.hugegraph.service.auth.GraphSpaceUserService;
+import org.apache.hugegraph.structure.auth.User;
+import org.apache.hugegraph.structure.auth.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/auth/users")
+public class GraphSpaceUserController extends AuthController {
+
+ @Autowired
+ private GraphSpaceUserService userService;
+
+ @GetMapping
+ public IPage querySpaceUserViews(
+ @PathVariable("graphspace") String graphSpace,
+ @RequestParam(name = "query", required = false,
+ defaultValue = "") String query,
+ @RequestParam(name = "page_no", required = false,
+ defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false,
+ defaultValue = "10") int pageSize) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.userService.queryPage(client, query, pageNo, pageSize);
+ }
+
+ @GetMapping("spaceadmin")
+ public IPage querySpaceAdmins(
+ @PathVariable("graphspace") String graphSpace,
+ @RequestParam(name = "query", required = false,
+ defaultValue = "") String query,
+ @RequestParam(name = "page_no", required = false,
+ defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false,
+ defaultValue = "10") int pageSize) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.userService.querySpaceAdmins(client, graphSpace, query,
+ pageNo, pageSize);
+ }
+
+ @GetMapping("{id}")
+ public UserView get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String userId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.userService.getUser(client, userId);
+ }
+
+ @GetMapping("spaceadmin/{id}")
+ public UserManager setGraphSpaceAdmin(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String userId) {
+ HugeClient client = this.authClient(null, null);
+ return client.auth().addSpaceAdmin(userId, graphSpace);
+ }
+
+ @DeleteMapping("spaceadmin/{id}")
+ public void removeGraphSpaceAdmin(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String userId) {
+ HugeClient client = this.authClient(null, null);
+ client.auth().delSpaceAdmin(userId, graphSpace);
+ }
+
+ @PostMapping
+ public UserView create(@PathVariable("graphspace") String graphSpace,
+ @RequestBody UserView userView) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.userService.createOrUpdate(client, userView);
+ }
+
+ @PutMapping("{id}")
+ public UserView createOrUpdate(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String userId,
+ @RequestBody UserView userView) {
+ HugeClient client = this.authClient(graphSpace, null);
+ userView.setId(userId);
+ return this.userService.createOrUpdate(client, userView);
+ }
+
+ @DeleteMapping("{id}")
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String userId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ this.userService.unauthUser(client, userId);
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GroupController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GroupController.java
new file mode 100644
index 000000000..f2fc8ad03
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/GroupController.java
@@ -0,0 +1,102 @@
+///*
+// *
+// * Licensed to the Apache Software Foundation (ASF) under one or more
+// * contributor license agreements. See the NOTICE file distributed with this
+// * work for additional information regarding copyright ownership. The ASF
+// * licenses this file to You under the Apache License, Version 2.0 (the
+// * "License"); you may not use this file except in compliance with the License.
+// * You may obtain a copy of the License at
+// *
+// * http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// * License for the specific language governing permissions and limitations
+// * under the License.
+// */
+//
+//package org.apache.hugegraph.controller.auth;
+//
+//import java.util.List;
+//import java.util.Map;
+//
+//import org.apache.hugegraph.controller.BaseController;
+//import com.baomidou.mybatisplus.core.metadata.IPage;
+//import org.apache.hugegraph.service.auth.GroupService;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.web.bind.annotation.DeleteMapping;
+//import org.springframework.web.bind.annotation.GetMapping;
+//import org.springframework.web.bind.annotation.PathVariable;
+//import org.springframework.web.bind.annotation.PostMapping;
+//import org.springframework.web.bind.annotation.PutMapping;
+//import org.springframework.web.bind.annotation.RequestBody;
+//import org.springframework.web.bind.annotation.RequestMapping;
+//import org.springframework.web.bind.annotation.RequestParam;
+//import org.springframework.web.bind.annotation.RestController;
+//
+//import org.apache.hugegraph.common.Constant;
+//import org.apache.hugegraph.structure.auth.Group;
+//import org.apache.hugegraph.driver.HugeClient;
+//
+//@RestController
+//@RequestMapping(Constant.API_VERSION + "auth/groups")
+//public class GroupController extends BaseController {
+//
+// @Autowired
+// private GroupService groupService;
+//
+// @GetMapping("list")
+// public List list() {
+// HugeClient client = this.authClient(null, null);
+// return this.groupService.list(client);
+// }
+//
+// @GetMapping
+// public IPage queryPage(@RequestParam(name = "query", required = false,
+// defaultValue = "") String query,
+// @RequestParam(name = "page_no", required = false,
+// defaultValue = "1") int pageNo,
+// @RequestParam(name = "page_size", required = false,
+// defaultValue = "10") int pageSize) {
+// HugeClient client = this.authClient(null, null);
+// return this.groupService.queryPage(client, query, pageNo, pageSize);
+// }
+//
+// @GetMapping("{id}")
+// public Group get(@PathVariable("id") String rid) {
+// HugeClient client = this.authClient(null, null);
+// return this.groupService.get(client, rid);
+// }
+//
+// @PostMapping
+// public Group add(@RequestBody Group group) {
+// HugeClient client = this.authClient(null, null);
+// return this.groupService.insert(client, group);
+// }
+//
+// @PutMapping("{id}")
+// public Group update(@PathVariable("id") String id,
+// @RequestBody Group group) {
+// HugeClient client = this.authClient(null, null);
+// Group g = this.groupService.get(client, id);
+// g.description(group.description());
+// g.nickname(group.nickname());
+// this.groupService.update(client, g);
+//
+// return g;
+// }
+//
+// @DeleteMapping("{id}")
+// public void delete(@PathVariable("id") String id) {
+// HugeClient client = this.authClient(null, null);
+// this.groupService.delete(client, id);
+// }
+//
+// @PostMapping("batch/{id}")
+// public Map batch(@PathVariable("id") String id,
+// @RequestBody Map action) {
+// HugeClient client = this.authClient(null, null);
+// return groupService.batch(client, id, action);
+// }
+//}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
new file mode 100644
index 000000000..967a78f12
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/LoginController.java
@@ -0,0 +1,240 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.entity.auth.UserEntity;
+import org.apache.hugegraph.exception.ExternalException;
+import org.apache.hugegraph.exception.ServerException;
+import com.google.common.collect.ImmutableMap;
+import org.apache.hugegraph.driver.factory.PDHugeClientFactory;
+import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.service.auth.UserService;
+import org.apache.hugegraph.service.auth.LoginAttemptGuard;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.structure.auth.Login;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.structure.auth.LoginResult;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "auth")
+public class LoginController extends BaseController {
+
+ private static final int TOKEN_EXPIRE_SECONDS = 60 * 60 * 24 * 30;
+ private static final int CONNECT_TIMEOUT_MILLIS = 5000;
+ private static final int READ_TIMEOUT_MILLIS = 30000;
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ @Autowired
+ UserService userService;
+ @Autowired
+ private HugeConfig config;
+ @Autowired
+ private LoginAttemptGuard loginAttemptGuard;
+
+ @PostMapping("/login")
+ public Object login(@RequestBody Login login) {
+ String address = this.getRequest().getRemoteAddr();
+ boolean pdEnabled = this.config.get(HubbleOptions.PD_ENABLED);
+ this.loginAttemptGuard.checkAllowed(login.name(), address);
+ // Set Expire: 1 Month
+ login.expire(TOKEN_EXPIRE_SECONDS);
+ try {
+ LoginResult result = this.authenticate(login, pdEnabled, address);
+ Object user;
+ if (!pdEnabled) {
+ user = currentUser(login.name());
+ } else {
+ try (HugeClient client =
+ this.createLoginTokenClient(result.token())) {
+ client.assignGraph(PDHugeClientFactory.DEFAULT_GRAPHSPACE,
+ null);
+ UserEntity entity = this.userService.getUser(client,
+ login.name());
+ entity.setSuperadmin(
+ this.userService.isSuperAdmin(client));
+ user = entity;
+ }
+ }
+
+ this.getRequest().getSession();
+ this.getRequest().changeSessionId();
+ this.setUser(login.name());
+ this.setCredentialPassword(login.password());
+ this.setToken(result.token());
+ return user;
+ } catch (Throwable e) {
+ this.clearAuthSession();
+ throw e;
+ } finally {
+ this.clearRequestHugeClient();
+ }
+ }
+
+ private LoginResult authenticate(Login login, boolean pdEnabled,
+ String address) {
+ try {
+ LoginResult result;
+ if (!pdEnabled) {
+ result = this.loginStandalone(login);
+ } else {
+ try (HugeClient client =
+ this.createLoginClient(login.name(),
+ login.password())) {
+ result = client.auth().login(login);
+ }
+ }
+ this.loginAttemptGuard.reset(login.name(), address);
+ return result;
+ } catch (Throwable e) {
+ if (isAuthenticationFailure(e)) {
+ this.loginAttemptGuard.recordFailure(login.name(), address);
+ } else {
+ this.loginAttemptGuard.release(login.name(), address);
+ }
+ throw e;
+ }
+ }
+
+ private static boolean isAuthenticationFailure(Throwable error) {
+ if (error instanceof ServerException) {
+ int status = ((ServerException) error).status();
+ return status == HttpStatus.UNAUTHORIZED.value() ||
+ status == HttpStatus.FORBIDDEN.value();
+ }
+ return error instanceof ExternalException &&
+ (((ExternalException) error).status() ==
+ HttpStatus.UNAUTHORIZED.value() ||
+ ((ExternalException) error).status() ==
+ HttpStatus.FORBIDDEN.value());
+ }
+
+ protected LoginResult loginStandalone(Login login) {
+ String endpoint = this.config.get(HubbleOptions.SERVER_URL) +
+ "/auth/login";
+ HttpURLConnection connection = null;
+ try {
+ connection = this.openConnection(new URL(endpoint));
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(true);
+ connection.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
+ connection.setReadTimeout(READ_TIMEOUT_MILLIS);
+ connection.setRequestProperty("Content-Type",
+ "application/json;charset=UTF-8");
+ String auth = login.name() + ":" + login.password();
+ String basic = Base64.getEncoder().encodeToString(
+ auth.getBytes(StandardCharsets.UTF_8));
+ connection.setRequestProperty("Authorization", "Basic " + basic);
+ Map body = ImmutableMap.of(
+ "user_name", login.name(),
+ "user_password", login.password(),
+ "token_expire", TOKEN_EXPIRE_SECONDS
+ );
+ try (OutputStream output = connection.getOutputStream()) {
+ output.write(MAPPER.writeValueAsBytes(body));
+ }
+ int status = connection.getResponseCode();
+ if (status == HttpStatus.UNAUTHORIZED.value() ||
+ status == HttpStatus.FORBIDDEN.value()) {
+ throw new ExternalException(status,
+ "graph-connection.username-or-password.incorrect");
+ }
+ if (status >= 400) {
+ throw new IOException("Standalone login failed: HTTP " +
+ status);
+ }
+ Map, ?> response;
+ try (InputStream input = connection.getInputStream()) {
+ response = MAPPER.readValue(input, Map.class);
+ }
+ LoginResult result = new LoginResult();
+ result.token(String.valueOf(response.get("token")));
+ return result;
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to login HugeGraph Server", e);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ protected HttpURLConnection openConnection(URL endpoint)
+ throws IOException {
+ return (HttpURLConnection) endpoint.openConnection();
+ }
+
+ protected HugeClient createLoginClient(String username, String password) {
+ return this.hugeClientPoolService.createTempBasicClient(username,
+ password);
+ }
+
+ protected HugeClient createLoginTokenClient(String token) {
+ return this.hugeClientPoolService.createTempTokenClient(token);
+ }
+
+ private static UserEntity currentUser(String username) {
+ UserEntity user = new UserEntity();
+ user.setId(username);
+ user.setName(username);
+ user.setNickname(username);
+ user.setAdminSpaces(Collections.emptyList());
+ user.setResSpaces(Collections.emptyList());
+ user.setSpacenum(0);
+ user.setSuperadmin(false);
+ return user;
+ }
+
+ @GetMapping("/status")
+ public Object status() {
+
+ HugeClient client = authClient(null, null);
+
+ String level = userService.userLevel(client);
+
+ return ImmutableMap.of("level", level);
+ }
+
+ // FIXME: Change logout to POST and add CSRF/Origin protection after coordinating
+ // the API change with the frontend; explicitly harden the session cookie as well.
+ @GetMapping("/logout")
+ public void logout() {
+ this.clearAuthSession();
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
new file mode 100644
index 000000000..386f1809b
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/RoleController.java
@@ -0,0 +1,119 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.service.auth.RoleService;
+import org.apache.hugegraph.structure.auth.Role;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/auth/roles")
+public class RoleController extends AuthController {
+
+ @Autowired
+ private RoleService roleService;
+
+ @GetMapping("list")
+ public List listName(@PathVariable("graphspace") String graphSpace) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.roleService.list(client, graphSpace);
+ }
+
+ @GetMapping
+ public IPage queryPage(
+ @PathVariable("graphspace") String graphSpace,
+ @RequestParam(name = "query", required = false,
+ defaultValue = "") String query,
+ @RequestParam(name = "page_no", required = false,
+ defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false,
+ defaultValue = "10") int pageSize) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.roleService.queryPage(client, graphSpace, query, pageNo,
+ pageSize);
+ }
+
+ @GetMapping("{id}")
+ public Role get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String roleId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.roleService.get(client, graphSpace, roleId);
+ }
+
+ @PostMapping
+ public Role add(@PathVariable("graphspace") String graphSpace,
+ @RequestBody Role role) {
+ HugeClient client = this.authClient(graphSpace, null);
+ role.graphSpace(graphSpace);
+ return this.roleService.insert(client, role);
+ }
+
+ @PutMapping("{id}")
+ public Role update(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String id,
+ @RequestBody Map body) {
+ HugeClient client = this.authClient(graphSpace, null);
+ Role current = this.roleService.get(client, graphSpace, id);
+ String name = firstNonBlank(body, "role_name", "group_name",
+ "new_group_name");
+ if (name != null) {
+ current.name(name);
+ current.nickname(name);
+ }
+ String description = firstNonBlank(body, "role_description",
+ "group_description");
+ if (description != null) {
+ current.description(description);
+ }
+ return this.roleService.update(client, current);
+ }
+
+ @DeleteMapping("{id}")
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String id) {
+ HugeClient client = this.authClient(graphSpace, null);
+ this.roleService.delete(client, id);
+ }
+
+ private static String firstNonBlank(Map body,
+ String... keys) {
+ for (String key : keys) {
+ Object value = body.get(key);
+ if (value instanceof String && !((String) value).isEmpty()) {
+ return (String) value;
+ }
+ }
+ return null;
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
new file mode 100644
index 000000000..5dbda66bf
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/TargetController.java
@@ -0,0 +1,96 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import java.util.List;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.service.auth.TargetService;
+import org.apache.hugegraph.structure.auth.Target;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/auth/targets")
+public class TargetController extends AuthController {
+
+ @Autowired
+ private TargetService targetService;
+
+ @GetMapping("list")
+ public List list(@PathVariable("graphspace") String graphSpace) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.targetService.list(client);
+ }
+
+ @GetMapping
+ public IPage queryPage(
+ @PathVariable("graphspace") String graphSpace,
+ @RequestParam(name = "query", required = false,
+ defaultValue = "") String query,
+ @RequestParam(name = "page_no", required = false,
+ defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false,
+ defaultValue = "10") int pageSize) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.targetService.queryPage(client, query, pageNo, pageSize);
+ }
+
+ @GetMapping("{id}")
+ public Target get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String targetId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.targetService.get(client, targetId);
+ }
+
+ @PostMapping
+ public Target add(@PathVariable("graphspace") String graphSpace,
+ @RequestBody Target target) {
+ HugeClient client = this.authClient(graphSpace, null);
+ return this.targetService.add(client, target);
+ }
+
+ @PutMapping("{id}")
+ public Target update(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String targetId,
+ @RequestBody Target target) {
+ HugeClient client = this.authClient(graphSpace, null);
+ Target current = this.targetService.get(client, targetId);
+ current.resources(target.resources());
+ current.description(target.description());
+ return this.targetService.update(client, current);
+ }
+
+ @DeleteMapping("{id}")
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("id") String targetId) {
+ HugeClient client = this.authClient(graphSpace, null);
+ this.targetService.delete(client, targetId);
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/UserController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/UserController.java
new file mode 100644
index 000000000..8c69bc33a
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/UserController.java
@@ -0,0 +1,159 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.common.Response;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.auth.PasswordEntity;
+import org.apache.hugegraph.entity.auth.UserEntity;
+import org.apache.hugegraph.service.auth.UserService;
+import org.apache.hugegraph.exception.UnauthorizedException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "auth/users")
+public class UserController extends BaseController {
+
+ @Autowired
+ UserService userService;
+
+ @GetMapping("list")
+ public Object list() {
+ List users = this.userService.listUsers(
+ this.authClient(null, null));
+ return ImmutableMap.of("users", users);
+ }
+
+ @GetMapping
+ public Object queryPage(@RequestParam(name = "query", required = false,
+ defaultValue = "") String query,
+ @RequestParam(name = "page_no", required = false,
+ defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false,
+ defaultValue = "10") int pageSize) {
+ return userService.queryPage(this.authClient(null, null),
+ query, pageNo, pageSize);
+ }
+
+ @PostMapping
+ public void create(@RequestBody UserEntity userEntity) {
+ HugeClient client = this.authClient(null, null);
+ userService.add(client, userEntity);
+ }
+
+ @PostMapping("batch")
+ public void createbatch(@RequestParam("file") MultipartFile csvfile) {
+ HugeClient client = this.authClient(null, null);
+ userService.addbatch(client, csvfile);
+ }
+
+ @GetMapping("{id}")
+ public Object get(@PathVariable("id") String id) {
+ return userService.get(this.authClient(null, null),
+ id);
+ }
+
+ @PutMapping("{id}")
+ public void update(@PathVariable("id") String id,
+ @RequestBody UserEntity userEntity) {
+ userEntity.setId(id);
+ userService.update(this.authClient(null, null), userEntity);
+ }
+
+ @DeleteMapping("{id}")
+ public void delete(@PathVariable("id") String id) {
+ userService.delete(this.authClient(null, null), id);
+ }
+
+ @PostMapping("updatepwd")
+ public Response updatepwd(@RequestBody PasswordEntity pwd) {
+ HugeClient client = this.authClient(null, null);
+ return userService.updatepwd(client, pwd.getUsername(), pwd.getOldpwd(), pwd.getNewpwd());
+ }
+
+ @GetMapping("listadminspace/{username}")
+ public List listadminspace(@PathVariable("username") String username) {
+ HugeClient client = this.authClient(null, null);
+ return userService.listAdminSpace(client, username);
+ }
+
+ @PostMapping("updateadminspace/{username}")
+ public void updateadminspace(@PathVariable("username") String username,
+ @RequestBody List adminspaces) {
+ HugeClient client = this.authClient(null, null);
+ userService.updateAdminSpace(client, username, adminspaces);
+ }
+
+ @PutMapping("personal")
+ public void updatePersonal(@RequestBody PersonalProfile profile) {
+ String username = this.getUser();
+ if (username == null) {
+ throw new UnauthorizedException();
+ }
+ this.checkParamsNotEmpty("nickname", profile.getNickname(), true);
+ String description = profile.getDescription() == null ? "" :
+ profile.getDescription();
+ userService.updatePersonal(this.authClient(null, null), username,
+ profile.getNickname(), description);
+ }
+
+ @GetMapping("getpersonal")
+ public Object getpersonal() {
+ return userService.getpersonal(this.authClient(null, null),
+ getUser());
+ }
+
+ public static class PersonalProfile {
+
+ private String nickname;
+ private String description;
+
+ public String getNickname() {
+ return this.nickname;
+ }
+
+ public void setNickname(String nickname) {
+ this.nickname = nickname;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+ }
+
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/WhiteIpListController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/WhiteIpListController.java
new file mode 100644
index 000000000..99300acdb
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/auth/WhiteIpListController.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.auth;
+
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.auth.WhiteIpListEntity;
+import org.apache.hugegraph.service.auth.WhiteIpListService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping(Constant.API_VERSION + "auth/whiteiplist")
+public class WhiteIpListController extends AuthController {
+ @Autowired
+ private WhiteIpListService whiteListService;
+
+ @GetMapping("list")
+ public Map list() {
+ HugeClient client = this.authClient(null, null);
+ return this.whiteListService.get(client);
+ }
+
+ @PostMapping("batch")
+ public Map update(@RequestBody WhiteIpListEntity whiteIpListEntity) {
+ HugeClient client = this.authClient(null, null);
+ return this.whiteListService.batch(client, whiteIpListEntity);
+ }
+
+ @PutMapping("updatestatus")
+ public Map update(@RequestBody boolean status) {
+ HugeClient client = this.authClient(null, null);
+ return this.whiteListService.updatestatus(client, status);
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/GraphController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/GraphController.java
index d103059fd..1acb0d5b9 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/GraphController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graph/GraphController.java
@@ -18,35 +18,51 @@
package org.apache.hugegraph.controller.graph;
-import java.util.Map;
-import java.util.Set;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.hugegraph.common.Constant;
-import org.apache.hugegraph.controller.BaseController;
-import org.apache.hugegraph.entity.graph.EdgeEntity;
-import org.apache.hugegraph.entity.graph.VertexEntity;
-import org.apache.hugegraph.entity.query.GraphView;
-import org.apache.hugegraph.entity.schema.EdgeLabelEntity;
-import org.apache.hugegraph.entity.schema.VertexLabelEntity;
-import org.apache.hugegraph.service.graph.GraphService;
-import org.apache.hugegraph.service.schema.EdgeLabelService;
-import org.apache.hugegraph.service.schema.VertexLabelService;
-import org.apache.hugegraph.structure.constant.IdStrategy;
+import org.apache.hugegraph.common.OptionType;
+
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.loader.util.JsonUtil;
import org.apache.hugegraph.structure.graph.Edge;
import org.apache.hugegraph.structure.graph.Vertex;
-import org.apache.hugegraph.util.Ex;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.entity.graph.EdgeEntity;
+import org.apache.hugegraph.entity.graph.VertexEntity;
+import org.apache.hugegraph.entity.graph.VertexQueryEntity;
+import org.apache.hugegraph.entity.query.ElementEditHistory;
+import org.apache.hugegraph.entity.query.GraphView;
+import org.apache.hugegraph.service.graph.GraphService;
+import org.apache.hugegraph.service.query.EditElementHistoryService;
+import org.apache.hugegraph.service.schema.EdgeLabelService;
+import org.apache.hugegraph.service.schema.VertexLabelService;
+import org.apache.hugegraph.util.E;
+
+import lombok.extern.slf4j.Slf4j;
+
@RestController
-@RequestMapping(Constant.API_VERSION + "graph-connections/{connId}/graph")
+@Slf4j
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}")
public class GraphController extends BaseController {
@Autowired
@@ -55,84 +71,209 @@ public class GraphController extends BaseController {
private EdgeLabelService elService;
@Autowired
private GraphService graphService;
+ @Autowired
+ private EditElementHistoryService editEleHisService;
@PostMapping("vertex")
- public GraphView addVertex(@PathVariable("connId") int connId,
+ public GraphView addVertex(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@RequestBody VertexEntity entity) {
- this.checkParamsValid(connId, entity, true);
- return this.graphService.addVertex(connId, entity);
+ HugeClient client = this.authClient(graphSpace, graph);
+ GraphView graphView = this.graphService.addVertex(client, entity);
+
+ E.checkState(graphView.getVertices().size() == 1,
+ "Adding a vertex must return exactly one vertex");
+ VertexQueryEntity v = graphView.getVertices().iterator().next();
+ String vertexId = v.id().toString();
+ addEditEleHistory(graphSpace, graph, vertexId, entity.getLabel(),
+ v.properties().size(),
+ OptionType.ADD, JsonUtil.toJson(entity));
+ return graphView;
}
@PutMapping("vertex/{id}")
- public Vertex updateVertex(@PathVariable("connId") int connId,
+ public Vertex updateVertex(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("id") String vertexId,
@RequestBody VertexEntity entity) {
+ HugeClient client = this.authClient(graphSpace, graph);
vertexId = UriUtils.decode(vertexId, Constant.CHARSET);
- this.checkParamsValid(connId, entity, false);
this.checkIdSameAsBody(vertexId, entity);
- return this.graphService.updateVertex(connId, entity);
+ Vertex result =
+ this.graphService.updateVertex(client, vertexId, entity);
+ addEditEleHistory(graphSpace, graph, vertexId,
+ entity.getLabel(), result.properties().size(),
+ OptionType.UPDATE, JsonUtil.toJson(entity));
+ return result;
+ }
+
+ @DeleteMapping("vertex/{id}")
+ public void deleteVertex(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("id") String vertexId) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ vertexId = UriUtils.decode(vertexId, Constant.CHARSET);
+ Vertex vertex = client.graph().getVertex(vertexId);
+ String label = vertex.label();
+
+ this.graphService.deleteVertex(client, vertexId);
+ addEditEleHistory(graphSpace, graph, vertexId, label,
+ vertex.properties().size(), OptionType.DELETE, "");
}
@PostMapping("edge")
- public GraphView addEdge(@PathVariable("connId") int connId,
+ public GraphView addEdge(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@RequestBody EdgeEntity entity) {
- this.checkParamsValid(connId, entity, true);
- return this.graphService.addEdge(connId, entity);
+ HugeClient client = this.authClient(graphSpace, graph);
+ GraphView edge = this.graphService.addEdge(client, entity);
+ E.checkState(edge.getEdges().size() == 1,
+ "Adding an edge must return exactly one edge");
+ Edge e = edge.getEdges().iterator().next();
+ String edgeId = e.id().toString();
+ addEditEleHistory(graphSpace, graph,
+ edgeId, entity.getLabel(),
+ e.properties().size(), OptionType.ADD,
+ JsonUtil.toJson(entity));
+ return edge;
}
@PutMapping("edge/{id}")
- public Edge updateEdge(@PathVariable("connId") int connId,
+ public Edge updateEdge(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("id") String edgeId,
@RequestBody EdgeEntity entity) {
edgeId = UriUtils.decode(edgeId, Constant.CHARSET);
- this.checkParamsValid(connId, entity, false);
+ HugeClient client = this.authClient(graphSpace, graph);
this.checkIdSameAsBody(edgeId, entity);
- return this.graphService.updateEdge(connId, entity);
+ Edge edge = this.graphService.updateEdge(client, edgeId, entity);
+ addEditEleHistory(graphSpace, graph, edgeId, entity.getLabel(),
+ edge.properties().size(), OptionType.UPDATE,
+ JsonUtil.toJson(entity));
+ return edge;
}
- private void checkParamsValid(int connId, VertexEntity entity,
- boolean create) {
- Ex.check(!StringUtils.isEmpty(entity.getLabel()),
- "common.param.cannot-be-null-or-empty", "label");
- // If schema doesn't exist, it will throw exception
- VertexLabelEntity vlEntity = this.vlService.get(entity.getLabel(),
- connId);
- IdStrategy idStrategy = vlEntity.getIdStrategy();
- if (create) {
- Ex.check(idStrategy.isCustomize(), () -> entity.getId() != null,
- "common.param.cannot-be-null", "id");
- } else {
- Ex.check(entity.getId() != null,
- "common.param.cannot-be-null", "id");
- }
+ @DeleteMapping("edge/{id}")
+ public void deleteEdge(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("id") String edgeId) {
+ edgeId = UriUtils.decode(edgeId, Constant.CHARSET);
+ HugeClient client = this.authClient(graphSpace, graph);
+ Edge edge = client.graph().getEdge(edgeId);
+ String label = edge.label();
- Set nonNullableProps = vlEntity.getNonNullableProps();
- Map properties = entity.getProperties();
- Ex.check(properties.keySet().containsAll(nonNullableProps),
- "graph.vertex.all-nonnullable-prop.should-be-setted");
+ this.graphService.deleteEdge(client, edgeId);
+ addEditEleHistory(graphSpace, graph, edgeId, label,
+ edge.properties().size(), OptionType.DELETE, "");
}
- private void checkParamsValid(int connId, EdgeEntity entity,
- boolean create) {
- Ex.check(!StringUtils.isEmpty(entity.getLabel()),
- "common.param.cannot-be-null-or-empty", "label");
- // If schema doesn't exist, it will throw exception
- EdgeLabelEntity elEntity = this.elService.get(entity.getLabel(), connId);
- if (create) {
- Ex.check(entity.getId() == null,
- "common.param.must-be-null", "id");
+ @DeleteMapping("element/batch")
+ public void deleteElements(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestParam(name = "type",
+ required = true) String type,
+ @RequestParam(name = "ids", required = true)
+ List elementIds) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ ArrayList list = new ArrayList<>();
+ HashSet set = new HashSet<>(elementIds);
+ if ("VERTEX".equals(type)) {
+ for (String vertexId : set) {
+ vertexId = UriUtils.decode(vertexId, Constant.CHARSET);
+ Vertex vertex = client.graph().getVertex(vertexId);
+ String label = vertex.label();
+ this.graphService.deleteVertex(client, vertexId);
+ list.add(getEditEleHistory(graphSpace, graph, vertexId, label,
+ vertex.properties().size(), OptionType.DELETE, ""));
+ }
+ } else if ("EDGE".equals(type)) {
+ for (String edgeId : elementIds) {
+ edgeId = UriUtils.decode(edgeId, Constant.CHARSET);
+ Edge edge = client.graph().getEdge(edgeId);
+ String label = edge.label();
+ this.graphService.deleteEdge(client, edgeId);
+ list.add(getEditEleHistory(graphSpace, graph, edgeId, label,
+ edge.properties().size(),
+ OptionType.DELETE, ""));
+ }
} else {
- Ex.check(entity.getId() != null,
- "common.param.cannot-be-null", "id");
+ throw new IllegalArgumentException(
+ "type must in [VERTEX, EDGE], but got '" + type + "'");
}
- Ex.check(entity.getSourceId() != null,
- "common.param.must-be-null", "source_id");
- Ex.check(entity.getTargetId() != null,
- "common.param.must-be-null", "target_id");
-
- Set nonNullableProps = elEntity.getNonNullableProps();
- Map properties = entity.getProperties();
- Ex.check(properties.keySet().containsAll(nonNullableProps),
- "graph.edge.all-nonnullable-prop.should-be-setted");
+ editEleHisService.add(list);
+ }
+
+ @GetMapping("edgelabel/{label}")
+ public HashMap getEdgeProperties(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("label") String label) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.graphService.getEdgeProperties(client, label);
+ }
+
+ @GetMapping("vertexlabel/{label}")
+ public HashMap getVertexProperties(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("label") String label) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.graphService.getVertexProperties(client, label);
+ }
+
+ @PostMapping("edgestype")
+ public HashMap getEdgeStyle(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody List labels) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.graphService.getEdgeStyle(client, labels);
+ }
+
+ @PostMapping("vertexstyle")
+ public HashMap getVertexStyle(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody List labels) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.graphService.getVertexStyle(client, labels);
+ }
+
+ @PostMapping("import")
+ public GraphView importJson(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestParam("file") MultipartFile jsonFile) throws IOException {
+ HugeClient client = this.authClient(graphSpace, graph);
+ return this.graphService.importJson(client, jsonFile);
+ }
+
+ private void addEditEleHistory(String graphSpace,
+ String graph,
+ String elementId,
+ String label,
+ int propertyNum,
+ OptionType optionType,
+ String content) {
+ this.editEleHisService.add(graphSpace, graph, elementId, label,
+ propertyNum, optionType.name(),
+ new Date(), getUser(), content);
+ }
+
+ private ElementEditHistory getEditEleHistory(String graphSpace,
+ String graph,
+ String elementId,
+ String label,
+ int propertyNum,
+ OptionType optionType,
+ String content) {
+ return ElementEditHistory.builder()
+ .graphspace(graphSpace)
+ .graph(graph)
+ .elementId(elementId)
+ .label(label)
+ .propertyNum(propertyNum)
+ .optionType(optionType.name())
+ .optionTime(new Date())
+ .optionPerson(getUser())
+ .content(content)
+ .build();
}
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graphs/GraphsController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graphs/GraphsController.java
new file mode 100644
index 000000000..f61936a2a
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/graphs/GraphsController.java
@@ -0,0 +1,359 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.graphs;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hugegraph.entity.graphs.GraphCloneEntity;
+import org.apache.hugegraph.entity.graphs.GraphCreateEntity;
+import org.apache.hugegraph.entity.graphs.GraphUpdateEntity;
+import org.apache.hugegraph.exception.ExternalException;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.entity.graphs.GraphStatisticsEntity;
+import org.apache.hugegraph.exception.ServerException;
+import org.apache.hugegraph.service.space.VermeerService;
+import org.apache.hugegraph.util.HubbleUtil;
+import org.apache.hugegraph.structure.constant.GraphReadMode;
+import com.google.common.collect.ImmutableMap;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.service.graphs.GraphsService;
+import org.apache.hugegraph.service.load.JobManagerService;
+import org.apache.hugegraph.service.query.ExecuteHistoryService;
+import org.apache.hugegraph.service.query.GremlinCollectionService;
+import org.apache.hugegraph.service.space.SchemaTemplateService;
+
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs")
+public class GraphsController extends BaseController {
+
+ @Autowired
+ GraphsService graphsService;
+ @Autowired
+ SchemaTemplateService schemaTemplateService;
+ @Autowired
+ ExecuteHistoryService executeHistoryService;
+ @Autowired
+ GremlinCollectionService gremlinCollectionService;
+ @Autowired
+ JobManagerService jobManagerService;
+ @Autowired
+ VermeerService vermeerService;
+ @Autowired
+ HugeConfig config;
+
+ public boolean isVermeerEnabled() {
+ String username = this.getUser();
+ String password = this.getCredentialPassword();
+ return vermeerService.isVermeerEnabled(username, password);
+ }
+
+ public String getGraphFromVermeer(String vermeer) {
+ return vermeer.split("-")[1];
+ }
+
+ private Map getVermeerGraphs(String graphspace,
+ boolean enable) {
+ if (enable) {
+ HugeClient client = this.authClient(graphspace, null);
+ List> graphinfos;
+ try {
+ graphinfos = HubbleUtil.uncheckedCast(
+ client.vermeer().getGraphsInfo().get("graphs"));
+ } catch (ServerException e) {
+ log.info("Failed to connect to the Vermeer service",
+ e.cause());
+ graphinfos = null;
+ }
+ if (graphinfos == null || graphinfos.size() == 0) {
+ // if vermeer data cleared
+ return ImmutableMap.of();
+ }
+ String prefix = graphspace + "-";
+ graphinfos = graphinfos.stream().filter((g) -> g.get("name")
+ .toString().startsWith(prefix))
+ .collect(Collectors.toList());
+ Map briefs = new HashMap<>(graphinfos.size());
+ for (Map info: graphinfos) {
+ String name = getGraphFromVermeer(info.get("name").toString());
+ Map brief = new HashMap<>();
+ brief.put("name", name);
+ brief.put("status", info.get("status").toString());
+
+ String lastLoadTime = info.get("update_time").toString();
+ // todo date format
+ brief.put("last_load_time", lastLoadTime);
+ briefs.put(name, brief);
+ }
+ return briefs;
+ }
+ return ImmutableMap.of();
+ }
+
+ private Map getVermeerGraph(String graphspace,
+ String graph) {
+ boolean enable = isVermeerEnabled();
+ if (enable) {
+ String prefix = graphspace + "-";
+ Map graphInfo = null;
+ try {
+ HugeClient client = this.authClient(graphspace, null);
+
+ graphInfo = HubbleUtil.uncheckedCast(
+ client.vermeer().getGraphInfoByName(prefix + graph)
+ .get("graph"));
+ } catch (ServerException e) {
+ // if dashboard enables vermeer but server sets wrong vermeer
+ // address, return null
+ log.info("Failed to connect to the Vermeer service",
+ e.cause());
+ return ImmutableMap.of();
+ }
+
+ if (graphInfo == null || graphInfo.isEmpty()) {
+ return ImmutableMap.of();
+ }
+
+ Map brief = new HashMap<>();
+
+ String name = getGraphFromVermeer(graphInfo.get("name").toString());
+ brief.put("name", name);
+ brief.put("status", graphInfo.get("status").toString());
+ String lastLoadTime = graphInfo.get("update_time").toString();
+ // todo date format
+ brief.put("last_load_time", lastLoadTime);
+ return brief;
+ }
+ return ImmutableMap.of();
+ }
+
+ @GetMapping("list")
+ public Object listGraphs(@PathVariable("graphspace") String graphspace) {
+ // Get list of authorized graphs
+ HugeClient client = this.authClient(graphspace, null);
+
+ boolean enable = isVermeerEnabled();
+ Map vermeerInfo = getVermeerGraphs(graphspace, enable);
+ List> sortedGraphs =
+ graphsService.sortedGraphsProfile(client, graphspace, "", "",
+ enable, vermeerInfo);
+
+ return ImmutableMap.of("graphs", sortedGraphs);
+ }
+
+ @GetMapping
+ public Object queryPage(@PathVariable("graphspace") String graphspace,
+ @RequestParam(name = "query", required = false,
+ defaultValue = "") String query,
+ @RequestParam(name = "create_time", required = false,
+ defaultValue = "") String createTime,
+ @RequestParam(name = "page_no", required = false,
+ defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false,
+ defaultValue = "10") int pageSize) {
+ HugeClient client = this.authClient(graphspace, null);
+ boolean enable = isVermeerEnabled();
+ Map vermeerInfo = getVermeerGraphs(graphspace, enable);
+ Object result = this.graphsService.queryPage(client, graphspace,
+ getUser(), query,
+ createTime, pageNo,
+ pageSize, enable,
+ vermeerInfo);
+
+ return result;
+ }
+
+ @GetMapping("{graph}/get")
+ public Object get(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph) {
+ Map vermeerInfo = getVermeerGraph(graphspace, graph);
+ Object result = graphsService.get(this.authClient(graphspace, graph),
+ graphspace, graph, vermeerInfo);
+ return result;
+ }
+
+ @PostMapping("{graph}/statistics")
+ public void postStatistics(
+ @PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph) {
+ HugeClient client = this.authClient(graphspace, graph);
+ graphsService.postStatistics(null, client, graphspace, graph);
+ }
+
+ @GetMapping("{graph}/statistics")
+ public GraphStatisticsEntity getStatistics(
+ @PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph) {
+ HugeClient client = this.authClient(graphspace, graph);
+ GraphStatisticsEntity result =
+ graphsService.getStatistics(client, graphspace,
+ graph);
+ return result;
+ }
+
+ @PostMapping("{graph}")
+ public Object create(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph,
+ @RequestBody(required = false)
+ GraphCreateEntity request) {
+ String nickname = request == null ? graph : request.getNickname();
+ String schema = request == null ? null : request.getSchema();
+ if (StringUtils.isEmpty(nickname)) {
+ nickname = graph;
+ }
+ return this.graphsService.create(this.authClient(graphspace, null),
+ nickname, graph, schema);
+ }
+
+ @PutMapping("{graph}")
+ public void update(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph,
+ @RequestBody GraphUpdateEntity request) {
+
+ this.graphsService.update(this.authClient(graphspace, null),
+ request.getNickname(), graph);
+ }
+
+ @DeleteMapping("{graph}")
+ public void delete(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph,
+ @RequestParam(value = "message", required = false,
+ defaultValue = "I'm sure to drop the graph")
+ String message) {
+
+ Map vermeer = getVermeerGraph(graphspace, graph);
+ if (!vermeer.isEmpty()) {
+ HugeClient client = this.authClient(null, null);
+ try {
+ vermeerService.deleteGraph(client, graphspace, graph);
+ } catch (Exception e) {
+ // HugeGraph-Common assert request with delete method returns
+ // 202 or 204
+ if (!e.getMessage().contains("deleted ok")) {
+ throw new ExternalException("delete vermeer graph error " +
+ "%s", e.getMessage());
+ }
+ }
+ }
+
+ this.graphsService.delete(this.authClient(graphspace, graph), graph,
+ message);
+
+ // Clean Local DB Data
+ executeHistoryService.deleteByGraph(graphspace, graph);
+ gremlinCollectionService.deleteByGraph(graphspace, graph);
+ jobManagerService.removeByGraph(graphspace, graph);
+ }
+
+ @PostMapping("{graph}/clear")
+ public void clearGraph(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph) {
+ this.graphsService.clearGraph(this.authClient(graphspace, graph), graph);
+ }
+
+ @PostMapping("{graph}/default")
+ public void setDefaultByCanonicalApi(
+ @PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph) {
+ this.graphsService.setDefault(this.authClient(graphspace, null), graph);
+ }
+
+ @DeleteMapping("{graph}/default")
+ public void unSetDefaultByCanonicalApi(
+ @PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph) {
+ this.graphsService.unSetDefault(this.authClient(graphspace, graph),
+ graph);
+ }
+
+ @GetMapping("default")
+ public Map getDefault(
+ @PathVariable("graphspace") String graphspace) {
+ return this.graphsService.getDefault(this.authClient(graphspace, null));
+ }
+
+ @PutMapping("{graph}/graph_read_mode")
+ public void graphReadMode(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph,
+ @RequestBody String mode) {
+ this.graphsService.graphReadMode(this.authClient(graphspace, graph),
+ graph, mode);
+ }
+
+ @GetMapping("{graph}/graph_read_mode")
+ public Map graphReadMode(
+ @PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph) {
+ Map status = new HashMap<>();
+ GraphReadMode graphMode = this.graphsService.graphReadMode(
+ this.authClient(graphspace, graph), graph);
+ if ("all".equals(graphMode.string())) {
+ status.put("status", "0");
+ } else if ("oltp_only".equals(graphMode.string())) {
+ status.put("status", "1");
+ }
+ return status;
+ }
+
+ @PostMapping("{graph}/clone")
+ public Object clone(@PathVariable("graphspace") String graphspace,
+ @PathVariable("graph") String graph,
+ @RequestBody GraphCloneEntity graphCloneEntity) {
+ return this.graphsService.clone(this.authClient(graphspace, graph),
+ graphCloneEntity.convertMap(graphspace,
+ graph));
+ }
+ //
+ //@Data
+ //@NoArgsConstructor
+ //@AllArgsConstructor
+ //@Builder
+ //private static class GraphCloneEntity {
+ // @JsonProperty("target_graphspace")
+ // public String targetGraphSpace;
+ //
+ // @JsonProperty("target_graph")
+ // public String targetGraph;
+ //
+ // public Map convertMap() {
+ // return Map.of("target_graphspace", targetGraphSpace,
+ // "target_graph", targetGraph);
+ // }
+ //}
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ingest/IngestController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ingest/IngestController.java
new file mode 100644
index 000000000..86809ccc7
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/ingest/IngestController.java
@@ -0,0 +1,842 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.ingest;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.common.Response;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
+import org.apache.hugegraph.entity.GraphConnection;
+import org.apache.hugegraph.entity.enums.FileMappingStatus;
+import org.apache.hugegraph.entity.enums.JobStatus;
+import org.apache.hugegraph.entity.enums.LoadStatus;
+import org.apache.hugegraph.entity.load.Datasource;
+import org.apache.hugegraph.entity.load.EdgeMapping;
+import org.apache.hugegraph.entity.load.FieldMappingItem;
+import org.apache.hugegraph.entity.load.FileMapping;
+import org.apache.hugegraph.entity.load.FileSetting;
+import org.apache.hugegraph.entity.load.JobManager;
+import org.apache.hugegraph.entity.load.LoadParameter;
+import org.apache.hugegraph.entity.load.LoadTask;
+import org.apache.hugegraph.entity.load.NullValues;
+import org.apache.hugegraph.entity.load.ValueMappingItem;
+import org.apache.hugegraph.entity.load.VertexMapping;
+import org.apache.hugegraph.exception.InternalException;
+import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.service.load.DatasourceService;
+import org.apache.hugegraph.service.load.FileMappingService;
+import org.apache.hugegraph.service.load.JobManagerService;
+import org.apache.hugegraph.service.load.LoadTaskService;
+import org.apache.hugegraph.util.Ex;
+import org.apache.hugegraph.util.FileUtil;
+import org.apache.hugegraph.util.HubbleUtil;
+import org.apache.hugegraph.util.UrlUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Log4j2
+@RestController
+@RequestMapping(Constant.API_VERSION + "ingest")
+public class IngestController extends BaseController {
+
+ @Autowired
+ private JobManagerService jobManagerService;
+ @Autowired
+ private LoadTaskService loadTaskService;
+ @Autowired
+ private DatasourceService datasourceService;
+ @Autowired
+ private FileMappingService fileMappingService;
+ @Autowired
+ private HugeConfig config;
+
+ // ===== Datasource endpoints =====
+
+ @GetMapping("/datasources/list")
+ public Response datasourceList(
+ @RequestParam(name = "query", required = false, defaultValue = "") String query,
+ @RequestParam(name = "page_no", required = false, defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false, defaultValue = "10") int pageSize) {
+ IPage page = datasourceService.list(pageNo, pageSize, query);
+ return Response.builder().status(Constant.STATUS_OK).data(page).build();
+ }
+
+ @GetMapping("/datasources/{id}")
+ public Response datasourceGet(@PathVariable("id") int id) {
+ Datasource ds = datasourceService.get(id);
+ if (ds == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Datasource not found: " + id).build();
+ }
+ return Response.builder().status(Constant.STATUS_OK).data(ds).build();
+ }
+
+ @PostMapping("/datasources")
+ public Response datasourceCreate(@RequestBody Datasource entity) {
+ datasourceService.save(entity);
+ return Response.builder().status(Constant.STATUS_OK)
+ .data(Map.of("datasource_id", entity.getId())).build();
+ }
+
+ @DeleteMapping("/datasources/{id}")
+ public Response datasourceDelete(@PathVariable("id") int id) {
+ datasourceService.remove(id);
+ return Response.builder().status(Constant.STATUS_OK).build();
+ }
+
+ @PostMapping("/datasources/delete")
+ public Response datasourceBatchDelete(@RequestBody List ids) {
+ datasourceService.removeBatch(ids);
+ return Response.builder().status(Constant.STATUS_OK).build();
+ }
+
+ @PostMapping("/files/upload")
+ public Response uploadFile(@RequestParam("file") MultipartFile file) {
+ Ex.check(file != null && !file.isEmpty(), "load.upload.file.cannot-be-empty");
+ String fileName = FilenameUtils.getName(file.getOriginalFilename());
+ Ex.check(StringUtils.isNotBlank(fileName) &&
+ fileName.equals(file.getOriginalFilename()),
+ "load.upload.file.name.invalid");
+ Long limit = this.config.get(HubbleOptions.UPLOAD_SINGLE_FILE_SIZE_LIMIT);
+ Ex.check(file.getSize() <= limit, "load.upload.file.exceed-single-size",
+ limit);
+
+ Path uploadRoot = Paths.get(this.config.get(HubbleOptions.UPLOAD_FILE_LOCATION))
+ .toAbsolutePath()
+ .normalize();
+ Path target = uploadRoot.resolve("ingest")
+ .resolve(UUID.randomUUID().toString())
+ .resolve(fileName)
+ .normalize();
+ Ex.check(target.startsWith(uploadRoot),
+ "load.upload.file.path.outside-root", target, uploadRoot);
+ try {
+ FileUtils.forceMkdirParent(target.toFile());
+ file.transferTo(target.toFile());
+ } catch (IOException e) {
+ throw new InternalException("Failed to save ingest upload file %s",
+ e, fileName);
+ }
+
+ Map data = new HashMap<>();
+ data.put("file", target.toString());
+ return Response.builder().status(Constant.STATUS_OK).data(data).build();
+ }
+
+ @GetMapping("/schemas")
+ public Response datasourceSchema(@RequestParam("datasource") int id) {
+ Datasource datasource = datasourceService.get(id);
+ if (datasource == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Datasource not found: " + id).build();
+ }
+
+ Map config = datasource.getDatasourceConfig();
+ if (!"FILE".equals(config.get("type"))) {
+ return Response.builder().status(Constant.STATUS_BAD_REQUEST)
+ .message("Only FILE datasource schema is supported")
+ .build();
+ }
+
+ List header = this.stringList(config.get("header"));
+ if (!header.isEmpty()) {
+ return Response.builder().status(Constant.STATUS_OK)
+ .data(header).build();
+ }
+
+ FileSetting setting = this.buildFileSetting(config, Collections.emptyList(),
+ false);
+ ColumnInfo columns = this.readColumns(this.requireUploadFile(config),
+ setting, false);
+ return Response.builder().status(Constant.STATUS_OK)
+ .data(columns.names).build();
+ }
+
+ @PostMapping("/jdbc/check")
+ public Response checkJdbc() {
+ return Response.builder().status(Constant.STATUS_BAD_REQUEST)
+ .message("JDBC datasource check is not supported locally")
+ .build();
+ }
+
+ // ===== Task endpoints (job_manager table) =====
+
+ @PostMapping("/tasks")
+ public Response createTask(@RequestBody IngestTaskRequest request) {
+ Ex.check(request != null, "common.param.cannot-be-null-or-empty",
+ "task");
+ Ex.check("ONCE".equals(request.taskScheduleType),
+ "Only ONCE ingest tasks are currently supported");
+ Datasource datasource = datasourceService.get(request.datasourceId);
+ Ex.check(datasource != null, "Datasource not found: %s",
+ request.datasourceId);
+ Map dsConfig = datasource.getDatasourceConfig();
+ Ex.check("FILE".equals(dsConfig.get("type")),
+ "Only FILE datasource ingest tasks are currently supported");
+
+ IngestStruct struct = request.firstStruct();
+ Map input = new HashMap<>(dsConfig);
+ if (struct.input != null) {
+ input.putAll(struct.input);
+ }
+
+ String graphSpace = request.ingestionOption.graphspace;
+ String graph = request.ingestionOption.graph;
+ File sourceFile = this.requireUploadFile(input);
+ long totalSize = sourceFile.length();
+ List header = this.stringList(input.get("header"));
+ boolean hasPhysicalHeader = !this.stringList(dsConfig.get("header"))
+ .isEmpty();
+ FileSetting setting = this.buildFileSetting(input, header,
+ hasPhysicalHeader);
+ if (setting.getColumnNames() == null ||
+ setting.getColumnNames().isEmpty()) {
+ ColumnInfo columns = this.readColumns(sourceFile, setting,
+ hasPhysicalHeader);
+ setting.setColumnNames(columns.names);
+ setting.setColumnValues(columns.values);
+ }
+
+ JobManager job = JobManager.builder()
+ .graphSpace(graphSpace)
+ .graph(graph)
+ .jobName(request.taskName)
+ .jobSize(totalSize)
+ .jobStatus(JobStatus.SETTING)
+ .createTime(HubbleUtil.nowDate())
+ .updateTime(HubbleUtil.nowDate())
+ .build();
+ FileMapping mapping = new FileMapping(graphSpace, graph,
+ sourceFile.getName(),
+ sourceFile.getPath());
+ mapping.setFileStatus(FileMappingStatus.COMPLETED);
+ mapping.setTotalSize(totalSize);
+ mapping.setTotalLines(FileUtil.countLines(sourceFile.getPath()));
+ mapping.setFileSetting(setting);
+ mapping.setLoadParameter(new LoadParameter());
+ mapping.setVertexMappings(this.vertexMappings(struct.vertices));
+ mapping.setEdgeMappings(this.edgeMappings(struct.edges));
+
+ boolean started = false;
+ try {
+ this.jobManagerService.save(job);
+ mapping.setJobId(job.getId());
+ this.fileMappingService.save(mapping);
+
+ GraphConnection connection = this.graphConnection(graphSpace, graph);
+ HugeClient client = this.authClient(graphSpace, graph);
+ LoadTask task = this.loadTaskService.start(connection, mapping,
+ client);
+ started = true;
+ Map data = new HashMap<>();
+ data.put("task_id", job.getId());
+ data.put("job_id", task.getId());
+ return Response.builder().status(Constant.STATUS_OK)
+ .data(data).build();
+ } finally {
+ if (job.getId() != null) {
+ job.setJobStatus(started ? JobStatus.LOADING :
+ JobStatus.FAILED);
+ job.setUpdateTime(HubbleUtil.nowDate());
+ this.jobManagerService.update(job);
+ }
+ }
+ }
+
+ @GetMapping("/tasks/list")
+ public Response taskList(
+ @RequestParam(name = "query", required = false, defaultValue = "") String query,
+ @RequestParam(name = "page_no", required = false, defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false, defaultValue = "10") int pageSize) {
+
+ // list all jobs across all graphspaces - use empty strings to get all
+ // We need to query without graphspace/graph filter for the ingest view
+ IPage page = jobManagerService.listAll(pageNo, pageSize, query);
+
+ IPage result = page.convert(job -> {
+ TaskVO vo = new TaskVO();
+ vo.taskId = job.getId();
+ vo.taskName = job.getJobName();
+ vo.taskScheduleType = "ONCE";
+ vo.taskScheduleStatus = toScheduleStatus(job.getJobStatus());
+ vo.createTime = job.getCreateTime();
+ vo.creator = "";
+
+ // Build ingestion_option from job fields
+ Map option = new HashMap<>();
+ option.put("graphspace", job.getGraphSpace());
+ option.put("graph", job.getGraph());
+ vo.ingestionOption = option;
+
+ // Build ingestion_mapping with structs from file mappings
+ List tasks = loadTaskService.taskListByJob(job.getId());
+ List> structs = tasks.stream().map(t -> {
+ Map struct = new HashMap<>();
+ Map input = new HashMap<>();
+ input.put("type", "FILE");
+ input.put("path", t.getFileName());
+ struct.put("input", input);
+ return struct;
+ }).collect(Collectors.toList());
+ Map mapping = new HashMap<>();
+ mapping.put("structs", structs);
+ vo.ingestionMapping = mapping;
+
+ // Build last_metrics from latest load task
+ if (!tasks.isEmpty()) {
+ LoadTask latest = tasks.get(tasks.size() - 1);
+ Map metrics = new HashMap<>();
+ metrics.put("status", latest.getStatus().name());
+ metrics.put("load_progress", latest.getLoadProgress());
+ vo.lastMetrics = metrics;
+ }
+
+ // Build job_summary
+ JobSummaryVO summary = new JobSummaryVO();
+ for (LoadTask t : tasks) {
+ if (t.getStatus() == LoadStatus.SUCCEED) {
+ summary.successCount++;
+ } else if (t.getStatus() == LoadStatus.FAILED) {
+ summary.failedCount++;
+ } else if (t.getStatus().inRunning()) {
+ summary.runningCount++;
+ }
+ }
+ vo.jobSummary = summary;
+
+ return vo;
+ });
+
+ return Response.builder().status(Constant.STATUS_OK).data(result).build();
+ }
+
+ @GetMapping("/tasks/{id}")
+ public Response taskDetail(@PathVariable("id") int id) {
+ JobManager job = jobManagerService.get(id);
+ if (job == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Task not found: " + id).build();
+ }
+ return Response.builder().status(Constant.STATUS_OK).data(job).build();
+ }
+
+ @DeleteMapping("/tasks/{id}")
+ public Response deleteTask(@PathVariable("id") int id) {
+ jobManagerService.remove(id);
+ return Response.builder().status(Constant.STATUS_OK).build();
+ }
+
+ @PutMapping("/tasks/{id}/enable")
+ public Response enableTask(@PathVariable("id") int id) {
+ JobManager job = jobManagerService.get(id);
+ if (job == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Task not found: " + id).build();
+ }
+ job.setJobStatus(JobStatus.DEFAULT);
+ jobManagerService.update(job);
+ return Response.builder().status(Constant.STATUS_OK).build();
+ }
+
+ @PutMapping("/tasks/{id}/disable")
+ public Response disableTask(@PathVariable("id") int id) {
+ JobManager job = jobManagerService.get(id);
+ if (job == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Task not found: " + id).build();
+ }
+ job.setJobStatus(JobStatus.FAILED);
+ jobManagerService.update(job);
+ return Response.builder().status(Constant.STATUS_OK).build();
+ }
+
+ // ===== Job endpoints (load_task table) =====
+
+ @GetMapping("/jobs/list")
+ public Response jobList(
+ @RequestParam(name = "taskid", required = false, defaultValue = "0") int taskId,
+ @RequestParam(name = "page_no", required = false, defaultValue = "1") int pageNo,
+ @RequestParam(name = "page_size", required = false, defaultValue = "10") int pageSize) {
+
+ List tasks = loadTaskService.taskListByJob(taskId);
+
+ // Manual pagination
+ int total = tasks.size();
+ int fromIndex = Math.min((pageNo - 1) * pageSize, total);
+ int toIndex = Math.min(fromIndex + pageSize, total);
+ List pageData = tasks.subList(fromIndex, toIndex);
+
+ List records = pageData.stream().map(t -> {
+ JobVO vo = new JobVO();
+ vo.jobId = t.getId();
+ vo.taskId = t.getJobId();
+ vo.jobStatus = t.getStatus().name();
+ vo.jobMessage = "";
+ vo.createTime = t.getCreateTime();
+
+ JobMetricsVO metrics = new JobMetricsVO();
+ metrics.totalCount = t.getFileReadLines() != null ? t.getFileReadLines() : 0L;
+ long durationMs = t.getDuration() != null ? t.getDuration() : 0L;
+ long durationSec = durationMs > 0 ?
+ Math.max(1L, durationMs / 1000L) : 1L;
+ metrics.avgRate = durationMs > 0 ? (float) metrics.totalCount / durationSec : 0f;
+ metrics.curRate = t.getStatus().inRunning() ? metrics.avgRate : 0f;
+ metrics.totalTime = durationMs;
+ vo.jobMetrics = metrics;
+
+ return vo;
+ }).collect(Collectors.toList());
+
+ Map result = new HashMap<>();
+ result.put("records", records);
+ result.put("total", total);
+ result.put("size", pageSize);
+ result.put("current", pageNo);
+
+ return Response.builder().status(Constant.STATUS_OK).data(result).build();
+ }
+
+ @GetMapping("/jobs/{id}")
+ public Response jobDetail(@PathVariable("id") int id) {
+ LoadTask task = loadTaskService.get(id);
+ if (task == null) {
+ return Response.builder().status(Constant.STATUS_NOT_FOUND)
+ .message("Job not found: " + id).build();
+ }
+ return Response.builder().status(Constant.STATUS_OK).data(task).build();
+ }
+
+ @DeleteMapping("/jobs/{id}")
+ public Response deleteJob(@PathVariable("id") int id) {
+ loadTaskService.remove(id);
+ return Response.builder().status(Constant.STATUS_OK).build();
+ }
+
+ // ===== Metrics =====
+
+ @GetMapping("/metrics/task")
+ public Response metricsTask() {
+ List all = jobManagerService.listAll();
+ all.forEach(jobManagerService::refreshStatus);
+
+ long runningOnce = 0;
+ long runningCron = 0;
+ long runningRealtime = 0;
+ long todoOnce = 0;
+ long todoCron = 0;
+ long todoRealtime = 0;
+
+ for (JobManager job : all) {
+ if (job.getJobStatus() == JobStatus.LOADING) {
+ runningOnce++;
+ } else if (job.getJobStatus() == JobStatus.DEFAULT ||
+ job.getJobStatus() == JobStatus.SETTING) {
+ todoOnce++;
+ }
+ }
+
+ Map todo = new HashMap<>();
+ todo.put("ONCE", todoOnce);
+ todo.put("CRON", todoCron);
+ todo.put("REALTIME", todoRealtime);
+
+ Map running = new HashMap<>();
+ running.put("ONCE", runningOnce);
+ running.put("CRON", runningCron);
+ running.put("REALTIME", runningRealtime);
+
+ Map data = new HashMap<>();
+ data.put("total_realtime_size", 0);
+ data.put("total_other_size", all.size());
+ data.put("todo", todo);
+ data.put("running", running);
+
+ return Response.builder().status(Constant.STATUS_OK).data(data).build();
+ }
+
+ // ===== Helpers =====
+
+ private GraphConnection graphConnection(String graphSpace, String graph) {
+ GraphConnection connection = new GraphConnection();
+ connection.setCluster(config.get(HubbleOptions.PD_CLUSTER));
+ connection.setRouteType(config.get(HubbleOptions.ROUTE_TYPE));
+ connection.setPdPeers(config.get(HubbleOptions.PD_PEERS));
+ connection.setGraphSpace(graphSpace);
+ connection.setGraph(graph);
+ connection.setToken(this.getToken());
+ connection.setUsername(this.getUser());
+ connection.setPassword(this.getCredentialPassword());
+ if (!config.get(HubbleOptions.PD_ENABLED)) {
+ UrlUtil.Host host = UrlUtil.parseHost(config.get(
+ HubbleOptions.SERVER_URL));
+ connection.setProtocol(host.getScheme());
+ connection.setHost(host.getHost());
+ connection.setPort(host.getPort());
+ }
+ return connection;
+ }
+
+ private File requireUploadFile(Map config) {
+ String path = this.string(config.get("path"));
+ Ex.check(StringUtils.isNotBlank(path),
+ "Datasource file path is empty");
+ return this.fileMappingService.requirePathUnderUploadRoot(path);
+ }
+
+ private FileSetting buildFileSetting(Map input,
+ List header,
+ boolean hasPhysicalHeader) {
+ FileSetting setting = new FileSetting();
+ setting.setHasHeader(hasPhysicalHeader);
+ if (!header.isEmpty()) {
+ setting.setColumnNames(header);
+ }
+ setting.setFormat(this.stringOrDefault(input.get("format"), "CSV"));
+ setting.setDelimiter(this.stringOrDefault(input.get("delimiter"), ","));
+ setting.setCharset(this.stringOrDefault(input.get("charset"), "UTF-8"));
+ setting.setDateFormat(this.stringOrDefault(input.get("date_format"),
+ "yyyy-MM-dd HH:mm:ss"));
+ setting.setTimeZone(this.stringOrDefault(input.get("time_zone"),
+ "GMT+8"));
+ Object skippedLine = input.get("skipped_line");
+ if (skippedLine instanceof Map) {
+ setting.setSkippedLine(this.stringOrDefault(
+ ((Map, ?>) skippedLine).get("regex"), "(^#|^//).*"));
+ } else {
+ setting.setSkippedLine(this.stringOrDefault(skippedLine,
+ "(^#|^//).*"));
+ }
+ setting.changeFormatIfNeeded();
+ return setting;
+ }
+
+ private ColumnInfo readColumns(File file, FileSetting setting,
+ boolean hasPhysicalHeader) {
+ try (BufferedReader reader = Files.newBufferedReader(file.toPath())) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.matches(setting.getSkippedLine())) {
+ break;
+ }
+ }
+ Ex.check(line != null, "The file has no data line can treat as header");
+
+ List firstLine = this.splitLine(line, setting.getDelimiter());
+ if (hasPhysicalHeader) {
+ String sample = reader.readLine();
+ List values = sample == null ? Collections.emptyList() :
+ this.splitLine(sample,
+ setting.getDelimiter());
+ return new ColumnInfo(firstLine, values);
+ }
+
+ List names = new ArrayList<>();
+ for (int i = 1; i <= firstLine.size(); i++) {
+ names.add("col-" + i);
+ }
+ return new ColumnInfo(names, firstLine);
+ } catch (IOException e) {
+ throw new InternalException("Failed to read columns from file %s",
+ e, file);
+ }
+ }
+
+ private List splitLine(String line, String delimiter) {
+ if (line == null) {
+ return Collections.emptyList();
+ }
+ String[] values = StringUtils.split(line, delimiter);
+ List list = new ArrayList<>();
+ if (values != null) {
+ Collections.addAll(list, values);
+ }
+ return list;
+ }
+
+ private Set vertexMappings(List> raw) {
+ Set mappings = new LinkedHashSet<>();
+ if (raw == null) {
+ return mappings;
+ }
+ for (int i = 0; i < raw.size(); i++) {
+ Map item = raw.get(i);
+ List idFields = this.stringList(item.get("id_fields"));
+ String id = this.string(item.get("id"));
+ if (idFields.isEmpty() && StringUtils.isNotBlank(id)) {
+ idFields = Collections.singletonList(id);
+ }
+ VertexMapping mapping = VertexMapping.builder()
+ .idFields(idFields)
+ .build();
+ this.fillElementMapping(mapping, item, "vertex-" + i);
+ mappings.add(mapping);
+ }
+ return mappings;
+ }
+
+ private Set edgeMappings(List> raw) {
+ Set mappings = new LinkedHashSet<>();
+ if (raw == null) {
+ return mappings;
+ }
+ for (int i = 0; i < raw.size(); i++) {
+ Map item = raw.get(i);
+ EdgeMapping mapping = EdgeMapping.builder()
+ .sourceFields(this.stringList(
+ item.get("source")))
+ .targetFields(this.stringList(
+ item.get("target")))
+ .build();
+ this.fillElementMapping(mapping, item, "edge-" + i);
+ mappings.add(mapping);
+ }
+ return mappings;
+ }
+
+ private void fillElementMapping(
+ org.apache.hugegraph.entity.load.ElementMapping mapping,
+ Map item, String id) {
+ mapping.setId(id);
+ mapping.setLabel(this.string(item.get("label")));
+ mapping.setFieldMappings(this.fieldMappings(
+ item.get("field_mapping")));
+ mapping.setValueMappings(this.valueMappings(
+ item.get("value_mapping")));
+ mapping.setNullValues(this.nullValues(item.get("null_values")));
+ }
+
+ private List fieldMappings(Object raw) {
+ if (!(raw instanceof Map)) {
+ return Collections.emptyList();
+ }
+ List mappings = new ArrayList<>();
+ for (Map.Entry, ?> entry : ((Map, ?>) raw).entrySet()) {
+ mappings.add(FieldMappingItem.builder()
+ .columnName(this.string(entry.getKey()))
+ .mappedName(this.string(entry.getValue()))
+ .build());
+ }
+ return mappings;
+ }
+
+ private List valueMappings(Object raw) {
+ if (!(raw instanceof Map)) {
+ return Collections.emptyList();
+ }
+ List mappings = new ArrayList<>();
+ for (Map.Entry, ?> entry : ((Map, ?>) raw).entrySet()) {
+ List values = new ArrayList<>();
+ if (entry.getValue() instanceof Map) {
+ for (Map.Entry, ?> valueEntry :
+ ((Map, ?>) entry.getValue()).entrySet()) {
+ values.add(ValueMappingItem.ValueItem.builder()
+ .columnValue(this.string(valueEntry.getKey()))
+ .mappedValue(this.string(valueEntry.getValue()))
+ .build());
+ }
+ }
+ mappings.add(ValueMappingItem.builder()
+ .columnName(this.string(entry.getKey()))
+ .values(values)
+ .build());
+ }
+ return mappings;
+ }
+
+ private NullValues nullValues(Object raw) {
+ Set checked = new LinkedHashSet<>();
+ Set customized = new LinkedHashSet<>();
+ if (raw instanceof Map) {
+ checked.addAll(this.objectSet(((Map, ?>) raw).get("checked")));
+ customized.addAll(this.objectSet(((Map, ?>) raw).get(
+ "customized")));
+ } else {
+ checked.addAll(this.objectSet(raw));
+ }
+ return NullValues.builder()
+ .checked(checked)
+ .customized(customized)
+ .build();
+ }
+
+ private Set objectSet(Object raw) {
+ Set values = new LinkedHashSet<>();
+ if (raw instanceof Iterable) {
+ for (Object item : (Iterable>) raw) {
+ values.add(item);
+ }
+ } else if (raw != null) {
+ values.add(raw);
+ }
+ return values;
+ }
+
+ private List stringList(Object raw) {
+ if (raw instanceof Iterable) {
+ List values = new ArrayList<>();
+ for (Object item : (Iterable>) raw) {
+ values.add(this.string(item));
+ }
+ return values;
+ }
+ if (raw == null || StringUtils.isBlank(raw.toString())) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(raw.toString());
+ }
+
+ private String stringOrDefault(Object raw, String defaultValue) {
+ String value = this.string(raw);
+ return StringUtils.isBlank(value) ? defaultValue : value;
+ }
+
+ private String string(Object raw) {
+ return raw == null ? null : raw.toString();
+ }
+
+ private String toScheduleStatus(JobStatus status) {
+ if (status == JobStatus.FAILED) {
+ return "DISABLE";
+ }
+ return "ENABLE";
+ }
+
+ // ===== VOs =====
+
+ @Data
+ static class TaskVO {
+ @JsonProperty("task_id") Integer taskId;
+ @JsonProperty("task_name") String taskName;
+ @JsonProperty("task_schedule_type") String taskScheduleType;
+ @JsonProperty("task_schedule_status") String taskScheduleStatus;
+ @JsonProperty("ingestion_option") Object ingestionOption;
+ @JsonProperty("ingestion_mapping") Object ingestionMapping;
+ @JsonProperty("last_metrics") Object lastMetrics;
+ @JsonProperty("job_summary") JobSummaryVO jobSummary;
+ @JsonProperty("create_time") Date createTime;
+ @JsonProperty("creator") String creator;
+ }
+
+ @Data
+ static class JobSummaryVO {
+ @JsonProperty("success_count") int successCount;
+ @JsonProperty("failed_count") int failedCount;
+ @JsonProperty("running_count") int runningCount;
+ }
+
+ @Data
+ static class JobVO {
+ @JsonProperty("job_id") Integer jobId;
+ @JsonProperty("task_id") Integer taskId;
+ @JsonProperty("job_status") String jobStatus;
+ @JsonProperty("job_message") String jobMessage;
+ @JsonProperty("job_metrics") JobMetricsVO jobMetrics;
+ @JsonProperty("create_time") Date createTime;
+ }
+
+ @Data
+ static class JobMetricsVO {
+ @JsonProperty("total_count") long totalCount;
+ @JsonProperty("avg_rate") float avgRate;
+ @JsonProperty("cur_rate") float curRate;
+ @JsonProperty("total_time") long totalTime;
+ }
+
+ @Data
+ static class IngestTaskRequest {
+ @JsonProperty("task_name") String taskName;
+ @JsonProperty("datasource_id") Integer datasourceId;
+ @JsonProperty("task_schedule_type") String taskScheduleType;
+ @JsonProperty("ingestion_option") IngestionOption ingestionOption;
+ @JsonProperty("ingestion_mapping") IngestionMapping ingestionMapping;
+
+ IngestStruct firstStruct() {
+ Ex.check(this.ingestionOption != null,
+ "common.param.cannot-be-null-or-empty",
+ "ingestion_option");
+ Ex.check(this.ingestionMapping != null &&
+ this.ingestionMapping.structs != null &&
+ !this.ingestionMapping.structs.isEmpty(),
+ "common.param.cannot-be-null-or-empty",
+ "ingestion_mapping.structs");
+ return this.ingestionMapping.structs.get(0);
+ }
+ }
+
+ @Data
+ static class IngestionOption {
+ @JsonProperty("graphspace") String graphspace;
+ @JsonProperty("graph") String graph;
+ }
+
+ @Data
+ static class IngestionMapping {
+ @JsonProperty("structs") List structs;
+ }
+
+ @Data
+ static class IngestStruct {
+ @JsonProperty("input") Map input;
+ @JsonProperty("vertices") List> vertices;
+ @JsonProperty("edges") List> edges;
+ }
+
+ static class ColumnInfo {
+ private final List names;
+ private final List values;
+
+ ColumnInfo(List names, List values) {
+ this.names = names;
+ this.values = values;
+ }
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
new file mode 100644
index 000000000..8c1695fe0
--- /dev/null
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/langchain/LangChainController.java
@@ -0,0 +1,785 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.hugegraph.controller.langchain;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.hugegraph.controller.query.GremlinController;
+import org.apache.hugegraph.driver.SchemaManager;
+import org.apache.hugegraph.entity.query.GremlinQuery;
+import org.apache.hugegraph.entity.query.JsonView;
+import org.apache.hugegraph.service.query.QueryService;
+import org.apache.hugegraph.structure.schema.EdgeLabel;
+import org.apache.hugegraph.structure.schema.VertexLabel;
+import org.apache.hugegraph.config.ConfigOption;
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.options.HubbleOptions;
+import org.apache.hugegraph.util.Ex;
+import org.apache.hugegraph.util.JsonUtil;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.compress.utils.Lists;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hugegraph.util.E;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import org.apache.hugegraph.common.Constant;
+import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * langchain controller
+ */
+@Log4j2
+@RestController
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs/{graph}")
+public class LangChainController extends BaseController {
+
+ private static final String DEFAULT_PYTHON_FILE = "langchaincode/excute_langchain.py";
+ private static final String DEFAULT_PYTHON_SCRIPT = "excute_langchain.py";
+
+ private static final String G_V = "g.v";
+ private static final String G_E = "g.e";
+
+ private static final String WENXIN_4_MODEL = "wenxin4";
+ private static final String GPT_4_MODEL = "gpt4";
+
+ private static final List DEFAULT_MODEL = Arrays.asList(WENXIN_4_MODEL, GPT_4_MODEL);
+
+ @Autowired
+ private QueryService queryService;
+
+ @Autowired
+ private HugeConfig config;
+
+ @PostMapping("langchain")
+ public Object langchain(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody RequestLangChainParams requestLangChainParams) {
+ E.checkNotNull(requestLangChainParams, "params must not be null");
+ log.info("LangChainController langchain params:{}");
+ this.checkParams(requestLangChainParams);
+ this.checkModelParams(requestLangChainParams);
+ this.checkUserParam(requestLangChainParams);
+
+ this.tryLogin(graphSpace, graph,
+ requestLangChainParams.userName, requestLangChainParams.password);
+
+ return this.langChainQuery(graphSpace, graph, requestLangChainParams);
+ }
+
+ @PostMapping("langchain/hubble")
+ public Object langchainHubble(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody RequestLangChainParams requestLangChainParams) {
+ E.checkNotNull(requestLangChainParams, "params must not be null");
+ log.info("LangChainController langchain request model:{} file:{}",
+ requestLangChainParams.model, requestLangChainParams.fileName);
+ this.checkParams(requestLangChainParams);
+
+ return this.langChainQuery(graphSpace, graph, requestLangChainParams);
+ }
+
+ private ResponseLangChain langChainQuery(String graphSpace, String graph,
+ RequestLangChainParams requestLangChainParams) {
+ HugeClient client = this.authClient(graphSpace, graph);
+ SchemaManager schemaManager = client.schema();
+ List vertexLabels = schemaManager.getVertexLabels();
+ List edgeLabels = schemaManager.getEdgeLabels();
+ String schema = JsonUtil.toJson(this.getBigModelSchema(vertexLabels, edgeLabels));
+ log.info("langchain schema:{}", schema);
+
+ String filePath = this.resolvePythonScriptPath(
+ requestLangChainParams.fileName);
+ log.info("LangChainController filePath:{}", filePath);
+
+ List result =
+ this.excutePythonRuntime(requestLangChainParams.pythonPath,
+ filePath, requestLangChainParams.query,
+ requestLangChainParams.openKey, schema,
+ requestLangChainParams.model,
+ requestLangChainParams.ernieClientId,
+ requestLangChainParams.ernieClientSecret);
+ if (CollectionUtils.isEmpty(result)) {
+ return this.generateResponseLangChain(requestLangChainParams.query,
+ "LangChain not generate gremlin");
+ } else {
+ return this.generateResponseLangChain(requestLangChainParams.query,
+ result.get(result.size() - 1));
+ }
+ }
+
+ @PostMapping("langchain/schema")
+ public Object langchainSchema(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody RequestLangChainParams requestLangChainParams) {
+ E.checkNotNull(requestLangChainParams, "params must not be null");
+ log.info("LangChainController langchain schema request username:{}",
+ requestLangChainParams.userName);
+ this.checkUserParam(requestLangChainParams);
+
+ this.tryLogin(graphSpace, graph,
+ requestLangChainParams.userName, requestLangChainParams.password);
+
+ HugeClient client = this.authClient(graphSpace, graph);
+ SchemaManager schemaManager = client.schema();
+ List vertexLabels = schemaManager.getVertexLabels();
+ List edgeLabels = schemaManager.getEdgeLabels();
+ HashMap schema = this.getBigModelSchema(vertexLabels, edgeLabels);
+ return schema;
+ }
+
+ @PostMapping("gremlin")
+ public Object gremlin(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody RequestLangChainParams requestLangChainParams) {
+ E.checkNotNull(requestLangChainParams, "params must not be null");
+ GremlinQuery query = new GremlinQuery();
+ query.setContent(requestLangChainParams.query);
+ this.checkParamsValid(query);
+ this.checkUserParam(requestLangChainParams);
+
+ this.tryLogin(graphSpace, graph,
+ requestLangChainParams.userName, requestLangChainParams.password);
+
+ try {
+ HugeClient client = this.authClient(graphSpace, graph);
+ JsonView result =
+ this.queryService.executeSingleGremlinQuery(client, query);
+ return result.getData();
+ } catch (Throwable e) {
+ throw e;
+ }
+ }
+
+ @PostMapping("langchain_no_schema")
+ public Object langchainNoSchema(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @RequestBody RequestLangChainParams requestLangChainParams) {
+ E.checkNotNull(requestLangChainParams, "params must not be null");
+ log.info("LangChainController langchain no schema request model:{} file:{}",
+ requestLangChainParams.model, requestLangChainParams.fileName);
+
+ this.checkParams(requestLangChainParams);
+ this.checkModelParams(requestLangChainParams);
+
+ String filePath = this.resolvePythonScriptPath(
+ requestLangChainParams.fileName);
+ log.info("LangChainController filePath:{}", filePath);
+
+ List result =
+ this.excutePythonByProcessBuilder(
+ requestLangChainParams.pythonPath, filePath,
+ requestLangChainParams.query,
+ requestLangChainParams.openKey,
+ requestLangChainParams.graphSchema,
+ requestLangChainParams.model,
+ requestLangChainParams.ernieClientId,
+ requestLangChainParams.ernieClientSecret);
+ if (CollectionUtils.isEmpty(result)) {
+ return this.generateResponseLangChain(requestLangChainParams.query,
+ "LangChain not generate gremlin");
+ } else {
+ return this.generateResponseLangChain(requestLangChainParams.query,
+ result.get(result.size() - 1));
+ }
+ }
+
+ private void tryLogin(String graphSpace, String graph,
+ String username, String password) {
+ log.info("Attempting to login username:{}", username);
+
+ E.checkNotNull(username, "username cannot be null");
+ E.checkNotNull(password, "password cannot be null");
+ String token = this.getToken();
+ if (StringUtils.isNotEmpty(token)) {
+ log.info("Attempting to login token exist, username:{}", username);
+ return;
+ }
+ if (Objects.isNull(this.getToken())) {
+ log.error("Attempting to login failed, username:{}", username);
+ throw new IllegalStateException("login failed");
+ }
+ }
+
+ /**
+ *
+ * @param pythonPath
+ * @param pythonScriptPath
+ * @param query
+ * @param openKey
+ * @param graphSchema
+ * @return
+ */
+ private List excutePythonRuntime(String pythonPath,
+ String pythonScriptPath,
+ String query,
+ String openKey,
+ String graphSchema,
+ String model,
+ String ernieClientId,
+ String ernieClientSecret) {
+ String[] args1 = this.getExcuteArgs(pythonPath, pythonScriptPath,
+ query, openKey, graphSchema,
+ model, ernieClientId,
+ ernieClientSecret);
+ return this.executePythonProcess(args1, model,
+ this.secretValues(openKey,
+ ernieClientSecret));
+ }
+
+ /**
+ * 使用ProcessBuilder执行python脚本
+ * @param pythonPath
+ * @param pythonScriptPath
+ * @param query
+ * @param openKey
+ * @param graphSchema
+ * @return
+ */
+ private List excutePythonByProcessBuilder(String pythonPath,
+ String pythonScriptPath,
+ String query,
+ String openKey,
+ String graphSchema,
+ String model,
+ String ernieClientId,
+ String ernieClientSecret) {
+ String[] args1 = this.getExcuteArgs(pythonPath, pythonScriptPath,
+ query, openKey, graphSchema,
+ model, ernieClientId,
+ ernieClientSecret);
+ return this.executePythonProcess(args1, model,
+ this.secretValues(openKey,
+ ernieClientSecret));
+ }
+
+ private String resolvePythonScriptPath(String fileName) {
+ E.checkArgument(StringUtils.isNotBlank(fileName),
+ "fileName must not be blank");
+ Path requested = Paths.get(fileName).normalize();
+ E.checkArgument(!requested.isAbsolute() &&
+ requested.getFileName() != null &&
+ (requested.getNameCount() == 1 ||
+ DEFAULT_PYTHON_FILE.equals(requested.toString())),
+ "python file is not allowed");
+
+ String scriptName = requested.getFileName().toString();
+ List allowlist =
+ this.configValue(HubbleOptions.LANGCHAIN_SCRIPT_ALLOWLIST);
+
+ String safeScriptName = null;
+ for (String allowed : allowlist) {
+ if (allowed.equals(scriptName)) {
+ safeScriptName = allowed;
+ break;
+ }
+ }
+ if (safeScriptName == null) {
+ throw new IllegalArgumentException("python file is not allowed");
+ }
+
+ Path root = Paths.get(this.configValue(HubbleOptions.LANGCHAIN_SCRIPT_DIR))
+ .normalize();
+ if (!root.toFile().isDirectory()) {
+ throw new IllegalArgumentException("langchain script dir not exist");
+ }
+ Path script = root.resolve(safeScriptName).normalize();
+ if (!script.startsWith(root)) {
+ throw new IllegalArgumentException("python file is not allowed");
+ }
+ if (!script.toFile().exists()) {
+ throw new IllegalArgumentException("python file not exist");
+ }
+ return script.toString();
+ }
+
+ private List executePythonProcess(String[] args, String model,
+ Set secrets) {
+ log.info("lang chain execute python command: {}",
+ this.sanitizeCommandForLog(args));
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ pb.redirectErrorStream(false);
+
+ List lineList = new ArrayList<>();
+ List errorList = new ArrayList<>();
+ Process process;
+ try {
+ process = pb.start();
+ Thread stdout = this.readProcessStream(process.getInputStream(),
+ lineList, "stdout", secrets);
+ Thread stderr = this.readProcessStream(process.getErrorStream(),
+ errorList, "stderr", secrets);
+ int timeout = this.configValue(HubbleOptions.LANGCHAIN_EXECUTE_TIMEOUT);
+ if (!process.waitFor(timeout, TimeUnit.SECONDS)) {
+ process.destroyForcibly();
+ throw new IllegalStateException("LangChain python execution timeout");
+ }
+ stdout.join(TimeUnit.SECONDS.toMillis(1L));
+ stderr.join(TimeUnit.SECONDS.toMillis(1L));
+
+ int exitCode = process.exitValue();
+ if (exitCode != 0) {
+ log.error("LangChain python stderr:{}",
+ this.redactLines(errorList, secrets));
+ throw new IllegalStateException(
+ "LangChain python exited with code " + exitCode);
+ }
+
+ if (!this.judgeResultSuccess(lineList, model)) {
+ List redactedLines = this.redactLines(lineList, secrets);
+ this.calculateError(redactedLines, model);
+ log.error("excutePython lineList:{}", redactedLines);
+ lineList.clear();
+ } else {
+ lineList = getGremlinResults(lineList, model);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("LangChain python execution failed", e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("LangChain python execution interrupted", e);
+ }
+ return lineList;
+ }
+
+ private Thread readProcessStream(InputStream stream, List lines,
+ String streamName, Set secrets) {
+ Thread thread = new Thread(() -> {
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(stream, StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ lines.add(line);
+ log.info("execute {} ret:{}", streamName,
+ this.redact(line, secrets));
+ }
+ } catch (IOException e) {
+ log.error("Read LangChain process {} failed", streamName, e);
+ }
+ }, "langchain-" + streamName + "-reader");
+ thread.setDaemon(true);
+ thread.start();
+ return thread;
+ }
+
+ private Set secretValues(String openKey, String ernieClientSecret) {
+ Set secrets = new java.util.HashSet<>();
+ if (StringUtils.isNotEmpty(openKey)) {
+ secrets.add(openKey);
+ }
+ if (StringUtils.isNotEmpty(ernieClientSecret)) {
+ secrets.add(ernieClientSecret);
+ }
+ return secrets;
+ }
+
+ private List redactLines(List lines, Set secrets) {
+ List redacted = new ArrayList<>(lines.size());
+ for (String line : lines) {
+ redacted.add(this.redact(line, secrets));
+ }
+ return redacted;
+ }
+
+ private String redact(String value, Set secrets) {
+ String redacted = value;
+ for (String secret : secrets) {
+ redacted = StringUtils.replace(redacted, secret, "******");
+ }
+ return redacted;
+ }
+
+ private String sanitizeCommandForLog(String[] args) {
+ List sanitized = new ArrayList<>();
+ for (int i = 0; i < args.length; i++) {
+ sanitized.add(args[i]);
+ if ("--open_key".equals(args[i]) ||
+ "--ernie_client_secret".equals(args[i])) {
+ if (i + 1 < args.length) {
+ sanitized.add("******");
+ i++;
+ }
+ }
+ }
+ return String.join(" ", sanitized);
+ }
+
+ private List getGremlinResults(List lineList, String model) {
+ if (GPT_4_MODEL.equals(model)) {
+ return Arrays.asList(this.getGpt4GremlinResults(lineList));
+ } else if (WENXIN_4_MODEL.equals(model)) {
+ return Arrays.asList(this.getWenXin4GremlinResults(lineList));
+ }
+ return Collections.emptyList();
+ }
+
+ private String getGpt4GremlinResults(List lineList) {
+ for (String line : lineList) {
+ String tmp = line.toLowerCase();
+ if (tmp.contains(G_V) || tmp.contains(G_E)) {
+ if (!line.startsWith(G_V) || !line.startsWith(G_E)) {
+ line = cutGremlin(line);
+ }
+ return line;
+ }
+ }
+ return StringUtils.EMPTY;
+ }
+
+ private String getWenXin4GremlinResults(List lineList) {
+ for (String line : lineList) {
+ String tmp = line.toLowerCase();
+ if (tmp.contains(G_V) || tmp.contains(G_E)) {
+ if (!line.startsWith(G_V) || !line.startsWith(G_E)) {
+ line = cutGremlin(line);
+ }
+ return line;
+ }
+ }
+ return StringUtils.EMPTY;
+ }
+
+ private String cutGremlin(String line) {
+ int start = 0;
+ int end = 0;
+ for (int i = 0; i < line.length(); i++) {
+ if (line.charAt(i) == 'g') {
+ start = i;
+ break;
+ }
+ }
+ for (int i = line.length() - 1; i >= 0; i--) {
+ if (line.charAt(i) == ')') {
+ end = i + 1;
+ break;
+ }
+ }
+ return line.substring(start, end);
+ }
+
+ /**
+ * 判断结果是否正确
+ * @param lineList
+ * @return
+ */
+ private boolean judgeResultSuccess(List lineList, String model) {
+ if (GPT_4_MODEL.equals(model)) {
+ return this.judgeGpt4ResultSuccess(lineList);
+ } else if (WENXIN_4_MODEL.equals(model)) {
+ return this.judgeWenXin4ResultSuccess(lineList);
+ }
+ return false;
+ }
+
+ private boolean judgeGpt4ResultSuccess(List lineList) {
+ if (CollectionUtils.isEmpty(lineList)) {
+ return false;
+ }
+ for (String line : lineList) {
+ if (line.contains("Finished chain") ||
+ line.toLowerCase().contains(G_E) ||
+ line.toLowerCase().contains(G_V)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean judgeWenXin4ResultSuccess(List lineList) {
+ if (CollectionUtils.isEmpty(lineList)) {
+ return false;
+ }
+ for (String line : lineList) {
+ if (line.contains("```") || line.toLowerCase().contains(G_E) ||
+ line.toLowerCase().contains(G_V)) {
+ return true;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 输出error 信息
+ * @param proc
+ * @return
+ */
+ private void calculateError(List proc, String model) {
+ if (GPT_4_MODEL.equals(model)) {
+ this.calculateGpt4Error(proc);
+ } else if (WENXIN_4_MODEL.equals(model)) {
+ this.calculateWenXin4Error(proc);
+ }
+ }
+
+ private void calculateGpt4Error(List proc) {
+ try {
+ StringBuilder sb = new StringBuilder();
+ for (String line : proc) {
+ sb.append(line).append("\n");
+ }
+ log.error("calculateError error {}", sb.toString());
+ } catch (Exception e) {
+ log.error("calculateError error", e);
+ }
+ }
+
+ private void calculateWenXin4Error(List proc) {
+ try {
+ StringBuilder sb = new StringBuilder();
+ for (String line : proc) {
+ sb.append(line).append("\n");
+ }
+ log.error("calculateError error {}", sb.toString());
+ } catch (Exception e) {
+ log.error("calculateError error", e);
+ }
+ }
+
+ /**
+ * 生成返回结果
+ * @param query
+ * @param gremlin
+ * @return
+ */
+ private ResponseLangChain generateResponseLangChain(String query, String gremlin) {
+ return ResponseLangChain.builder().gremlin(gremlin).query(query).build();
+ }
+
+ private void checkUserParam(RequestLangChainParams requestLangChainParams) {
+ E.checkNotNull(requestLangChainParams.getUserName(),
+ "params username must not be null");
+ E.checkNotNull(requestLangChainParams.getPassword(),
+ "params password must not be null");
+ }
+
+ private void checkParams(RequestLangChainParams requestLangChainParams) {
+ E.checkNotNull(requestLangChainParams, "params must not be null");
+ E.checkNotNull(requestLangChainParams.getQuery(), "params query must not be null");
+ E.checkNotNull(requestLangChainParams.getModel(), "params model must not be null");
+ E.checkState(DEFAULT_MODEL.contains(requestLangChainParams.getModel()),
+ "mode must be [\"wenxin4\", \"gpt4\"]");
+ }
+
+ private void checkModelParams(RequestLangChainParams requestLangChainParams) {
+ if (GPT_4_MODEL.equals(requestLangChainParams.getModel())) {
+ E.checkNotNull(requestLangChainParams.getOpenKey(),
+ "params open_key must not be null");
+ }
+ if (WENXIN_4_MODEL.equals(requestLangChainParams.getModel())) {
+ E.checkNotNull(requestLangChainParams.getErnieClientId(),
+ "params ernie_client_id must not be null");
+ E.checkNotNull(requestLangChainParams.getErnieClientSecret(),
+ "params ernie_client_secret must not be null");
+ }
+ }
+
+ private String[] getExcuteArgs(String pythonPath,
+ String pythonScriptPath,
+ String query,
+ String openKey,
+ String graphSchema,
+ String model,
+ String ernieClientId,
+ String ernieClientSecret) {
+ List argsList = new ArrayList<>();
+ argsList.add(this.configValue(HubbleOptions.LANGCHAIN_PYTHON_PATH));
+ argsList.add(pythonScriptPath);
+ argsList.add("--query");
+ argsList.add(query);
+ argsList.add("--graph_schema");
+ argsList.add(StringUtils.defaultString(graphSchema));
+ if (WENXIN_4_MODEL.equals(model)) {
+ if (ernieClientSecret != null) {
+ argsList.add("--ernie_client_secret");
+ argsList.add(ernieClientSecret);
+ }
+ if (ernieClientId != null) {
+ argsList.add("--ernie_client_id");
+ argsList.add(ernieClientId);
+ }
+ } else if (GPT_4_MODEL.equals(model)) {
+ if (openKey != null) {
+ argsList.add("--open_key");
+ argsList.add(openKey);
+ }
+ }
+ argsList.add("--model");
+ argsList.add(model);
+ return argsList.toArray(new String[argsList.size()]);
+ }
+
+ private T configValue(ConfigOption option) {
+ if (this.config == null) {
+ return option.defaultValue();
+ }
+ return this.config.get(option);
+ }
+
+ private void checkParamsValid(GremlinQuery query) {
+ Ex.check(!StringUtils.isEmpty(query.getContent()),
+ "common.param.cannot-be-null-or-empty",
+ "gremlin-query.content");
+ Ex.check(query.getContent().length() <= GremlinController.CONTENT_LENGTH_LIMIT,
+ "gremlin.statement.exceed-limit", GremlinController.CONTENT_LENGTH_LIMIT);
+ }
+
+ private HashMap getBigModelSchema(List vertexLabels,
+ List edgeLabels) {
+ List vertexLabelVoList = Lists.newArrayList();
+ List edgeLabelVoList = Lists.newArrayList();
+ List relationshipsList = Lists.newArrayList();
+ if (CollectionUtils.isNotEmpty(vertexLabels)) {
+ for (VertexLabel vertexLabel : vertexLabels) {
+ VertexLabelVo vertexLabelVo = new VertexLabelVo();
+ vertexLabelVo.setName(vertexLabel.name());
+ vertexLabelVo.setPrimaryKeys(vertexLabel.primaryKeys());
+ vertexLabelVo.setProperties(Lists.newArrayList());
+ vertexLabelVo.getProperties().addAll(vertexLabel.properties());
+
+ vertexLabelVoList.add(vertexLabelVo);
+ }
+ }
+
+ if (CollectionUtils.isNotEmpty(edgeLabels)) {
+ for (EdgeLabel edgeLabel : edgeLabels) {
+ EdgeLabelVo edgeLabelVo = new EdgeLabelVo();
+ edgeLabelVo.setName(edgeLabel.name());
+ edgeLabelVo.setProperties(Lists.newArrayList());
+ edgeLabelVo.getProperties().addAll(edgeLabel.properties());
+
+ edgeLabelVoList.add(edgeLabelVo);
+
+ relationshipsList.add(Relationship.getRelation(edgeLabel.sourceLabel(),
+ edgeLabel.name(), edgeLabel.targetLabel()));
+ }
+ }
+
+ HashMap schema = new HashMap();
+ schema.put("Node properties", vertexLabelVoList);
+ schema.put("Edge properties", edgeLabelVoList);
+ schema.put("Relationships", relationshipsList);
+ return schema;
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Builder
+ static class RequestLangChainParams {
+ @JsonProperty("python_path")
+ private String pythonPath = "python";
+
+ @JsonProperty("file_name")
+ private String fileName = DEFAULT_PYTHON_FILE;
+
+ @JsonProperty("query")
+ private String query;
+
+ @JsonProperty("open_key")
+ private String openKey;
+
+
+ @JsonProperty("graph_schema")
+ private String graphSchema = "";
+
+ @JsonProperty("ernie_client_secret")
+ private String ernieClientSecret;
+
+ @JsonProperty("ernie_client_id")
+ private String ernieClientId;
+
+ @JsonProperty("model")
+ private String model = "wenxin4";
+
+ @JsonProperty("username")
+ private String userName;
+
+ @JsonProperty("password")
+ private String password;
+
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ @Builder
+ static class ResponseLangChain {
+ @JsonProperty("query")
+ private String query;
+
+ @JsonProperty("gremlin")
+ private String gremlin;
+ }
+
+ @Data
+ static class VertexLabelVo {
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("primary_keys")
+ private List primaryKeys;
+
+ @JsonProperty("properties")
+ private List properties;
+ }
+
+ @Data
+ static class EdgeLabelVo {
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("properties")
+ private List properties;
+ }
+
+ static class Relationship {
+ public static String getRelation(String sourceLabel, String name, String targetLabel) {
+ return sourceLabel + "--" + name + "-->" + targetLabel;
+
+ }
+ }
+}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java
index 5614334e1..a9642e0ac 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileMappingController.java
@@ -18,20 +18,20 @@
package org.apache.hugegraph.controller.load;
-import java.util.List;
-import java.util.Set;
-
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
import org.apache.hugegraph.entity.enums.JobStatus;
-import org.apache.hugegraph.entity.load.EdgeMapping;
-import org.apache.hugegraph.entity.load.ElementMapping;
import org.apache.hugegraph.entity.load.FileMapping;
import org.apache.hugegraph.entity.load.FileSetting;
+import org.apache.hugegraph.entity.load.ElementMapping;
import org.apache.hugegraph.entity.load.JobManager;
import org.apache.hugegraph.entity.load.LoadParameter;
import org.apache.hugegraph.entity.load.VertexMapping;
+import org.apache.hugegraph.entity.load.EdgeMapping;
import org.apache.hugegraph.entity.schema.EdgeLabelEntity;
import org.apache.hugegraph.entity.schema.VertexLabelEntity;
import org.apache.hugegraph.exception.ExternalException;
@@ -54,14 +54,13 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-
-import lombok.extern.log4j.Log4j2;
+import java.util.List;
+import java.util.Set;
@Log4j2
@RestController
-@RequestMapping(Constant.API_VERSION +
- "graph-connections/{connId}/job-manager/{jobId}/file-mappings")
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}/job-manager/{jobId}/file-mappings")
public class FileMappingController extends BaseController {
@Autowired
@@ -74,7 +73,8 @@ public class FileMappingController extends BaseController {
private JobManagerService jobService;
@GetMapping
- public IPage list(@PathVariable("connId") int connId,
+ public IPage list(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam(name = "page_no",
required = false,
@@ -84,12 +84,15 @@ public IPage list(@PathVariable("connId") int connId,
required = false,
defaultValue = "10")
int pageSize) {
- return this.service.list(connId, jobId, pageNo, pageSize);
+ return this.service.list(graphSpace, graph, jobId, pageNo, pageSize);
}
@GetMapping("{id}")
- public FileMapping get(@PathVariable("id") int id) {
- FileMapping mapping = this.service.get(id);
+ public FileMapping get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @PathVariable("id") int id) {
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
@@ -97,8 +100,11 @@ public FileMapping get(@PathVariable("id") int id) {
}
@DeleteMapping("{id}")
- public void delete(@PathVariable("id") int id) {
- FileMapping mapping = this.service.get(id);
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @PathVariable("id") int id) {
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
@@ -108,15 +114,21 @@ public void delete(@PathVariable("id") int id) {
}
@DeleteMapping
- public void clear() {
- List mappings = this.service.listAll();
+ public void clear(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId) {
+ List mappings = this.service.listByJob(graphSpace, graph,
+ jobId);
for (FileMapping mapping : mappings) {
this.service.remove(mapping.getId());
}
}
@PostMapping("{id}/file-setting")
- public FileMapping fileSetting(@PathVariable("id") int id,
+ public FileMapping fileSetting(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @PathVariable("id") int id,
@RequestBody FileSetting newEntity) {
Ex.check(!StringUtils.isEmpty(newEntity.getDelimiter()),
"load.file-mapping.file-setting.delimiter-cannot-be-empty");
@@ -129,7 +141,7 @@ public FileMapping fileSetting(@PathVariable("id") int id,
Ex.check(!StringUtils.isEmpty(newEntity.getSkippedLine()),
"load.file-mapping.file-setting.skippedline-cannot-be-empty");
- FileMapping mapping = this.service.get(id);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
@@ -145,14 +157,17 @@ public FileMapping fileSetting(@PathVariable("id") int id,
}
@PostMapping("{id}/vertex-mappings")
- public FileMapping addVertexMapping(@PathVariable("connId") int connId,
+ public FileMapping addVertexMapping(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@RequestBody VertexMapping newEntity) {
- FileMapping mapping = this.service.get(id);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
- this.checkVertexMappingValid(connId, newEntity, mapping);
+ HugeClient client = this.authClient(graphSpace, graph);
+ this.checkVertexMappingValid(client, newEntity, mapping);
newEntity.setId(HubbleUtil.generateSimpleId());
mapping.getVertexMappings().add(newEntity);
@@ -161,15 +176,18 @@ public FileMapping addVertexMapping(@PathVariable("connId") int connId,
}
@PutMapping("{id}/vertex-mappings/{vmid}")
- public FileMapping updateVertexMapping(@PathVariable("connId") int connId,
+ public FileMapping updateVertexMapping(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@PathVariable("vmid") String vmId,
@RequestBody VertexMapping newEntity) {
- FileMapping mapping = this.service.get(id);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
- this.checkVertexMappingValid(connId, newEntity, mapping);
+ HugeClient client = this.authClient(graphSpace, graph);
+ this.checkVertexMappingValid(client, newEntity, mapping);
VertexMapping vertexMapping = mapping.getVertexMapping(vmId);
Ex.check(vertexMapping != null,
@@ -184,9 +202,13 @@ public FileMapping updateVertexMapping(@PathVariable("connId") int connId,
}
@DeleteMapping("{id}/vertex-mappings/{vmid}")
- public FileMapping deleteVertexMapping(@PathVariable("id") int id,
+ public FileMapping deleteVertexMapping(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @PathVariable("id") int id,
@PathVariable("vmid") String vmid) {
- FileMapping mapping = this.service.get(id);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
@@ -195,21 +217,24 @@ public FileMapping deleteVertexMapping(@PathVariable("id") int id,
boolean removed = mapping.getVertexMappings().remove(vertexMapping);
if (!removed) {
throw new ExternalException(
- "load.file-mapping.vertex-mapping.not-exist.id", vmid);
+ "load.file-mapping.vertex-mapping.not-exist.id", vmid);
}
this.service.update(mapping);
return mapping;
}
@PostMapping("{id}/edge-mappings")
- public FileMapping addEdgeMapping(@PathVariable("connId") int connId,
+ public FileMapping addEdgeMapping(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@RequestBody EdgeMapping newEntity) {
- FileMapping mapping = this.service.get(id);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
- this.checkEdgeMappingValid(connId, newEntity, mapping);
+ HugeClient client = this.authClient(graphSpace, graph);
+ this.checkEdgeMappingValid(client, newEntity, mapping);
newEntity.setId(HubbleUtil.generateSimpleId());
mapping.getEdgeMappings().add(newEntity);
@@ -218,15 +243,18 @@ public FileMapping addEdgeMapping(@PathVariable("connId") int connId,
}
@PutMapping("{id}/edge-mappings/{emid}")
- public FileMapping updateEdgeMapping(@PathVariable("connId") int connId,
+ public FileMapping updateEdgeMapping(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
@PathVariable("id") int id,
@PathVariable("emid") String emId,
@RequestBody EdgeMapping newEntity) {
- FileMapping mapping = this.service.get(id);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
- this.checkEdgeMappingValid(connId, newEntity, mapping);
+ HugeClient client = this.authClient(graphSpace, graph);
+ this.checkEdgeMappingValid(client, newEntity, mapping);
EdgeMapping edgeMapping = mapping.getEdgeMapping(emId);
Ex.check(edgeMapping != null,
@@ -241,9 +269,13 @@ public FileMapping updateEdgeMapping(@PathVariable("connId") int connId,
}
@DeleteMapping("{id}/edge-mappings/{emid}")
- public FileMapping deleteEdgeMapping(@PathVariable("id") int id,
+ public FileMapping deleteEdgeMapping(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @PathVariable("id") int id,
@PathVariable("emid") String emid) {
- FileMapping mapping = this.service.get(id);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId, id);
if (mapping == null) {
throw new ExternalException("load.file-mapping.not-exist.id", id);
}
@@ -252,7 +284,7 @@ public FileMapping deleteEdgeMapping(@PathVariable("id") int id,
boolean removed = mapping.getEdgeMappings().remove(edgeMapping);
if (!removed) {
throw new ExternalException(
- "load.file-mapping.edge-mapping.not-exist.id", emid);
+ "load.file-mapping.edge-mapping.not-exist.id", emid);
}
this.service.update(mapping);
return mapping;
@@ -263,9 +295,13 @@ public FileMapping deleteEdgeMapping(@PathVariable("id") int id,
* in actually
*/
@PostMapping("load-parameter")
- public void loadParameter(@RequestBody LoadParameter newEntity) {
+ public void loadParameter(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @RequestBody LoadParameter newEntity) {
this.checkLoadParameter(newEntity);
- List mappings = this.service.listAll();
+ List mappings = this.service.listByJob(graphSpace, graph,
+ jobId);
for (FileMapping mapping : mappings) {
LoadParameter oldEntity = mapping.getLoadParameter();
LoadParameter entity = this.mergeEntity(oldEntity, newEntity);
@@ -275,8 +311,10 @@ public void loadParameter(@RequestBody LoadParameter newEntity) {
}
@PutMapping("next-step")
- public JobManager nextStep(@PathVariable("jobId") int jobId) {
- JobManager jobEntity = this.jobService.get(jobId);
+ public JobManager nextStep(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId) {
+ JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.MAPPING,
"job.manager.status.unexpected",
@@ -286,10 +324,11 @@ public JobManager nextStep(@PathVariable("jobId") int jobId) {
return jobEntity;
}
- private void checkVertexMappingValid(int connId, VertexMapping vertexMapping,
+ private void checkVertexMappingValid(HugeClient client,
+ VertexMapping vertexMapping,
FileMapping fileMapping) {
VertexLabelEntity vl = this.vlService.get(vertexMapping.getLabel(),
- connId);
+ client);
Ex.check(!vl.getIdStrategy().isAutomatic(),
"load.file-mapping.vertex.automatic-id-unsupported");
@@ -305,24 +344,24 @@ private void checkVertexMappingValid(int connId, VertexMapping vertexMapping,
vl.getPrimaryKeys().size(),
"load.file-mapping.vertex.id-fields-should-same-size-pks");
Ex.check(!CollectionUtils.containsAny(
- vertexMapping.fieldMappingToMap().values(),
- vl.getPrimaryKeys()),
+ vertexMapping.fieldMappingToMap().values(),
+ vl.getPrimaryKeys()),
"load.file-mapping.vertex.mapping-fields-cannot-contains-pk");
} else {
Ex.check(vertexMapping.getIdFields().size() == 1,
"load.file-mapping.vertex.id-fields-should-only-one");
}
Ex.check(CollectionUtil.allUnique(
- vertexMapping.fieldMappingToMap().values()),
+ vertexMapping.fieldMappingToMap().values()),
"load.file-mapping.mapping-fields-should-no-duplicate");
this.checkMappingValid(vertexMapping, fileMapping);
}
- private void checkEdgeMappingValid(int connId, EdgeMapping edgeMapping,
+ private void checkEdgeMappingValid(HugeClient client, EdgeMapping edgeMapping,
FileMapping fileMapping) {
- EdgeLabelEntity el = this.elService.get(edgeMapping.getLabel(), connId);
- VertexLabelEntity source = this.vlService.get(el.getSourceLabel(), connId);
- VertexLabelEntity target = this.vlService.get(el.getTargetLabel(), connId);
+ EdgeLabelEntity el = this.elService.get(edgeMapping.getLabel(), client);
+ VertexLabelEntity source = this.vlService.get(el.getSourceLabel(), client);
+ VertexLabelEntity target = this.vlService.get(el.getTargetLabel(), client);
Ex.check(!source.getIdStrategy().isAutomatic(),
"load.file-mapping.vertex.automatic-id-unsupported");
Ex.check(!target.getIdStrategy().isAutomatic(),
@@ -341,7 +380,7 @@ private void checkEdgeMappingValid(int connId, EdgeMapping edgeMapping,
"load.file-mapping.edge.target-fields-should-in-column-names",
edgeMapping.getTargetFields(), columnNames);
Ex.check(CollectionUtil.allUnique(
- edgeMapping.fieldMappingToMap().values()),
+ edgeMapping.fieldMappingToMap().values()),
"load.file-mapping.mapping-fields-should-no-duplicate");
this.checkMappingValid(edgeMapping, fileMapping);
}
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java
index d019549a4..8efac6f53 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/FileUploadController.java
@@ -23,17 +23,31 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import org.apache.hugegraph.controller.BaseController;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.entity.enums.FileMappingStatus;
@@ -41,6 +55,7 @@
import org.apache.hugegraph.entity.load.FileMapping;
import org.apache.hugegraph.entity.load.FileUploadResult;
import org.apache.hugegraph.entity.load.JobManager;
+import org.apache.hugegraph.exception.ExternalException;
import org.apache.hugegraph.exception.InternalException;
import org.apache.hugegraph.options.HubbleOptions;
import org.apache.hugegraph.service.load.FileMappingService;
@@ -49,23 +64,14 @@
import org.apache.hugegraph.util.Ex;
import org.apache.hugegraph.util.FileUtil;
import org.apache.hugegraph.util.HubbleUtil;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
import lombok.extern.log4j.Log4j2;
@Log4j2
@RestController
-@RequestMapping(Constant.API_VERSION + "graph-connections/{connId}/job-manager/{jobId}/upload-file")
-public class FileUploadController {
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}/job-manager/{jobId}/upload-file")
+public class FileUploadController extends BaseController {
@Autowired
private HugeConfig config;
@@ -75,10 +81,14 @@ public class FileUploadController {
private JobManagerService jobService;
@GetMapping("token")
- public Map fileToken(@PathVariable("connId") int connId,
+ public Map fileToken(
+ @PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("names")
List fileNames) {
+ Ex.check(this.jobService.get(graphSpace, graph, jobId) != null,
+ "job-manager.not-exist.id", jobId);
Ex.check(CollectionUtil.allUnique(fileNames),
"load.upload.file.duplicate-name");
Map tokens = new HashMap<>();
@@ -94,7 +104,8 @@ public Map fileToken(@PathVariable("connId") int connId,
}
@PostMapping
- public FileUploadResult upload(@PathVariable("connId") int connId,
+ public FileUploadResult upload(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("file") MultipartFile file,
@RequestParam("name") String fileName,
@@ -107,16 +118,17 @@ public FileUploadResult upload(@PathVariable("connId") int connId,
this.checkTotalAndIndexValid(total, index);
this.checkFileNameValid(fileName);
this.checkFileNameMatchToken(fileName, token);
- JobManager jobEntity = this.jobService.get(jobId);
+ JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Long sourceFileSize = this.resolveSourceFileSize(file, fileSize,
total, index);
- this.checkFileValid(jobId, jobEntity, file, fileName);
+ this.checkFileValid(graphSpace, graph, jobId, jobEntity, file, fileName);
if (jobEntity.getJobStatus() == JobStatus.DEFAULT) {
jobEntity.setJobStatus(JobStatus.UPLOADING);
this.jobService.update(jobEntity);
}
// Ensure location exist and generate file path
- String filePath = this.generateFilePath(connId, jobId, fileName);
+ String filePath = this.generateFilePath(graphSpace, graph, jobId,
+ fileName);
// Check this file deleted before
ReadWriteLock lock = this.uploadingTokenLocks().get(token);
FileUploadResult result;
@@ -133,16 +145,18 @@ public FileUploadResult upload(@PathVariable("connId") int connId,
try {
FileMapping reservedMapping;
synchronized (this.service) {
- reservedMapping = this.reserveUploadQuota(connId, jobId,
+ reservedMapping = this.reserveUploadQuota(graphSpace, graph, jobId,
fileName, filePath,
sourceFileSize);
}
- result = this.service.uploadFile(file, index, filePath);
+ result = this.service.uploadFile(file, fileName, index, filePath);
if (result.getStatus() == FileUploadResult.Status.FAILURE) {
return result;
}
synchronized (this.service) {
- FileMapping mapping = this.service.get(connId, jobId, fileName);
+ // Verify the existence of fragmented files
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId,
+ fileName);
if (mapping == null) {
mapping = reservedMapping;
}
@@ -160,7 +174,8 @@ public FileUploadResult upload(@PathVariable("connId") int connId,
this.service.update(mapping);
return result;
}
- JobManager currentJob = this.jobService.get(jobId);
+ JobManager currentJob = this.jobService.get(graphSpace, graph,
+ jobId);
long actualFileSize;
try {
actualFileSize = this.resolveUploadedFileSize(
@@ -203,18 +218,20 @@ public FileUploadResult upload(@PathVariable("connId") int connId,
}
@DeleteMapping
- public Boolean delete(@PathVariable("connId") int connId,
+ public Boolean delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("name") String fileName,
@RequestParam("token") String token) {
this.checkFileNameValid(fileName);
- JobManager jobEntity = this.jobService.get(jobId);
+ JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.UPLOADING ||
jobEntity.getJobStatus() == JobStatus.MAPPING ||
jobEntity.getJobStatus() == JobStatus.SETTING,
"deleted.file.no-permission");
- FileMapping mapping = this.service.get(connId, jobId, fileName);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId,
+ fileName);
Ex.check(mapping != null, "load.file-mapping.not-exist.name", fileName);
ReadWriteLock lock = this.uploadingTokenLocks().get(token);
@@ -240,8 +257,10 @@ public Boolean delete(@PathVariable("connId") int connId,
}
@PutMapping("next-step")
- public JobManager nextStep(@PathVariable("jobId") int jobId) {
- JobManager jobEntity = this.jobService.get(jobId);
+ public JobManager nextStep(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId) {
+ JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.UPLOADING,
"job.manager.status.unexpected",
@@ -262,6 +281,10 @@ private void checkTotalAndIndexValid(int total, int index) {
if (index < 0) {
throw new InternalException("The request params 'index' must >= 0");
}
+ if (index >= total) {
+ throw new InternalException("The request params 'index' must < " +
+ "'total'");
+ }
}
private void checkFileNameMatchToken(String fileName, String token) {
@@ -279,8 +302,9 @@ private void checkFileNameValid(String fileName) {
"load.upload.file.name.invalid");
}
- private void checkFileValid(int jobId, JobManager jobEntity,
- MultipartFile file, String fileName) {
+ private void checkFileValid(String graphSpace, String graph, int jobId,
+ JobManager jobEntity, MultipartFile file,
+ String fileName) {
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.DEFAULT ||
jobEntity.getJobStatus() == JobStatus.UPLOADING ||
@@ -293,19 +317,29 @@ private void checkFileValid(int jobId, JobManager jobEntity,
log.debug("File content type: {}", file.getContentType());
String format = FilenameUtils.getExtension(fileName);
- List formatWhiteList = this.config.get(
- HubbleOptions.UPLOAD_FILE_FORMAT_LIST);
- Ex.check(formatWhiteList.contains(format),
+ Ex.check(StringUtils.isNotBlank(format),
"load.upload.file.format.unsupported");
+ List formatWhiteList = this.config.get(
+ HubbleOptions.UPLOAD_FILE_FORMAT_LIST);
+ String normalizedFormat = format.toLowerCase(Locale.ROOT);
+ boolean supported = formatWhiteList != null &&
+ formatWhiteList.stream()
+ .filter(StringUtils::isNotBlank)
+ .map(String::trim)
+ .map(item -> item.toLowerCase(Locale.ROOT))
+ .anyMatch(normalizedFormat::equals);
+ Ex.check(supported, "load.upload.file.format.unsupported");
}
- private FileMapping reserveUploadQuota(int connId, int jobId,
+ private FileMapping reserveUploadQuota(String graphSpace, String graph,
+ int jobId,
String fileName, String filePath,
Long sourceFileSize) {
- JobManager currentJob = this.jobService.get(jobId);
+ JobManager currentJob = this.jobService.get(graphSpace, graph, jobId);
Ex.check(currentJob != null, "job-manager.not-exist.id", jobId);
- FileMapping mapping = this.service.get(connId, jobId, fileName);
+ FileMapping mapping = this.service.get(graphSpace, graph, jobId,
+ fileName);
Ex.check(mapping == null ||
mapping.getFileStatus() == FileMappingStatus.UPLOADING,
"load.upload.file.existed", fileName);
@@ -319,7 +353,7 @@ private FileMapping reserveUploadQuota(int connId, int jobId,
reservedUploadingSize);
if (mapping == null) {
- mapping = new FileMapping(connId, fileName, filePath);
+ mapping = new FileMapping(graphSpace, graph, fileName, filePath);
mapping.setJobId(jobId);
this.fillUploadingReservation(mapping, reservedFileSize);
this.service.save(mapping);
@@ -356,13 +390,13 @@ private void checkFileSizeLimit(long fileSize, long currentJobSize,
Ex.check(fileSize > 0L, "load.upload.file.cannot-be-empty");
long singleFileSizeLimit = this.config.get(
- HubbleOptions.UPLOAD_SINGLE_FILE_SIZE_LIMIT);
+ HubbleOptions.UPLOAD_SINGLE_FILE_SIZE_LIMIT);
Ex.check(fileSize <= singleFileSizeLimit,
"load.upload.file.exceed-single-size",
FileUtils.byteCountToDisplaySize(singleFileSizeLimit));
long totalFileSizeLimit = this.config.get(
- HubbleOptions.UPLOAD_TOTAL_FILE_SIZE_LIMIT);
+ HubbleOptions.UPLOAD_TOTAL_FILE_SIZE_LIMIT);
long totalReservedSize = this.safeAdd(this.safeAdd(fileSize,
currentJobSize),
reservedUploadingSize);
@@ -445,14 +479,20 @@ private long safeAdd(long left, long right) {
}
}
- private String generateFilePath(int connId, int jobId, String fileName) {
+ private String generateFilePath(String graphSpace, String graph, int jobId,
+ String fileName) {
String location = this.config.get(HubbleOptions.UPLOAD_FILE_LOCATION);
- String path = Paths.get(CONN_PREIFX + connId, JOB_PREIFX + jobId)
- .toString();
+ String path = Paths.get(CONN_PREIFX + graphSpace + "-" + graph,
+ JOB_PREIFX + jobId).toString();
this.ensureLocationExist(location, path);
// Before merge: upload-files/conn-1/verson_person.csv/part-1
// After merge: upload-files/conn-1/file-mapping-1/verson_person.csv
- return Paths.get(location, path, fileName).toString();
+ Path root = Paths.get(location, path).normalize();
+ Path filePathObj = root.resolve(fileName).normalize();
+ if (!filePathObj.startsWith(root)) {
+ throw new ExternalException("load.upload.file.name.invalid");
+ }
+ return filePathObj.toString();
}
private void ensureLocationExist(String location, String connPath) {
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java
index 12ccaa414..1db3ca327 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/JobManagerController.java
@@ -18,9 +18,8 @@
package org.apache.hugegraph.controller.load;
-import java.util.ArrayList;
-import java.util.List;
-
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.common.Response;
@@ -48,13 +47,13 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-
-import lombok.extern.log4j.Log4j2;
+import java.util.ArrayList;
+import java.util.List;
@Log4j2
@RestController
-@RequestMapping(Constant.API_VERSION + "graph-connections/{connId}/job-manager")
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}/job-manager")
public class JobManagerController {
private static final int LIMIT = 500;
@@ -71,27 +70,29 @@ public JobManagerController(JobManagerService service) {
}
@PostMapping
- public JobManager create(@PathVariable("connId") int connId,
+ public JobManager create(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@RequestBody JobManager entity) {
synchronized (this.service) {
Ex.check(entity.getJobName().length() <= 48,
"job.manager.job-name.reached-limit");
Ex.check(entity.getJobName() != null, () ->
- Constant.COMMON_NAME_PATTERN.matcher(
- entity.getJobName()).matches(),
+ Constant.COMMON_NAME_PATTERN.matcher(
+ entity.getJobName()).matches(),
"job.manager.job-name.unmatch-regex");
Ex.check(entity.getJobRemarks().length() <= 200,
"job.manager.job-remarks.reached-limit");
Ex.check(!StringUtils.isEmpty(entity.getJobRemarks()), () ->
- Constant.COMMON_REMARK_PATTERN.matcher(
- entity.getJobRemarks()).matches(),
+ Constant.COMMON_REMARK_PATTERN.matcher(
+ entity.getJobRemarks()).matches(),
"job.manager.job-remarks.unmatch-regex");
Ex.check(this.service.count() < LIMIT,
"job.manager.reached-limit", LIMIT);
- if (this.service.getTask(entity.getJobName(), connId) != null) {
+ if (this.service.getTask(entity.getJobName(), graphSpace, graph) != null) {
throw new InternalException("job.manager.job-name.repeated");
}
- entity.setConnId(connId);
+ entity.setGraphSpace(graphSpace);
+ entity.setGraph(graph);
entity.setJobStatus(JobStatus.DEFAULT);
entity.setJobDuration(0L);
entity.setJobSize(0L);
@@ -103,13 +104,17 @@ public JobManager create(@PathVariable("connId") int connId,
}
@DeleteMapping("{id}")
- public void delete(@PathVariable("id") int id) {
- this.service.deleteJob(id);
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("id") int id) {
+ this.service.deleteJob(graphSpace, graph, id);
}
@GetMapping("{id}")
- public JobManager get(@PathVariable("id") int id) {
- JobManager job = this.service.get(id);
+ public JobManager get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("id") int id) {
+ JobManager job = this.service.get(graphSpace, graph, id);
if (job == null) {
throw new ExternalException("job.manager.not-exist.id", id);
}
@@ -117,49 +122,52 @@ public JobManager get(@PathVariable("id") int id) {
}
@GetMapping("ids")
- public List list(@PathVariable("connId") int connId,
+ public List list(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@RequestParam("job_ids") List jobIds) {
- return this.service.list(connId, jobIds);
+ return this.service.list(graphSpace, graph, jobIds);
}
@GetMapping
- public IPage list(@PathVariable("connId") int connId,
+ public IPage list(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@RequestParam(name = "page_no",
required = false,
defaultValue = "1")
- int pageNo,
+ int pageNo,
@RequestParam(name = "page_size",
required = false,
defaultValue = "10")
- int pageSize,
+ int pageSize,
@RequestParam(name = "content",
required = false,
defaultValue = "")
- String content) {
- return this.service.list(connId, pageNo, pageSize, content);
+ String content) {
+ return this.service.list(graphSpace, graph, pageNo, pageSize, content);
}
@PutMapping("{id}")
- public JobManager update(@PathVariable("connId") int connId,
+ public JobManager update(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("id") int id,
@RequestBody JobManager newEntity) {
Ex.check(newEntity.getJobName().length() <= 48,
"job.manager.job-name.reached-limit");
Ex.check(newEntity.getJobName() != null, () ->
- Constant.COMMON_NAME_PATTERN.matcher(
- newEntity.getJobName()).matches(),
+ Constant.COMMON_NAME_PATTERN.matcher(
+ newEntity.getJobName()).matches(),
"job.manager.job-name.unmatch-regex");
Ex.check(!StringUtils.isEmpty(newEntity.getJobRemarks()), () ->
- Constant.COMMON_REMARK_PATTERN.matcher(
- newEntity.getJobRemarks()).matches(),
+ Constant.COMMON_REMARK_PATTERN.matcher(
+ newEntity.getJobRemarks()).matches(),
"job.manager.job-remarks.unmatch-regex");
// Check exist Job Manager with this id
- JobManager entity = this.service.get(id);
+ JobManager entity = this.service.get(graphSpace, graph, id);
if (entity == null) {
throw new ExternalException("job-manager.not-exist.id", id);
}
if (!newEntity.getJobName().equals(entity.getJobName()) &&
- this.service.getTask(newEntity.getJobName(), connId) != null) {
+ this.service.getTask(newEntity.getJobName(), graphSpace, graph) != null) {
throw new InternalException("job.manager.job-name.repeated");
}
entity.setJobName(newEntity.getJobName());
@@ -169,9 +177,10 @@ public JobManager update(@PathVariable("connId") int connId,
}
@GetMapping("{id}/reason")
- public Response reason(@PathVariable("connId") int connId,
+ public Response reason(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("id") int id) {
- JobManager job = this.service.get(id);
+ JobManager job = this.service.get(graphSpace, graph, id);
if (job == null) {
throw new ExternalException("job.manager.not-exist.id", id);
}
@@ -182,7 +191,8 @@ public Response reason(@PathVariable("connId") int connId,
int fileId = task.getFileId();
String reason = "";
if (task.getStatus() == LoadStatus.FAILED) {
- FileMapping mapping = this.fmService.get(fileId);
+ FileMapping mapping = this.fmService.get(graphSpace, graph, id,
+ fileId);
reason = this.taskService.readLoadFailedReason(mapping);
}
reasonResult.setTaskId(task.getJobId());
diff --git a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java
index b4862bf5b..edc0cd819 100644
--- a/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java
+++ b/hugegraph-hubble/hubble-be/src/main/java/org/apache/hugegraph/controller/load/LoadTaskController.java
@@ -18,24 +18,26 @@
package org.apache.hugegraph.controller.load;
-import java.util.ArrayList;
-import java.util.List;
-
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.extern.log4j.Log4j2;
import org.apache.hugegraph.common.Constant;
import org.apache.hugegraph.common.Response;
+import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.controller.BaseController;
+import org.apache.hugegraph.driver.HugeClient;
import org.apache.hugegraph.entity.GraphConnection;
import org.apache.hugegraph.entity.enums.JobStatus;
import org.apache.hugegraph.entity.load.FileMapping;
import org.apache.hugegraph.entity.load.JobManager;
import org.apache.hugegraph.entity.load.LoadTask;
import org.apache.hugegraph.exception.ExternalException;
-import org.apache.hugegraph.service.GraphConnectionService;
+import org.apache.hugegraph.options.HubbleOptions;
import org.apache.hugegraph.service.load.FileMappingService;
import org.apache.hugegraph.service.load.JobManagerService;
import org.apache.hugegraph.service.load.LoadTaskService;
import org.apache.hugegraph.util.Ex;
import org.apache.hugegraph.util.HubbleUtil;
+import org.apache.hugegraph.util.UrlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -46,23 +48,23 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-
-import lombok.extern.log4j.Log4j2;
+import java.util.ArrayList;
+import java.util.List;
@Log4j2
@RestController
-@RequestMapping(Constant.API_VERSION + "graph-connections/{connId}/job-manager/{jobId}/load-tasks")
+@RequestMapping(Constant.API_VERSION + "graphspaces/{graphspace}/graphs" +
+ "/{graph}/job-manager/{jobId}/load-tasks")
public class LoadTaskController extends BaseController {
private static final int LIMIT = 500;
- @Autowired
- private GraphConnectionService connService;
@Autowired
private FileMappingService fmService;
@Autowired
private JobManagerService jobService;
+ @Autowired
+ private HugeConfig config;
private final LoadTaskService service;
@@ -72,7 +74,8 @@ public LoadTaskController(LoadTaskService service) {
}
@GetMapping
- public IPage list(@PathVariable("connId") int connId,
+ public IPage list(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam(name = "page_no",
required = false,
@@ -82,18 +85,23 @@ public IPage list(@PathVariable("connId") int connId,
required = false,
defaultValue = "10")
int pageSize) {
- return this.service.list(connId, jobId, pageNo, pageSize);
+ return this.service.list(graphSpace, graph, jobId, pageNo, pageSize);
}
@GetMapping("ids")
- public List list(@PathVariable("connId") int connId,
+ public List list(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
@RequestParam("task_ids") List taskIds) {
- return this.service.list(connId, taskIds);
+ return this.service.list(graphSpace, graph, jobId, taskIds);
}
@GetMapping("{id}")
- public LoadTask get(@PathVariable("id") int id) {
- LoadTask task = this.service.get(id);
+ public LoadTask get(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @PathVariable("id") int id) {
+ LoadTask task = this.service.get(graphSpace, graph, jobId, id);
if (task == null) {
throw new ExternalException("load.task.not-exist.id", id);
}
@@ -101,25 +109,31 @@ public LoadTask get(@PathVariable("id") int id) {
}
@PostMapping
- public LoadTask create(@PathVariable("connId") int connId,
+ public LoadTask create(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestBody LoadTask entity) {
- JobManager jobEntity = this.jobService.get(jobId);
+ JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.SETTING,
"load.task.create.no-permission");
synchronized (this.service) {
Ex.check(this.service.count() < LIMIT,
"load.task.reached-limit", LIMIT);
- entity.setConnId(connId);
+ entity.setGraphSpace(graphSpace);
+ entity.setGraph(graph);
+ entity.setJobId(jobId);
this.service.save(entity);
}
return entity;
}
@DeleteMapping("{id}")
- public void delete(@PathVariable("id") int id) {
- LoadTask task = this.service.get(id);
+ public void delete(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
+ @PathVariable("jobId") int jobId,
+ @PathVariable("id") int id) {
+ LoadTask task = this.service.get(graphSpace, graph, jobId, id);
if (task == null) {
throw new ExternalException("load.task.not-exist.id", id);
}
@@ -127,28 +141,44 @@ public void delete(@PathVariable("id") int id) {
}
@PostMapping("start")
- public List start(@PathVariable("connId") int connId,
+ public List start(@PathVariable("graphspace") String graphSpace,
+ @PathVariable("graph") String graph,
@PathVariable("jobId") int jobId,
@RequestParam("file_mapping_ids")
List fileIds) {
- GraphConnection connection = this.connService.get(connId);
- if (connection == null) {
- throw new ExternalException("graph-connection.not-exist.id", connId);
+ GraphConnection connection = new GraphConnection();
+
+ connection.setCluster(config.get(HubbleOptions.PD_CLUSTER));
+ connection.setRouteType(config.get(HubbleOptions.ROUTE_TYPE));
+ connection.setPdPeers(config.get(HubbleOptions.PD_PEERS));
+ connection.setGraphSpace(graphSpace);
+ connection.setGraph(graph);
+ connection.setToken(this.getToken());
+ connection.setUsername(this.getUser());
+ connection.setPassword(this.getCredentialPassword());
+ if (!config.get(HubbleOptions.PD_ENABLED)) {
+ UrlUtil.Host host = UrlUtil.parseHost(config.get(HubbleOptions.SERVER_URL));
+ connection.setProtocol(host.getScheme());
+ connection.setHost(host.getHost());
+ connection.setPort(host.getPort());
}
- JobManager jobEntity = this.jobService.get(jobId);
+
+ JobManager jobEntity = this.jobService.get(graphSpace, graph, jobId);
Ex.check(jobEntity != null, "job-manager.not-exist.id", jobId);
Ex.check(jobEntity.getJobStatus() == JobStatus.SETTING,
"load.task.start.no-permission");
boolean existError = false;
try {
List