diff --git a/.cspell.json b/.cspell.json index 8b9af8e4..58d8ce26 100644 --- a/.cspell.json +++ b/.cspell.json @@ -108,7 +108,9 @@ "deserialised", "unmodelled", "recordss", - "rarr" + "rarr", + "servname", + "nodename" ], "languageSettings": [ { diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml deleted file mode 100644 index 13fcf623..00000000 --- a/.github/workflows/beta-release.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Public beta release -on: - push: - tags: '*.*.*-beta.*' -jobs: - build-and-deploy: - uses: ./.github/workflows/shared-build-and-deploy.yml - with: - ref: ${{ github.ref_name }} - server-id: central - profile: maven-central - tag: 'beta' - secrets: - server-username: ${{ secrets.CENTRAL_PUBLISHER_PORTAL_USERNAME }} - server-password: ${{ secrets.CENTRAL_PUBLISHER_PORTAL_PASSWORD }} - gpg-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - gpg-passphrase: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env - test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env - test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml index bdc19096..9a3c26f7 100644 --- a/.github/workflows/contract-tests.yml +++ b/.github/workflows/contract-tests.yml @@ -5,12 +5,24 @@ on: branches: - main - release/* + - flowvault-release/* jobs: contract-tests: - name: Contract Tests + # One job per module so a break in one is reported against that module by name, + # and both still run even when the other fails. + name: Contract Tests (${{ matrix.module }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - module: skyvault + artifact: skyflow-java + - module: flowvault + artifact: skyflow-flowvault-java + permissions: contents: read pull-requests: write @@ -29,29 +41,31 @@ jobs: cache: 'maven' - name: Verify API surface snapshot - run: mvn -B install -pl common,skyvault -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true + run: mvn -B install -pl common,${{ matrix.module }} -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true - name: Show API surface diff if: failure() run: | - echo "### API surface changes detected ###" - echo "See skyvault/target/japicmp/default-cli.diff for the full comparison against skyvault/api-report/skyflow-java.baseline.jar." - echo "If this change is intentional, run scripts/contract-snapshot-update.sh and commit the updated baseline jar." + echo "### API surface changes detected in ${{ matrix.module }} ###" + echo "Compared against ${{ matrix.module }}/api-report/${{ matrix.artifact }}.baseline.jar." + echo "If this change is intentional, run:" + echo " scripts/contract-snapshot-update.sh ${{ matrix.module }}" + echo "and commit the updated baseline jar." echo "" - cat skyvault/target/japicmp/default-cli.diff || true + cat ${{ matrix.module }}/target/japicmp/default-cli.diff || true - name: Upload API surface diff on failure if: failure() uses: actions/upload-artifact@v4 with: - name: api-surface-diff - path: skyvault/target/japicmp/** + name: api-surface-diff-${{ matrix.module }} + path: ${{ matrix.module }}/target/japicmp/** retention-days: 7 # The step above only shows a diff when the CURRENT build differs from the # committed baseline - once someone runs contract-snapshot-update.sh and # commits the refreshed baseline jar, that check goes green and shows nothing. - # A reviewer looking at a green PR that touches api-report/skyflow-java.baseline.jar + # A reviewer looking at a green PR that touches api-report/*.baseline.jar # (a binary file) would otherwise have no way to see WHAT was just approved as # the new contract. These steps explicitly diff the OLD committed baseline # (from the PR's base branch) against the NEW committed baseline (from this PR) @@ -61,40 +75,74 @@ jobs: if: always() && github.event.pull_request run: | git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1 - if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}" HEAD -- skyvault/api-report/skyflow-java.baseline.jar | grep -q .; then + BASELINE="${{ matrix.module }}/api-report/${{ matrix.artifact }}.baseline.jar" + if ! git diff --name-only "origin/${{ github.event.pull_request.base.ref }}" HEAD -- "$BASELINE" | grep -q .; then + echo "changed=false" >> "$GITHUB_OUTPUT" + elif git cat-file -e "origin/${{ github.event.pull_request.base.ref }}:$BASELINE" 2>/dev/null; then echo "changed=true" >> "$GITHUB_OUTPUT" else - echo "changed=false" >> "$GITHUB_OUTPUT" + # Added by this PR rather than modified: the module is getting its + # first baseline. git diff reports an addition as a change, but there + # is no old snapshot to `git show`, so a plain "true" here would send + # the next step into `git show :` and exit 128. + echo "changed=new" >> "$GITHUB_OUTPUT" fi - name: Diff old vs new contract baseline - if: always() && steps.baseline-diff-check.outputs.changed == 'true' + if: always() && (steps.baseline-diff-check.outputs.changed == 'true' || steps.baseline-diff-check.outputs.changed == 'new') run: | + BASELINE="${{ matrix.module }}/api-report/${{ matrix.artifact }}.baseline.jar" + + if [ "${{ steps.baseline-diff-check.outputs.changed }}" = "new" ]; then + { + echo "\`$BASELINE\` is **new in this PR** - \`${{ matrix.module }}\` had no committed baseline before, so there is nothing to diff against." + echo "" + echo "This snapshot becomes the approved contract: every later PR is compared against it, and any incompatible change fails the \`Contract Tests (${{ matrix.module }})\` job until someone regenerates it deliberately. Review it as the starting point, not as a change." + } > /tmp/contract-baseline-diff.md + cat /tmp/contract-baseline-diff.md + exit 0 + fi + curl -sL -o /tmp/japicmp-cli.jar "https://repo.maven.apache.org/maven2/com/github/siom79/japicmp/japicmp/0.26.0/japicmp-0.26.0-jar-with-dependencies.jar" - mvn -q -B dependency:build-classpath -pl skyvault -Dmdep.outputFile=/tmp/skyvault-classpath.txt -Dmaven.javadoc.skip=true -Dgpg.skip=true - git show "origin/${{ github.event.pull_request.base.ref }}:skyvault/api-report/skyflow-java.baseline.jar" > /tmp/old-baseline.jar + mvn -q -B dependency:build-classpath -pl ${{ matrix.module }} -Dmdep.outputFile=/tmp/module-classpath.txt -Dmaven.javadoc.skip=true -Dgpg.skip=true + git show "origin/${{ github.event.pull_request.base.ref }}:$BASELINE" > /tmp/old-baseline.jar + # Same allowlist the poms gate on, so the comment shows the contract and + # nothing else. Keep these in sync with the in the module poms. java -jar /tmp/japicmp-cli.jar \ -o /tmp/old-baseline.jar \ - -n skyvault/api-report/skyflow-java.baseline.jar \ + -n "$BASELINE" \ -a protected \ - -e "com.skyflow.generated.*;com.skyflow.utils.*" \ - --old-classpath "$(cat /tmp/skyvault-classpath.txt)" \ - --new-classpath "$(cat /tmp/skyvault-classpath.txt)" \ + -i "com.skyflow.Skyflow;com.skyflow.config;com.skyflow.enums;com.skyflow.errors;com.skyflow.serviceaccount.util;com.skyflow.vault.audit;com.skyflow.vault.bin;com.skyflow.vault.connection;com.skyflow.vault.controller;com.skyflow.vault.data;com.skyflow.vault.detect;com.skyflow.vault.tokens" \ + --old-classpath "$(cat /tmp/module-classpath.txt)" \ + --new-classpath "$(cat /tmp/module-classpath.txt)" \ -m \ + --ignore-missing-classes \ --markdown > /tmp/contract-baseline-diff.md || true cat /tmp/contract-baseline-diff.md - name: Comment contract baseline change on PR - if: always() && steps.baseline-diff-check.outputs.changed == 'true' + if: always() && (steps.baseline-diff-check.outputs.changed == 'true' || steps.baseline-diff-check.outputs.changed == 'new') uses: actions/github-script@v7 + env: + BASELINE_STATE: ${{ steps.baseline-diff-check.outputs.changed }} with: script: | const fs = require('fs'); + const module = '${{ matrix.module }}'; + const artifact = '${{ matrix.artifact }}'; const summary = fs.readFileSync('/tmp/contract-baseline-diff.md', 'utf8'); - const marker = ''; - const body = `${marker}\n## Contract baseline change detected\n\nThis PR updates \`skyvault/api-report/skyflow-java.baseline.jar\` (the approved public API contract). Here is exactly what it changes, comparing the baseline on \`${{ github.event.pull_request.base.ref }}\` against the baseline committed in this PR:\n\n${summary}`; + // per-module marker so the two matrix jobs update their own comment + const marker = ``; + const isNew = process.env.BASELINE_STATE === 'new'; + const heading = isNew + ? `## Contract baseline added (\`${module}\`)` + : `## Contract baseline change detected (\`${module}\`)`; + const preamble = isNew + ? `This PR adds \`${module}/api-report/${artifact}.baseline.jar\`, the approved public API contract for this module.` + : `This PR updates \`${module}/api-report/${artifact}.baseline.jar\` (the approved public API contract). Here is exactly what it changes, comparing the baseline on \`${{ github.event.pull_request.base.ref }}\` against the baseline committed in this PR:`; + const body = `${marker}\n${heading}\n\n${preamble}\n\n${summary}`; const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/internal-release.yml b/.github/workflows/internal-release.yml index 0fea51db..808cc7e4 100644 --- a/.github/workflows/internal-release.yml +++ b/.github/workflows/internal-release.yml @@ -1,28 +1,45 @@ name: Publish module to the JFROG Artifactory on: push: + # '**' not '*.*': Actions glob '*' does not match '/', so '*.*' let slash + # tags (flowvault/v1.0.0) through and fired this branch-only workflow. tags-ignore: - - '*.*' + - '**' paths-ignore: - "*.md" branches: - - release/* - flowvault-release/* + - skyvault-release/* + # Legacy: predates the per-module naming, still maps to skyvault. + - release/* jobs: resolve-module: runs-on: ubuntu-latest + # Skip our own bump commit, or this loops: bump -> push -> release -> bump. + # PAT-authenticated pushes DO trigger workflows; GITHUB_TOKEN pushes do not. + # build-and-deploy needs this job, so skipping here skips the run. + if: ${{ !contains(github.event.head_commit.message, '[AUTOMATED]') }} outputs: module: ${{ steps.set-module.outputs.module }} steps: + # Explicit match, no catch-all: defaulting once published the wrong module. - name: Resolve module from branch name id: set-module + env: + BRANCH: ${{ github.ref_name }} run: | - if [[ "${{ github.ref_name }}" == flowvault-release/* ]]; then - echo "module=flowvault" >> "$GITHUB_OUTPUT" - else - echo "module=skyvault" >> "$GITHUB_OUTPUT" - fi + case "$BRANCH" in + flowvault-release/*) MODULE="flowvault" ;; + skyvault-release/*) MODULE="skyvault" ;; + release/*) MODULE="skyvault" ;; + *) + echo "::error::Branch '$BRANCH' does not map to a module." + exit 1 + ;; + esac + echo "Branch '$BRANCH' -> module '$MODULE'" + echo "module=$MODULE" >> "$GITHUB_OUTPUT" build-and-deploy: needs: resolve-module @@ -41,3 +58,5 @@ jobs: skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} + pat-actions: ${{ secrets.PAT_ACTIONS }} + test-credentials-file-string: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} diff --git a/.github/workflows/pr-flowvault.yml b/.github/workflows/pr-flowvault.yml new file mode 100644 index 00000000..d15c89e8 --- /dev/null +++ b/.github/workflows/pr-flowvault.yml @@ -0,0 +1,86 @@ +name: PR CI Checks (flowvault) + +# flowvault is a folder under main, alongside skyvault - not a branch. +# This workflow fires for PRs targeting main or a flowvault-release/* branch +# that actually touch flowvault or its common dependency, and only builds/tests +# those two modules. skyvault is covered by pr.yml, not here. + +on: + pull_request: + branches: [ "main", "flowvault-release/**" ] + paths: + - "flowvault/**" + - "common/**" + - "pom.xml" + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "11" + cache: "maven" + + # package (not verify/install) deliberately stops short of any japicmp + # contract gate bound to a module's `verify` phase - that's a separate + # check. This job only proves flowvault (and common) compile and package. + - name: Build flowvault + run: | + mvn -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \ + clean package -pl flowvault -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true + + test: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "11" + cache: "maven" + + - name: create-json + id: create-json + uses: jsdaniell/create-json@1.1.2 + with: + name: "credentials.json" + json: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} + + - name: create env + id: create-env + run: | + for dir in common flowvault; do + if [ -f "$dir/pom.xml" ]; then + cp credentials.json "$dir/credentials.json" + { + echo "SKYFLOW_CREDENTIALS=${{ secrets.SKYFLOW_CREDENTIALS }}" + echo "TEST_EXPIRED_TOKEN=${{ secrets.TEST_EXPIRED_TOKEN }}" + echo "TEST_REUSABLE_TOKEN=${{ secrets.TEST_REUSABLE_TOKEN }}" + } >> "$dir/.env" + fi + done + + # jacoco:report is already bound to the `test` phase in the root pom + # (prepare-agent + report executions), so `mvn test` alone regenerates + # coverage - no need to invoke jacoco:report again on the command line. + - name: Run flowvault unit tests + run: | + mvn -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \ + clean test -pl flowvault -am -Dmaven.javadoc.skip=true -Dgpg.skip=true + + - name: Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} + files: flowvault/target/site/jacoco/jacoco.xml + flags: unittests-flowvault + name: codecov-skyflow-java-flowvault + fail_ci_if_error: true + verbose: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 720e16d8..c1cb280d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,20 +1,82 @@ name: Public release + +# Triggered by publishing a GitHub Release, not a raw tag push: the Release +# carries both facts needed here - target_commitish (the branch picked in the +# UI; a tag records only a commit) and tag_name (module prefix + version). +# +# Beta and final share this workflow - 'release' events cannot be filtered by +# tag pattern, and both behaved identically downstream. Kind comes from the tag. + on: - push: - tags: '[0-9]+.[0-9]+.[0-9]+' + release: + types: [published] + jobs: + resolve-release: + runs-on: ubuntu-latest + outputs: + module: ${{ steps.parse.outputs.module }} + version: ${{ steps.parse.outputs.version }} + kind: ${{ steps.parse.outputs.kind }} + steps: + - name: Parse module, version and release kind from the tag + id: parse + env: + TAG: ${{ github.event.release.tag_name }} + BRANCH: ${{ github.event.release.target_commitish }} + run: | + # Expected: /v[-beta.N] e.g. flowvault/v1.0.0, + # skyvault/v2.1.2, flowvault/v1.0.0-beta.1 + if [[ ! "$TAG" =~ ^[a-z]+/v[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?$ ]]; then + echo "::error::Tag '$TAG' is not /v[-beta.N]." \ + "Examples: flowvault/v1.0.0, skyvault/v2.1.2, flowvault/v1.0.0-beta.1" + exit 1 + fi + + PREFIX="${TAG%%/*}" # flowvault/v1.0.0 -> flowvault + VERSION="${TAG#*/}" # flowvault/v1.0.0 -> v1.0.0 + VERSION="${VERSION#v}" # v1.0.0 -> 1.0.0 + + # Tag prefix -> module directory (both match the directory name). + case "$PREFIX" in + flowvault) MODULE="flowvault" ;; + skyvault) MODULE="skyvault" ;; + *) + echo "::error::Unknown module prefix '$PREFIX' in tag '$TAG'" + exit 1 + ;; + esac + + if [[ "$VERSION" == *-beta.* ]]; then KIND="beta"; else KIND="public"; fi + + if [ -z "$BRANCH" ]; then + echo "::error::Release has no target_commitish - cannot determine the release branch." + exit 1 + fi + + echo "Tag '$TAG' -> module='$MODULE' version='$VERSION' kind='$KIND' branch='$BRANCH'" + echo "module=$MODULE" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "kind=$KIND" >> "$GITHUB_OUTPUT" + build-and-deploy: + needs: resolve-release uses: ./.github/workflows/shared-build-and-deploy.yml with: - ref: ${{ github.ref_name }} + ref: ${{ github.event.release.tag_name }} server-id: central profile: maven-central - tag: 'public' + tag: ${{ needs.resolve-release.outputs.kind }} + module: ${{ needs.resolve-release.outputs.module }} + version: ${{ needs.resolve-release.outputs.version }} + release-branch: ${{ github.event.release.target_commitish }} secrets: server-username: ${{ secrets.CENTRAL_PUBLISHER_PORTAL_USERNAME }} server-password: ${{ secrets.CENTRAL_PUBLISHER_PORTAL_PASSWORD }} gpg-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} gpg-passphrase: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env - test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env - test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env + skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} + test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} + test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} + pat-actions: ${{ secrets.PAT_ACTIONS }} + test-credentials-file-string: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} diff --git a/.github/workflows/shared-build-and-deploy.yml b/.github/workflows/shared-build-and-deploy.yml index 842dbc58..a8ed9dd1 100644 --- a/.github/workflows/shared-build-and-deploy.yml +++ b/.github/workflows/shared-build-and-deploy.yml @@ -27,7 +27,44 @@ on: required: false type: string default: '' - + + version: + description: >- + Explicit version to release. Set by tag-triggered (beta/public) + callers, which parse it out of a /v tag - the raw + tag text is not itself a usable Maven version. When empty, the + version is derived as before (see the Bump Version step). + required: false + type: string + default: '' + + release-branch: + description: >- + Branch that receives the version-bump commit, for beta/public + releases. Supplied by the caller from the GitHub Release's + target_commitish - i.e. the branch the human picked in the release + UI. This used to be guessed by matching a branch tip to the tagged + commit, which broke whenever the tip moved on (re-runs always + failed) and silently picked an unrelated branch when several shared + a tip. A tag records only a commit, never a branch, so the branch + has to be supplied rather than inferred. + required: false + type: string + default: '' + + dry-run: + description: >- + Validate the release pipeline WITHOUT publishing anything. + Everything still runs - tag parsing, version resolution, the pom + bump, the full build, tests and GPG signing - but 'mvn verify' + replaces 'mvn deploy' (so the deploy plugin never runs at all, + rather than being asked politely to skip) and the version-bump + commit is not pushed. Publishing to Maven Central is immutable, + so this is the only safe way to exercise the public path. + required: false + type: boolean + default: false + secrets: server-username: required: true @@ -50,6 +87,14 @@ on: test-reusable-token: required: true + # Reusable workflows do NOT inherit caller secrets: anything used below must + # be declared here AND passed by every caller, or it resolves to "" silently. + pat-actions: + required: true + + test-credentials-file-string: + required: true + jobs: publish: runs-on: ubuntu-latest @@ -57,6 +102,9 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 + # Admin service-account PAT: persisted by checkout and reused for the + # version-bump push, which needs the ruleset's admin bypass. SK-2986. + token: ${{ secrets.pat-actions }} - name: Set up maven or jfrog repository uses: actions/setup-java@v4 @@ -69,18 +117,20 @@ jobs: gpg-private-key: ${{ secrets.gpg-key }} # Value of the GPG private key to import gpg-passphrase: GPG_PASSPHRASE # env variable for GPG private key passphrase - - name: Resolve Branch for the Tagged Commit - id: resolve-branch + - name: Validate release branch input if: ${{ inputs.tag == 'beta' || inputs.tag == 'public' }} run: | - TAG_COMMIT=$(git rev-list -n 1 ${{ github.ref_name }}) - BRANCH_NAME=$(git for-each-ref --points-at="$TAG_COMMIT" --format='%(refname:short)' refs/remotes/origin | grep -v '/HEAD$' | sed 's#^origin/##' | head -n 1) - if [ -z "$BRANCH_NAME" ]; then - echo "Error: Could not resolve branch for the tag." + if [ -z "${{ inputs.release-branch }}" ]; then + echo "::error::release-branch is required for ${{ inputs.tag }} releases." exit 1 fi - echo "Resolved Branch Name: $BRANCH_NAME" - echo "branch_name=$BRANCH_NAME" >> $GITHUB_ENV + # The tagged commit must actually be on that branch, otherwise the + # bump would land somewhere the release was never cut from. + if ! git merge-base --is-ancestor HEAD "origin/${{ inputs.release-branch }}"; then + echo "::error::Tagged commit is not an ancestor of origin/${{ inputs.release-branch }}." + exit 1 + fi + echo "Release branch: ${{ inputs.release-branch }}" - name: Get Previous tag id: previoustag @@ -88,13 +138,27 @@ jobs: with: fallback: 1.0.0 + # Version priority: inputs.version (beta/public, parsed from the tag) > + # the module's own pom (internal) > previoustag (unreachable; a safety net). + # Tags are a flat repo-wide namespace with no module awareness, which is why + # internal reads the pom - a tag lookup stamped a v3 version onto every module. - name: Bump Version + id: bump-version run: | - chmod +x ./scripts/bump_version.sh + chmod +x ./scripts/bump_version.sh ./scripts/current_module_version.sh + if [ -n "${{ inputs.version }}" ]; then + BASE_VERSION="${{ inputs.version }}" + elif ${{ inputs.tag == 'internal' }}; then + BASE_VERSION=$(./scripts/current_module_version.sh "${{ inputs.module }}") + else + BASE_VERSION="${{ steps.previoustag.outputs.tag }}" + fi + echo "base_version=$BASE_VERSION" >> "$GITHUB_OUTPUT" + if ${{ inputs.tag == 'internal' }}; then - ./scripts/bump_version.sh "${{ steps.previoustag.outputs.tag }}" "$(git rev-parse --short "$GITHUB_SHA")" "${{ inputs.module }}" + ./scripts/bump_version.sh "$BASE_VERSION" "$(git rev-parse --short "$GITHUB_SHA")" "${{ inputs.module }}" else - ./scripts/bump_version.sh "${{ steps.previoustag.outputs.tag }}" "" "${{ inputs.module }}" + ./scripts/bump_version.sh "$BASE_VERSION" "" "${{ inputs.module }}" fi - name: Commit changes @@ -103,17 +167,33 @@ jobs: git config user.email ${{ github.actor }}@users.noreply.github.com if [[ "${{ inputs.tag }}" == "beta" || "${{ inputs.tag }}" == "public" ]]; then - git checkout ${{ env.branch_name }} + git checkout ${{ inputs.release-branch }} fi git add ${{ inputs.module }}/pom.xml + + # Nothing staged = pom already at this version (normal if set before + # tagging). That is success; a bare 'git commit' would exit 1 here. + if git diff --cached --quiet; then + echo "::notice::pom already at the target version - nothing to commit" + exit 0 + fi + if [[ "${{ inputs.tag }}" == "internal" ]]; then - git commit -m "[AUTOMATED] Private Release ${{ steps.previoustag.outputs.tag }}-dev-$(git rev-parse --short $GITHUB_SHA)" - git push origin ${{ github.ref_name }} -f + git commit -m "[AUTOMATED] Private Release ${{ steps.bump-version.outputs.base_version }}-dev-$(git rev-parse --short $GITHUB_SHA)" + if [[ "${{ inputs.dry-run }}" == "true" ]]; then + echo "::notice::DRY RUN - not pushing the version-bump commit" + else + git push origin ${{ github.ref_name }} -f + fi fi if [[ "${{ inputs.tag }}" == "beta" || "${{ inputs.tag }}" == "public" ]]; then - git commit -m "[AUTOMATED] Public Release - ${{ steps.previoustag.outputs.tag }}" - git push origin ${{ env.branch_name }} + git commit -m "[AUTOMATED] Public Release - ${{ steps.bump-version.outputs.base_version }}" + if [[ "${{ inputs.dry-run }}" == "true" ]]; then + echo "::notice::DRY RUN - not pushing the version-bump commit" + else + git push origin ${{ inputs.release-branch }} + fi fi - name: Create env @@ -129,7 +209,7 @@ jobs: uses: jsdaniell/create-json@1.1.2 with: name: "credentials.json" - json: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} + json: ${{ secrets.test-credentials-file-string }} - name: Distribute test fixtures to modules run: | @@ -140,16 +220,25 @@ jobs: cp credentials.json "$module/credentials.json" done + # Dry run uses 'verify', not 'deploy', so the deploy plugin and the + # central-publishing extension are never invoked at all. + # + # Javadoc is skipped for internal builds only - doclint on JDK 11 fails on + # comment nits. Beta/public must NOT skip it: Sonatype rejects a bundle + # with no -javadoc.jar. - name: Publish package run: | - if [[ "${{ inputs.tag }}" == "internal" ]]; then - # Javadoc is skipped for internal (JFrog) builds only: these are dev snapshots that - # nobody browses docs for, and strict doclint on the pinned JDK 11 fails the release - # on HTML nits in comments. Beta/public below must keep it — Sonatype requires the - # -javadoc.jar, so any doclint error there is a real break to fix at the source. + if [[ "${{ inputs.dry-run }}" == "true" ]]; then + echo "::notice::DRY RUN - running 'verify' instead of 'deploy'; nothing will be published" + if [[ "${{ inputs.tag }}" == "internal" ]]; then + mvn --batch-mode -pl ${{ inputs.module }} -am verify -P jfrog -DskipTests -Dmaven.javadoc.skip=true + else + mvn --batch-mode -pl ${{ inputs.module }} -am verify -P ${{ inputs.profile }} + fi + elif [[ "${{ inputs.tag }}" == "internal" ]]; then mvn --batch-mode -pl ${{ inputs.module }} -am deploy -P jfrog -DskipTests -Dmaven.javadoc.skip=true elif [[ "${{ inputs.tag }}" == "beta" || "${{ inputs.tag }}" == "public" ]]; then - mvn --batch-mode -pl ${{ inputs.module }} -am deploy -P ${{ inputs.profile }} -Dmaven.javadoc.skip=true + mvn --batch-mode -pl ${{ inputs.module }} -am deploy -P ${{ inputs.profile }} fi env: diff --git a/README.md b/README.md index 96b1446c..a5bce548 100644 --- a/README.md +++ b/README.md @@ -1,3149 +1,37 @@ # Skyflow Java -> **This is the current, recommended version of the Skyflow SDK.** V2.1.0 brings flexible auth, multi-vault support, builder patterns, native data types, and rich error diagnostics. -> -> Migrating from v1? See the **[Migration Guide](docs/migrate_to_v2.md)** for step-by-step instructions. V1 is in maintenance mode and will reach End of Life on October 31, 2026. - -The Skyflow Java SDK is designed to help with integrating Skyflow into a Java backend. +This repository hosts Skyflow's Java SDKs for integrating Skyflow into a Java backend. It's a single Maven reactor with more than one published artifact — pick the package that matches what you need below. [![CI](https://img.shields.io/static/v1?label=CI&message=passing&color=green?style=plastic&logo=github)](https://github.com/skyflowapi/skyflow-java/actions) -[![GitHub release](https://img.shields.io/github/v/release/skyflowapi/skyflow-java.svg)](https://mvnrepository.com/artifact/com.skyflow/skyflow-java) +[![GitHub release](https://img.shields.io/github/v/release/skyflowapi/skyflow-java.svg)](https://github.com/skyflowapi/skyflow-java/releases) [![License](https://img.shields.io/github/license/skyflowapi/skyflow-java)](https://github.com/skyflowapi/skyflow-java/blob/main/LICENSE) -# Table of Contents - -- [Table of Contents](#table-of-contents) -- [Overview](#overview) -- [Install](#install) - - [Requirements](#requirements) - - [Configuration](#configuration) - - [Gradle users](#gradle-users) - - [Maven users](#maven-users) -- [API Reference](docs/api_reference.md) -- [Migration from v1 to v2](docs/migrate_to_v2.md) -- [Quickstart](#quickstart) - - [Authenticate](#authenticate) - - [Initialize the client](#initialize-the-client) - - [Insert data into the vault](#insert-data-into-the-vault) -- [Vault](#vault) - - [VaultController](#vaultcontroller) - - [Insert data into the vault](#insert-data-into-the-vault-1) - - [Detokenize](#detokenize) - - [DetokenizeRecordResponse](#detokenizerecordresponse) - - [Tokenize](#tokenize) - - [Get](#get) - - [Get by skyflow IDS](#get-by-skyflow-ids) - - [Get tokens](#get-tokens) - - [Get by column name and column values](#get-by-column-name-and-column-values) - - [Redaction types](#redaction-types) - - [Update](#update) - - [Delete](#delete) - - [Query](#query) - - [Upload File](#upload-file) - -- [Detect](#detect) - - [Deidentify Text](#deidentify-text) - - [Reidentify Text](#reidentify-text) - - [Deidentify File](#deidentify-file) - - [Get Run](#get-run) - - [Detect response types](#detect-response-types) - - [Detect enums](#detect-enums) -- [Connections](#connections) - - [ConnectionController](#connectioncontroller) - - [Invoke a connection](#invoke-a-connection) -- [Client Management](#client-management) -- [Authenticate with bearer tokens](#authenticate-with-bearer-tokens) - - [Generate a bearer token](#generate-a-bearer-token) - - [Generate bearer tokens with context](#generate-bearer-tokens-with-context) - - [Generate scoped bearer tokens](#generate-scoped-bearer-tokens) - - [Generate signed data tokens](#generate-signed-data-tokens) - - [Bearer token expiry edge case](#bearer-token-expiry-edge-case) -- [Error Handling](#error-handling) - - [Catching SkyflowException](#catching-skyflowexception) - - [SkyflowException properties](#skyflowexception-properties) -- [Logging](#logging) -- [Reporting a Vulnerability](#reporting-a-vulnerability) - -# Overview - -- Authenticate using a Skyflow service account and generate bearer tokens for secure access. -- Perform Vault API operations such as inserting, retrieving, and tokenizing sensitive data with ease. -- Invoke connections to third-party APIs without directly handling sensitive data, ensuring compliance and data protection. - -> [!TIP] -> Looking for the full list of request builder methods, response getters, enums, helper class APIs, and service-account utilities? See the **[API Reference](docs/api_reference.md)**. - -# Install - -## Requirements - -- Java 8 and above (tested with Java 8) - -## Configuration - ---- - -### Gradle users - -Add this dependency to your project's `build.gradle` file: - -``` -implementation 'com.skyflow:skyflow-java:2.0.0' -``` - -### Maven users - -Add this dependency to your project's `pom.xml` file: - -```xml - - com.skyflow - skyflow-java - 2.0.0 - -``` - ---- - -# Migrate from v1 to v2 - -Upgrading from v1? See the dedicated migration guide: **[docs/migrate_to_v2.md](docs/migrate_to_v2.md)** - -# Quickstart - -Get started quickly with the essential steps: authenticate, initialize the client, and perform a basic vault operation. This section provides a minimal setup to help you integrate the SDK efficiently. - -### Authenticate - -You can use an API key to authenticate and authorize requests to an API. For authenticating via bearer tokens and different supported bearer token types, refer to the [Authenticate with bearer tokens](#authenticate-with-bearer-tokens) section. - -```java -// create a new credentials object -Credentials credentials = new Credentials(); -credentials.setApiKey(""); // add your API key in credentials -``` - -### Initialize the client - -To get started, you must first initialize the skyflow client. While initializing the skyflow client, you can specify different types of credentials. - -1. **API keys** - A unique identifier used to authenticate and authorize requests to an API. - -2. **Bearer tokens** - A temporary access token used to authenticate API requests, typically included in the Authorization header. - -3. **Service account credentials file path** - The file path pointing to a JSON file containing credentials for a service account, used for secure API access. - -4. **Service account credentials string (JSON formatted)** - A JSON-formatted string containing service account credentials, often used as an alternative to a file for programmatic authentication. - -Note: Only one type of credential can be used at a time. If multiple credentials are provided, the last one added will take precedence. - -```java -import com.skyflow.Skyflow; -import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; -import com.skyflow.enums.Env; -import com.skyflow.enums.LogLevel; -import com.skyflow.errors.SkyflowException; - -/** - * Example program to initialize the Skyflow client with various configurations. - * The Skyflow client facilitates secure interactions with the Skyflow vault, - * such as securely managing sensitive data. - */ -public class InitSkyflowClient { - public static void main(String[] args) throws SkyflowException { - // Step 1: Define the primary credentials for authentication. - // Note: Only one type of credential can be used at a time. You can choose between: - // - API key - // - Bearer token - // - A credentials string (JSON-formatted) - // - A file path to a credentials file. - - // Initialize primary credentials using a Bearer token for authentication. - Credentials primaryCredentials = new Credentials(); - primaryCredentials.setToken(""); // Replace with your actual authentication token. - - // Step 2: Configure the primary vault details. - // VaultConfig stores all necessary details to connect to a specific Skyflow vault. - VaultConfig primaryConfig = new VaultConfig(); - primaryConfig.setVaultId(""); // Replace with your primary vault's ID. - primaryConfig.setClusterId(""); // Replace with the cluster ID (part of the vault URL, e.g., https://{clusterId}.vault.skyflowapis.com). - primaryConfig.setEnv(Env.PROD); // Set the environment (PROD, SANDBOX, STAGE, DEV). - primaryConfig.setCredentials(primaryCredentials); // Attach the primary credentials to this vault configuration. - - // Step 3: Create credentials as a JSON object (if a Bearer Token is not provided). - // Demonstrates an alternate approach to authenticate with Skyflow using a credentials object. - JsonObject credentialsObject = new JsonObject(); - credentialsObject.addProperty("clientId", ""); // Replace with your Client ID. - credentialsObject.addProperty("clientName", ""); // Replace with your Client Name. - credentialsObject.addProperty("tokenUri", ""); // Replace with the Token URI. - credentialsObject.addProperty("keyId", ""); // Replace with your Key ID. - credentialsObject.addProperty("privateKey", ""); // Replace with your Private Key. - - // Step 4: Convert the JSON object to a string and use it as credentials. - // This approach allows the use of dynamically generated or pre-configured credentials. - Credentials skyflowCredentials = new Credentials(); - skyflowCredentials.setCredentialsString(credentialsObject.toString()); // Converts JSON object to string for use as credentials. - - // Step 5: Define secondary credentials (API key-based authentication as an example). - // Demonstrates a different type of authentication mechanism for Skyflow vaults. - Credentials secondaryCredentials = new Credentials(); - secondaryCredentials.setApiKey(""); // Replace with your API Key for authentication. - - // Step 6: Configure the secondary vault details. - // A secondary vault configuration can be used for operations involving multiple vaults. - VaultConfig secondaryConfig = new VaultConfig(); - secondaryConfig.setVaultId(""); // Replace with your secondary vault's ID. - secondaryConfig.setClusterId(""); // Replace with the corresponding cluster ID. - secondaryConfig.setEnv(Env.SANDBOX); // Set the environment for this vault. - secondaryConfig.setCredentials(secondaryCredentials); // Attach the secondary credentials to this configuration. - - // Step 7: Define tertiary credentials using a path to a credentials JSON file. - // This method demonstrates an alternative authentication method. - Credentials tertiaryCredentials = new Credentials(); - tertiaryCredentials.setPath(""); // Replace with the path to your credentials file. - - // Step 8: Configure the tertiary vault details. - VaultConfig tertiaryConfig = new VaultConfig(); - tertiaryConfig.setVaultId(""); // Replace with the tertiary vault ID. - tertiaryConfig.setClusterId(""); // Replace with the corresponding cluster ID. - tertiaryConfig.setEnv(Env.STAGE); // Set the environment for this vault. - tertiaryConfig.setCredentials(tertiaryCredentials); // Attach the tertiary credentials. - - // Step 9: Build and initialize the Skyflow client. - // Skyflow client is configured with multiple vaults and credentials. - Skyflow skyflowClient = Skyflow.builder() - .setLogLevel(LogLevel.INFO) // Set log level for debugging or monitoring purposes. - .addVaultConfig(primaryConfig) // Add the primary vault configuration. - .addVaultConfig(secondaryConfig) // Add the secondary vault configuration. - .addVaultConfig(tertiaryConfig) // Add the tertiary vault configuration. - .addSkyflowCredentials(skyflowCredentials) // Add JSON-formatted credentials if applicable. - .build(); - - // The Skyflow client is now fully initialized. - // Use the `skyflowClient` object to perform secure operations such as: - // - Inserting data - // - Retrieving data - // - Deleting data - // within the configured Skyflow vaults. - } -} -``` - -Notes: - -- If both Skyflow common credentials and individual credentials at the configuration level are specified, the individual credentials at the configuration level will take precedence. -- If neither Skyflow common credentials nor individual configuration-level credentials are provided, the SDK attempts to retrieve credentials from the `SKYFLOW_CREDENTIALS` environment variable. -- All Vault operations require a client instance. -- `Credentials.setContext()` accepts either a `String` or a `Map` for context-aware authorization. See [Generate bearer tokens with context](#generate-bearer-tokens-with-context) for full usage. - -### Insert data into the vault - -To insert data into your vault, use the `insert` method. The `InsertRequest` class creates an insert request, which includes the values to be inserted as a list of records. Below is a simple example to get started. For advanced options, check out [Insert data into the vault](#insert-data-into-the-vault-1) section. - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.InsertRequest; -import com.skyflow.vault.data.InsertResponse; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * This example demonstrates how to insert sensitive data (e.g., card information) into a Skyflow vault using the Skyflow client. - * - * 1. Initializes the Skyflow client. - * 2. Prepares a record with sensitive data (e.g., card number and cardholder name). - * 3. Creates an insert request for inserting the data into the Skyflow vault. - * 4. Prints the response of the insert operation. - */ -public class InsertExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize data to be inserted into the Skyflow vault - ArrayList> insertData = new ArrayList<>(); - - // Create a HashMap for a single record with card number and cardholder name as fields - HashMap insertRecord = new HashMap<>(); - insertRecord.put("card_number", "4111111111111111"); // Replace with actual card number (sensitive data) - insertRecord.put("cardholder_name", "john doe"); // Replace with actual cardholder name (sensitive data) - - // Add the created record to the list of data to be inserted - insertData.add(insertRecord); - - // Step 2: Build the InsertRequest object with the table name and data to insert - InsertRequest insertRequest = InsertRequest.builder() - .table("table1") // Specify the table in the vault where the data will be inserted - .values(insertData) // Attach the data (records) to be inserted - .returnTokens(true) // Specify if tokens should be returned upon successful insertion - .build(); // Build the insert request object - - // Step 3: Perform the insert operation using the Skyflow client - InsertResponse insertResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").insert(insertRequest); - // Replace the vault ID "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID - - // Step 4: Print the response from the insert operation - System.out.println(insertResponse); - } catch (SkyflowException e) { - // Step 5: Handle any exceptions that may occur during the insert operation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the stack trace for debugging purposes - } - } -} -``` - -Skyflow returns tokens for the record that was just inserted. - -```json -{ - "insertedFields": [ - { - "card_number": "5484-7829-1702-9110", - "requestIndex": "0", - "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1", - "cardholder_name": "b2308e2a-c1f5-469b-97b7-1f193159399b" - } - ], - "errors": [] -} -``` - -> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. - -# Vault - -The [Vault](https://github.com/skyflowapi/skyflow-java/tree/main/samples/src/main/java/com/example/vault) module performs operations on the vault, including inserting records, detokenizing tokens, and retrieving tokens associated with a `skyflow_id`. - -## VaultController - -`VaultController` is the class returned by `skyflowClient.vault()` and `skyflowClient.vault(vaultId)`. All vault operations are called on this object. - -```java -// Uses the default (first configured) vault -VaultController vault = skyflowClient.vault(); - -// Uses a specific vault by ID -VaultController vault = skyflowClient.vault(""); -``` - -**Methods:** - -| Method | Parameters | Returns | Description | -|--------|-----------|---------|-------------| -| `insert(InsertRequest)` | [`InsertRequest`](docs/api_reference.md#insertrequest) | [`InsertResponse`](docs/api_reference.md#insertresponse) | Insert one or more records | -| `detokenize(DetokenizeRequest)` | [`DetokenizeRequest`](docs/api_reference.md#detokenizerequest) | [`DetokenizeResponse`](docs/api_reference.md#detokenizeresponse) | Detokenize tokens to their original values | -| `tokenize(TokenizeRequest)` | [`TokenizeRequest`](docs/api_reference.md#tokenizerequest) | [`TokenizeResponse`](docs/api_reference.md#tokenizeresponse) | Tokenize sensitive values | -| `get(GetRequest)` | [`GetRequest`](docs/api_reference.md#getrequest) | [`GetResponse`](docs/api_reference.md#getresponse) | Retrieve records by Skyflow ID or column value | -| `update(UpdateRequest)` | [`UpdateRequest`](docs/api_reference.md#updaterequest) | [`UpdateResponse`](docs/api_reference.md#updateresponse) | Update a record by Skyflow ID | -| `delete(DeleteRequest)` | [`DeleteRequest`](docs/api_reference.md#deleterequest) | [`DeleteResponse`](docs/api_reference.md#deleteresponse) | Delete records by Skyflow ID | -| `query(QueryRequest)` | [`QueryRequest`](docs/api_reference.md#queryrequest) | [`QueryResponse`](docs/api_reference.md#queryresponse) | Execute a SQL query | -| `uploadFile(FileUploadRequest)` | [`FileUploadRequest`](docs/api_reference.md#fileuploadrequest) | [`FileUploadResponse`](docs/api_reference.md#fileuploadresponse) | Upload a file to a vault column | - -All methods throw `SkyflowException` on error. - -## Insert data into the vault - -Apart from using the `insert` method to insert data into your vault covered in [Quickstart](#quickstart), you can also specify options in [`InsertRequest`](docs/api_reference.md#insertrequest), such as returning tokenized data, upserting records, or continuing the operation in case of errors. Returns an [`InsertResponse`](docs/api_reference.md#insertresponse). - -### Construct an insert request - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.InsertRequest; -import com.skyflow.vault.data.InsertResponse; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Example program to demonstrate inserting data into a Skyflow vault, along with corresponding InsertRequest schema. - * - */ -public class InsertSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Prepare the data to be inserted into the Skyflow vault - ArrayList> insertData = new ArrayList<>(); - - // Create the first record with field names and their respective values - HashMap insertRecord1 = new HashMap<>(); - insertRecord1.put("", ""); // Replace with actual field name and value - insertRecord1.put("", ""); // Replace with actual field name and value - - // Create the second record with field names and their respective values - HashMap insertRecord2 = new HashMap<>(); - insertRecord2.put("", ""); // Replace with actual field name and value - insertRecord2.put("", ""); // Replace with actual field name and value - - // Add the records to the list of data to be inserted - insertData.add(insertRecord1); - insertData.add(insertRecord2); - - // Step 2: Build an InsertRequest object with the table name and the data to insert - InsertRequest insertRequest = InsertRequest.builder() - .table("") // Replace with the actual table name in your Skyflow vault - .values(insertData) // Attach the data to be inserted - .build(); - - // Step 3: Use the Skyflow client to perform the insert operation - InsertResponse insertResponse = skyflowClient.vault("").insert(insertRequest); - // Replace with your actual vault ID - - // Print the response from the insert operation - System.out.println("Insert Response: " + insertResponse); - } catch (SkyflowException e) { - // Step 4: Handle any exceptions that occur during the insert operation - System.out.println("Error occurred while inserting data: "); - e.printStackTrace(); // Print the stack trace for debugging - } - } -} -``` - -### Insert call [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/InsertExample.java) with `continueOnError` option - -The `continueOnError` flag is a boolean that determines whether insert operation should proceed despite encountering partial errors. Set to `true` to allow the process to continue even if some errors occur. - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.InsertRequest; -import com.skyflow.vault.data.InsertResponse; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * This example demonstrates how to insert multiple records into a Skyflow vault using the Skyflow client. - * - * 1. Initializes the Skyflow client. - * 2. Prepares multiple records with sensitive data (e.g., card number and cardholder name). - * 3. Creates an insert request with the records to insert into the Skyflow vault. - * 4. Specifies options to continue on error and return tokens. - * 5. Prints the response of the insert operation. - */ -public class InsertExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list to hold the data records to be inserted into the vault - ArrayList> insertData = new ArrayList<>(); - - // Step 2: Create the first record with card number and cardholder name - HashMap insertRecord1 = new HashMap<>(); - insertRecord1.put("card_number", "4111111111111111"); // Replace with actual card number (sensitive data) - insertRecord1.put("cardholder_name", "john doe"); // Replace with actual cardholder name (sensitive data) - - // Step 3: Create the second record with card number and cardholder name - HashMap insertRecord2 = new HashMap<>(); - insertRecord2.put("card_number", "4111111111111111"); // Ensure field name matches ("card_number") - insertRecord2.put("cardholder_name", "jane doe"); // Replace with actual cardholder name (sensitive data) - - // Step 4: Add the records to the insertData list - insertData.add(insertRecord1); - insertData.add(insertRecord2); - - // Step 5: Build the InsertRequest object with the data records to insert - InsertRequest insertRequest = InsertRequest.builder() - .table("table1") // Specify the table in the vault where data will be inserted - .values(insertData) // Attach the data records to be inserted - .returnTokens(true) // Specify if tokens should be returned upon successful insertion - .continueOnError(true) // Specify to continue inserting records even if an error occurs for some records - .build(); // Build the insert request object - - // Step 6: Perform the insert operation using the Skyflow client - InsertResponse insertResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").insert(insertRequest); - // Replace the vault ID "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID - - // Step 7: Print the response from the insert operation - System.out.println(insertResponse); - } catch (SkyflowException e) { - // Step 8: Handle any exceptions that may occur during the insert operation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the stack trace for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "insertedFields": [ - { - "card_number": "5484-7829-1702-9110", - "requestIndex": "0", - "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1", - "cardholder_name": "b2308e2a-c1f5-469b-97b7-1f193159399b" - } - ], - "errors": [ - { - "requestIndex": "1", - "error": "Insert failed. Column card_number is invalid. Specify a valid column." - } - ] -} -``` - -> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. - -### Insert call example with `upsert` option - -An upsert operation checks for a record based on a unique column's value. If a match exists, the record is updated; otherwise, a new record is inserted. - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.InsertRequest; -import com.skyflow.vault.data.InsertResponse; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * This example demonstrates how to insert or upsert a record into a Skyflow vault using the Skyflow client, with the option to return tokens. - * - * 1. Initializes the Skyflow client. - * 2. Prepares a record to insert or upsert (e.g., cardholder name). - * 3. Creates an insert request with the data to be inserted or upserted into the Skyflow vault. - * 4. Specifies the field (cardholder_name) for upsert operations. - * 5. Prints the response of the insert or upsert operation. - */ -public class UpsertExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list to hold the data records for the insert/upsert operation - ArrayList> upsertData = new ArrayList<>(); - - // Step 2: Create a record with the field 'cardholder_name' to insert or upsert - HashMap upsertRecord = new HashMap<>(); - upsertRecord.put("cardholder_name", "jane doe"); // Replace with the actual cardholder name - - // Step 3: Add the record to the upsertData list - upsertData.add(upsertRecord); - - // Step 4: Build the InsertRequest object with the upsertData - InsertRequest insertRequest = InsertRequest.builder() - .table("table1") // Specify the table in the vault where data will be inserted/upserted - .values(upsertData) // Attach the data records to be inserted/upserted - .returnTokens(true) // Specify if tokens should be returned upon successful operation - .upsert("cardholder_name") // Specify the field to be used for upsert operations (e.g., cardholder_name) - .build(); // Build the insert request object - - // Step 5: Perform the insert/upsert operation using the Skyflow client - InsertResponse insertResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").insert(insertRequest); - // Replace the vault ID "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID - - // Step 6: Print the response from the insert/upsert operation - System.out.println(insertResponse); - } catch (SkyflowException e) { - // Step 7: Handle any exceptions that may occur during the insert/upsert operation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the stack trace for debugging purposes - } - } -} -``` - -Skyflow returns tokens, with `upsert` support, for the record you just inserted. - -```json -{ - "insertedFields": [ - { - "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1", - "cardholder_name": "73ce45ce-20fd-490e-9310-c1d4f603ee83" - } - ], - "errors": [] -} -``` - -## Detokenize - -To retrieve tokens from your vault, use the `detokenize` method. [`DetokenizeRequest`](docs/api_reference.md#detokenizerequest) requires a list of detokenization data as input. Returns a [`DetokenizeResponse`](docs/api_reference.md#detokenizeresponse). - -### Construct a detokenize request - -Each entry in the detokenize list is a [`DetokenizeData`](docs/api_reference.md#detokenizedata) object pairing a token with its desired redaction type. See the [API Reference](docs/api_reference.md#detokenizerequest) for all `DetokenizeRequest` builder options. - -```java -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.tokens.DetokenizeRequest; -import com.skyflow.vault.tokens.DetokenizeResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to detokenize sensitive data from tokens stored in a Skyflow vault, along with corresponding DetokenizeRequest schema. - * - */ -public class DetokenizeSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of tokens to be detokenized (replace with actual tokens) - ArrayList detokenizeData1 = new ArrayList<>(); - DetokenizeData detokenizeDataRecord1 = new DetokenizeData("", RedactionType.PLAIN_TEXT); // Replace with a token to detokenize with PLAIN_TEXT redaction - DetokenizeData detokenizeDataRecord2 = new DetokenizeData("", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction - detokenizeData1.add(detokenizeDataRecord1); - detokenizeData1.add(detokenizeDataRecord2); - - // Step 2: Create the DetokenizeRequest object with the tokens and redaction type - DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() - .detokenizeData(detokenizeData1) // Specify detokenize data with specified redaction types - .continueOnError(true) // Continue even if one token cannot be detokenized - .build(); // Build the detokenization request - - // Step 3: Call the Skyflow vault to detokenize the provided tokens - DetokenizeResponse detokenizeResponse = skyflowClient.vault("").detokenize(detokenizeRequest); - // Replace with your actual Skyflow vault ID - - // Step 4: Print the detokenization response, which contains the detokenized data - System.out.println(detokenizeResponse); - } catch (SkyflowException e) { - // Step 5: Handle any errors that occur during the detokenization process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Notes: - -- `redactionType` defaults to [`RedactionType.PLAIN_TEXT`](#redaction-types). -- `continueOnError` defaults to `true`. - -### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/DetokenizeExample.java) of a detokenize call: - -```java -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.tokens.DetokenizeRequest; -import com.skyflow.vault.tokens.DetokenizeResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to detokenize sensitive data from tokens stored in a Skyflow vault. - * - * 1. Initializes the Skyflow client. - * 2. Creates a list of tokens (e.g., credit card tokens) that represent the sensitive data. - * 3. Builds a detokenization request using the provided tokens and specifies how the redacted data should be returned. - * 4. Calls the Skyflow vault to detokenize the tokens and retrieves the detokenized data. - * 5. Prints the detokenization response, which contains the detokenized values or errors. - */ -public class DetokenizeExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of tokens to be detokenized (replace with actual token values) - ArrayList detokenizeData1 = new ArrayList<>(); - DetokenizeData detokenizeDataRecord1 = new DetokenizeData("9738-1683-0486-1480", RedactionType.PLAIN_TEXT); // Replace with a token to detokenize with PLAIN_TEXT redaction - DetokenizeData detokenizeDataRecord2 = new DetokenizeData("6184-6357-8409-6668", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction - detokenizeData1.add(detokenizeDataRecord1); - detokenizeData1.add(detokenizeDataRecord2); - - // Step 2: Create the DetokenizeRequest object with the tokens and redaction type - DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() - .detokenizeData(detokenizeData1) // Specify detokenize data with specified redaction types - .continueOnError(true) // Continue even if one token cannot be detokenized - .build(); // Build the detokenization request - - // Step 3: Call the Skyflow vault to detokenize the provided tokens - DetokenizeResponse detokenizeResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").detokenize(detokenizeRequest); - // Replace "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID - - // Step 4: Print the detokenization response, which contains the detokenized data - System.out.println(detokenizeResponse); - } catch (SkyflowException e) { - // Step 5: Handle any errors that occur during the detokenization process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "detokenizedFields": [{ - "token": "9738-1683-0486-1480", - "value": "4111111111111115", - "type": "STRING", - }, { - "token": "6184-6357-8409-6668", - "value": "4111111111111119", - "type": "STRING", - }], - "errors": [] -} - -``` - -### DetokenizeRecordResponse - -`DetokenizeResponse.getDetokenizedFields()` and `DetokenizeResponse.getErrors()` each return a `List`. Use this class to read individual token results: - -```java -DetokenizeResponse detokenizeResponse = skyflowClient.vault("").detokenize(detokenizeRequest); - -for (DetokenizeRecordResponse record : detokenizeResponse.getDetokenizedFields()) { - System.out.println("Token : " + record.getToken()); - System.out.println("Value : " + record.getValue()); - System.out.println("Type : " + record.getType()); - System.out.println("ReqID : " + record.getRequestId()); -} - -for (DetokenizeRecordResponse err : detokenizeResponse.getErrors()) { - System.out.println("Failed token : " + err.getToken()); - System.out.println("Error : " + err.getError()); -} -``` - -See [`DetokenizeRecordResponse`](docs/api_reference.md#detokenizerecordresponse) in the API Reference for the full attribute list. - -### An example of a detokenize call with `continueOnError` option: - -```java -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.tokens.DetokenizeRequest; -import com.skyflow.vault.tokens.DetokenizeResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to detokenize sensitive data (e.g., credit card numbers) from tokens in a Skyflow vault. - * - * 1. Initializes the Skyflow client. - * 2. Creates a list of tokens (e.g., credit card tokens) to be detokenized. - * 3. Builds a detokenization request with the tokens and specifies the redaction type for the detokenized data. - * 4. Calls the Skyflow vault to detokenize the tokens and retrieves the detokenized data. - * 5. Prints the detokenization response, which includes the detokenized values or errors. - */ -public class DetokenizeExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of tokens to be detokenized (replace with actual token values) - // Step 1: Initialize a list of tokens to be detokenized (replace with actual token values) - ArrayList detokenizeData1 = new ArrayList<>(); - DetokenizeData detokenizeDataRecord1 = new DetokenizeData("9738-1683-0486-1480", RedactionType.PLAIN_TEXT); // Replace with a token to detokenize with PLAIN_TEXT redaction - DetokenizeData detokenizeDataRecord2 = new DetokenizeData("6184-6357-8409-6668", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction - DetokenizeData detokenizeDataRecord2 = new DetokenizeData("4914-9088-2814-384", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction - - detokenizeData1.add(detokenizeDataRecord1); - detokenizeData1.add(detokenizeDataRecord2); - - // Step 2: Create the DetokenizeRequest object with the tokens and redaction type - DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() - .detokenizeData(detokenizeData1) // Specify detokenize data with specified redaction types - .continueOnError(true) // Continue even if one token cannot be detokenized - .build(); // Build the detokenization request - - // Step 3: Call the Skyflow vault to detokenize the provided tokens - DetokenizeResponse detokenizeResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").detokenize(detokenizeRequest); - // Replace "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID - - // Step 4: Print the detokenization response, which contains the detokenized data or errors - System.out.println(detokenizeResponse); - } catch (SkyflowException e) { - // Step 5: Handle any errors that occur during the detokenization process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "detokenizedFields": [{ - "token": "9738-1683-0486-1480", - "value": "4111111111111115", - "type": "STRING", - }, { - "token": "6184-6357-8409-6668", - "value": "4111111111111119", - "type": "STRING", - }], - "errors": [{ - "token": "4914-9088-2814-384", - "error": "Token Not Found", - }] -} -``` - -## Tokenize - -Tokenization replaces sensitive data with unique identifier tokens. This approach protects sensitive information by securely storing the original data while allowing the use of tokens within your application. - -To tokenize data, use the `tokenize` method. [`TokenizeRequest`](docs/api_reference.md#tokenizerequest) accepts a list of [`ColumnValue`](docs/api_reference.md#columnvalue) objects. Returns a [`TokenizeResponse`](docs/api_reference.md#tokenizeresponse). - -### Construct a tokenize request - -Each entry in the tokenize list is a [`ColumnValue`](docs/api_reference.md#columnvalue) object pairing a value with its column group. See the [API Reference](docs/api_reference.md#tokenizerequest) for all `TokenizeRequest` builder options. - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.tokens.ColumnValue; -import com.skyflow.vault.tokens.TokenizeRequest; -import com.skyflow.vault.tokens.TokenizeResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to tokenize sensitive data (e.g., credit card information) using the Skyflow client, along with corresponding TokenizeRequest schema. - * - */ -public class TokenizeSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of column values to be tokenized (replace with actual sensitive data) - ArrayList columnValues = new ArrayList<>(); - - // Step 2: Create column values for each sensitive data field (e.g., card number and cardholder name) - ColumnValue columnValue1 = ColumnValue.builder().value("").columnGroup("").build(); // Replace and with actual data - ColumnValue columnValue2 = ColumnValue.builder().value("").columnGroup("").build(); // Replace and with actual data - - // Add the created column values to the list - columnValues.add(columnValue1); - columnValues.add(columnValue2); - - // Step 3: Build the TokenizeRequest with the column values - TokenizeRequest tokenizeRequest = TokenizeRequest.builder().values(columnValues).build(); - - // Step 4: Call the Skyflow vault to tokenize the sensitive data - TokenizeResponse tokenizeResponse = skyflowClient.vault("").tokenize(tokenizeRequest); - // Replace with your actual Skyflow vault ID - - // Step 5: Print the tokenization response, which contains the generated tokens or errors - System.out.println(tokenizeResponse); - } catch (SkyflowException e) { - // Step 6: Handle any errors that occur during the tokenization process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/TokenizeExample.java) of Tokenize call: - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.tokens.ColumnValue; -import com.skyflow.vault.tokens.TokenizeRequest; -import com.skyflow.vault.tokens.TokenizeResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to tokenize sensitive data (e.g., credit card information) using the Skyflow client. - * - * 1. Initializes the Skyflow client. - * 2. Creates a column value for sensitive data (e.g., credit card number). - * 3. Builds a tokenize request with the column value to be tokenized. - * 4. Sends the request to the Skyflow vault for tokenization. - * 5. Prints the tokenization response, which includes the token or errors. - */ -public class TokenizeExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of column values to be tokenized (replace with actual sensitive data) - ArrayList columnValues = new ArrayList<>(); - - // Step 2: Create a column value for the sensitive data (e.g., card number with its column group) - ColumnValue columnValue = ColumnValue.builder() - .value("4111111111111111") // Replace with the actual sensitive data (e.g., card number) - .columnGroup("card_number_cg") // Replace with the actual column group name - .build(); - - // Add the created column value to the list - columnValues.add(columnValue); - - // Step 3: Build the TokenizeRequest with the column value - TokenizeRequest tokenizeRequest = TokenizeRequest.builder().values(columnValues).build(); - - // Step 4: Call the Skyflow vault to tokenize the sensitive data - TokenizeResponse tokenizeResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").tokenize(tokenizeRequest); - // Replace "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID - - // Step 5: Print the tokenization response, which contains the generated token or any errors - System.out.println(tokenizeResponse); - } catch (SkyflowException e) { - // Step 6: Handle any errors that occur during the tokenization process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "tokens": [5479-4229-4622-1393] -} -``` - -## Get - -To retrieve data using Skyflow IDs or unique column values, use the `get` method. [`GetRequest`](docs/api_reference.md#getrequest) accepts parameters such as table name, redaction type, Skyflow IDs, column names, and column values. `ids` and `columnName`/`columnValues` are mutually exclusive. Returns a [`GetResponse`](docs/api_reference.md#getresponse). - -### Construct a get request - -```java -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.GetRequest; -import com.skyflow.vault.data.GetResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to retrieve data from the Skyflow vault using different methods, along with corresponding GetRequest schema. - * - */ -public class GetSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of Skyflow IDs to retrieve records (replace with actual Skyflow IDs) - ArrayList ids = new ArrayList<>(); - ids.add(""); // Replace with actual Skyflow ID - ids.add(""); // Replace with actual Skyflow ID - - // Step 2: Create a GetRequest to retrieve records by Skyflow ID without returning tokens - GetRequest getByIdRequest = GetRequest.builder() - .ids(ids) - .table("") // Replace with the actual table name - .returnTokens(false) // Set to false to avoid returning tokens - .redactionType(RedactionType.PLAIN_TEXT) // Redact data as plain text - .build(); - - // Send the request to the Skyflow vault and retrieve the records - GetResponse getByIdResponse = skyflowClient.vault("").get(getByIdRequest); // Replace with actual Vault ID - System.out.println(getByIdResponse); - - // Step 3: Create another GetRequest to retrieve records by Skyflow ID with tokenized values - GetRequest getTokensRequest = GetRequest.builder() - .ids(ids) - .table("") // Replace with the actual table name - .returnTokens(true) // Set to true to return tokenized values - .build(); - - // Send the request to the Skyflow vault and retrieve the tokenized records - GetResponse getTokensResponse = skyflowClient.vault("").get(getTokensRequest); // Replace with actual Vault ID - System.out.println(getTokensResponse); - - // Step 4: Create a GetRequest to retrieve records based on specific column values - ArrayList columnValues = new ArrayList<>(); - columnValues.add(""); // Replace with the actual column value - columnValues.add(""); // Replace with the actual column value - - GetRequest getByColumnRequest = GetRequest.builder() - .table("") // Replace with the actual table name - .columnName("") // Replace with the column name - .columnValues(columnValues) // Add the list of column values to filter by - .redactionType(RedactionType.PLAIN_TEXT) // Redact data as plain text - .build(); - - // Send the request to the Skyflow vault and retrieve the records filtered by column values - GetResponse getByColumnResponse = skyflowClient.vault("").get(getByColumnRequest); // Replace with actual Vault ID - System.out.println(getByColumnResponse); - } catch (SkyflowException e) { - // Step 5: Handle any errors that occur during the retrieval process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -### Get by skyflow IDs - -Retrieve specific records using `skyflow_ids`. Ideal for fetching exact records when IDs are known. - -#### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/GetExample.java) of a get call to retrieve data using Redaction type: - -```java -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.GetRequest; -import com.skyflow.vault.data.GetResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to retrieve data from the Skyflow vault using a list of Skyflow IDs. - * - * 1. Initializes the Skyflow client with a given vault ID. - * 2. Creates a request to retrieve records based on Skyflow IDs. - * 3. Specifies that the response should not return tokens. - * 4. Uses plain text redaction type for the retrieved records. - * 5. Prints the response to display the retrieved records. - */ -public class GetExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of Skyflow IDs (replace with actual Skyflow IDs) - ArrayList ids = new ArrayList<>(); - ids.add("a581d205-1969-4350-acbe-a2a13eb871a6"); // Replace with actual Skyflow ID - ids.add("5ff887c3-b334-4294-9acc-70e78ae5164a"); // Replace with actual Skyflow ID - - // Step 2: Create a GetRequest to retrieve records based on Skyflow IDs - // The request specifies: - // - `ids`: The list of Skyflow IDs to retrieve - // - `table`: The table from which the records will be retrieved - // - `returnTokens`: Set to false, meaning tokens will not be returned in the response - // - `redactionType`: Set to PLAIN_TEXT, meaning the retrieved records will have data redacted as plain text - GetRequest getByIdRequest = GetRequest.builder() - .ids(ids) - .table("table1") // Replace with the actual table name - .returnTokens(false) // Set to false to avoid returning tokens - .redactionType(RedactionType.PLAIN_TEXT) // Redact data as plain text - .build(); - - // Step 3: Send the request to the Skyflow vault and retrieve the records - GetResponse getByIdResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").get(getByIdRequest); // Replace with actual Vault ID - System.out.println(getByIdResponse); // Print the response to the console - - } catch (SkyflowException e) { - // Step 4: Handle any errors that occur during the data retrieval process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "data": [ - { - "card_number": "4555555555555553", - "email": "john.doe@gmail.com", - "name": "john doe", - "skyflowId": "a581d205-1969-4350-acbe-a2a13eb871a6" - }, - { - "card_number": "4555555555555559", - "email": "jane.doe@gmail.com", - "name": "jane doe", - "skyflowId": "5ff887c3-b334-4294-9acc-70e78ae5164a" - } - ], - "errors": [] -} -``` - -> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. - -### Get tokens - -Return tokens for records. Ideal for securely processing sensitive data while maintaining data privacy. - -#### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/getExample.java) of get call to retrieve tokens using Skyflow IDs: - -```java -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.GetRequest; -import com.skyflow.vault.data.GetResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to retrieve data from the Skyflow vault and return tokens along with the records. - * - * 1. Initializes the Skyflow client with a given vault ID. - * 2. Creates a request to retrieve records based on Skyflow IDs and ensures tokens are returned. - * 3. Prints the response to display the retrieved records along with the tokens. - */ -public class GetExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of Skyflow IDs (replace with actual Skyflow IDs) - ArrayList ids = new ArrayList<>(); - ids.add("a581d205-1969-4350-acbe-a2a13eb871a6"); // Replace with actual Skyflow ID - ids.add("5ff887c3-b334-4294-9acc-70e78ae5164a"); // Replace with actual Skyflow ID - - // Step 2: Create a GetRequest to retrieve records based on Skyflow IDs - // The request specifies: - // - `ids`: The list of Skyflow IDs to retrieve - // - `table`: The table from which the records will be retrieved - // - `returnTokens`: Set to true, meaning tokens will be included in the response - GetRequest getTokensRequest = GetRequest.builder() - .ids(ids) - .table("table1") // Replace with the actual table name - .returnTokens(true) // Set to true to include tokens in the response - .build(); - - // Step 3: Send the request to the Skyflow vault and retrieve the records with tokens - GetResponse getTokensResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").get(getTokensRequest); // Replace with actual Vault ID - System.out.println(getTokensResponse); // Print the response to the console - - } catch (SkyflowException e) { - // Step 4: Handle any errors that occur during the data retrieval process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "data": [ - { - "card_number": "3998-2139-0328-0697", - "email": "c9a6c9555060@82c092e7.bd52", - "name": "82c092e7-74c0-4e60-bd52-c9a6c9555060", - "skyflowId": "a581d205-1969-4350-acbe-a2a13eb871a6" - }, - { - "card_number": "3562-0140-8820-7499", - "email": "6174366e2bc6@59f82e89.93fc", - "name": "59f82e89-138e-4f9b-93fc-6174366e2bc6", - "skyflowId": "5ff887c3-b334-4294-9acc-70e78ae5164a" - } - ], - "errors": [] -} -``` - -> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. - -### Get By column name and column values - -Retrieve records by unique column values. Ideal for querying data without knowing Skyflow IDs, using alternate unique identifiers. - -#### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/GetExample.java) of get call to retrieve data using column name and column values: - -```java -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.GetRequest; -import com.skyflow.vault.data.GetResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to retrieve data from the Skyflow vault based on column values. - * - * 1. Initializes the Skyflow client with a given vault ID. - * 2. Creates a request to retrieve records based on specific column values (e.g., email addresses). - * 3. Prints the response to display the retrieved records after redacting sensitive data based on the specified redaction type. - */ -public class GetExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Initialize a list of column values (email addresses in this case) - ArrayList columnValues = new ArrayList<>(); - columnValues.add("john.doe@gmail.com"); // Example email address - columnValues.add("jane.doe@gmail.com"); // Example email address - - // Step 2: Create a GetRequest to retrieve records based on column values - // The request specifies: - // - `table`: The table from which the records will be retrieved - // - `columnName`: The column to filter the records by (e.g., "email") - // - `columnValues`: The list of values to match in the specified column - // - `redactionType`: Defines how sensitive data should be redacted (set to PLAIN_TEXT here) - GetRequest getByColumnRequest = GetRequest.builder() - .table("table1") // Replace with the actual table name - .columnName("email") // The column name to filter by (e.g., "email") - .columnValues(columnValues) // The list of column values to match - .redactionType(RedactionType.PLAIN_TEXT) // Set the redaction type (e.g., PLAIN_TEXT) - .build(); - - // Step 3: Send the request to the Skyflow vault and retrieve the records - GetResponse getByColumnResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").get(getByColumnRequest); // Replace with actual Vault ID - System.out.println(getByColumnResponse); // Print the response to the console - - } catch (SkyflowException e) { - // Step 4: Handle any errors that occur during the data retrieval process - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "data": [ - { - "card_number": "4555555555555553", - "email": "john.doe@gmail.com", - "name": "john doe", - "skyflowId": "a581d205-1969-4350-acbe-a2a13eb871a6" - }, - { - "card_number": "4555555555555559", - "email": "jane.doe@gmail.com", - "name": "jane doe", - "skyflowId": "5ff887c3-b334-4294-9acc-70e78ae5164a" - } - ], - "errors": [] -} -``` - -> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. - -### Redaction types - -See [`RedactionType`](docs/api_reference.md#redactiontype) in the API Reference for all available values and their descriptions. - -## Update - -To update data in your vault, use the `update` method. [`UpdateRequest`](docs/api_reference.md#updaterequest) accepts the table name, data map, optional tokens, `returnTokens`, and `tokenMode`. Returns an [`UpdateResponse`](docs/api_reference.md#updateresponse) with the `skyflow_id` and (when `returnTokens=true`) a token per updated column. - -### Construct an update request - -```java -import com.skyflow.enums.TokenMode; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.UpdateRequest; -import com.skyflow.vault.data.UpdateResponse; - -import java.util.HashMap; - -/** - * This example demonstrates how to update records in the Skyflow vault by providing new data and/or tokenized values, along with corresponding UpdateRequest schema. - * - */ -public class UpdateSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Prepare the data to update in the vault - // Use a HashMap to store the data that will be updated in the specified table - HashMap data = new HashMap<>(); - data.put("skyflow_id", ""); // Skyflow ID for identifying the record to update - data.put("", ""); // Example of a column name and its value to update - data.put("", ""); // Another example of a column name and its value to update - - // Step 2: Prepare the tokens (if necessary) for certain columns that require tokenization - // Use a HashMap to specify columns that need tokens in the update request - HashMap tokens = new HashMap<>(); - tokens.put("", ""); // Example of a column name that should be tokenized - - // Step 3: Create an UpdateRequest to specify the update operation - // The request includes the table name, token mode, data, tokens, and the returnTokens flag - UpdateRequest updateRequest = UpdateRequest.builder() - .table("") // Replace with the actual table name to update - .tokenMode(TokenMode.ENABLE) // Specifies the tokenization mode (ENABLE means tokenization is applied) - .data(data) // The data to update in the record - .tokens(tokens) // The tokens associated with specific columns - .returnTokens(true) // Specify whether to return tokens in the response - .build(); - - // Step 4: Send the request to the Skyflow vault and update the record - UpdateResponse updateResponse = skyflowClient.vault("").update(updateRequest); // Replace with actual Vault ID - System.out.println(updateResponse); // Print the response to confirm the update result - - } catch (SkyflowException e) { - // Step 5: Handle any errors that occur during the update operation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/UpdateExample.java) of update call - -```java -import com.skyflow.enums.TokenMode; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.UpdateRequest; -import com.skyflow.vault.data.UpdateResponse; - -import java.util.HashMap; - -/** - * This example demonstrates how to update a record in the Skyflow vault with specified data and tokens. - * - * 1. Initializes the Skyflow client with a given vault ID. - * 2. Constructs an update request with data to modify and tokens to include. - * 3. Sends the request to update the record in the vault. - * 4. Prints the response to confirm the success or failure of the update operation. - */ -public class UpdateExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Prepare the data to update in the vault - // A HashMap is used to store the data that will be updated in the specified table - HashMap data = new HashMap<>(); - data.put("skyflow_id", "5b699e2c-4301-4f9f-bcff-0a8fd3057413"); // Skyflow ID identifies the record to update - data.put("name", "john doe"); // Updating the "name" column with a new value - data.put("card_number", "4111111111111115"); // Updating the "card_number" column with a new value - - // Step 2: Prepare the tokens to include in the update request - // Tokens can be included to update sensitive data with tokenized values - HashMap tokens = new HashMap<>(); - tokens.put("name", "72b8ffe3-c8d3-4b4f-8052-38b2a7405b5a"); // Tokenized value for the "name" column - - // Step 3: Create an UpdateRequest to define the update operation - // The request specifies the table name, token mode, data, and tokens for the update - UpdateRequest updateRequest = UpdateRequest.builder() - .table("table1") // Replace with the actual table name to update - .tokenMode(TokenMode.ENABLE) // Token mode enabled to allow tokenization of sensitive data - .data(data) // The data to update in the record - .tokens(tokens) // The tokenized values for sensitive columns - .build(); - - // Step 4: Send the update request to the Skyflow vault - UpdateResponse updateResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").update(updateRequest); // Replace with your actual Vault ID - System.out.println(updateResponse); // Print the response to confirm the update result - - } catch (SkyflowException e) { - // Step 5: Handle any exceptions that occur during the update operation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception stack trace for debugging purposes - } - } -} -``` - -Sample response: - -- When `returnTokens` is set to `true` - -```json -{ - "skyflowId": "5b699e2c-4301-4f9f-bcff-0a8fd3057413", - "name": "72b8ffe3-c8d3-4b4f-8052-38b2a7405b5a", - "card_number": "4315-7650-1359-9681" -} -``` - -- When `returnTokens` is set to `false` - -```json -{ - "skyflowId": "5b699e2c-4301-4f9f-bcff-0a8fd3057413" -} -``` - -## Delete - -To delete records using Skyflow IDs, use the `delete` method. [`DeleteRequest`](docs/api_reference.md#deleterequest) accepts a table name and list of Skyflow IDs. Returns a [`DeleteResponse`](docs/api_reference.md#deleteresponse). - -### Construct a delete request - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.DeleteRequest; -import com.skyflow.vault.data.DeleteResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to delete records from a Skyflow vault using specified Skyflow IDs, along with corresponding DeleteRequest schema. - * - */ -public class DeleteSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Prepare a list of Skyflow IDs for the records to delete - // The list stores the Skyflow IDs of the records that need to be deleted from the vault - ArrayList ids = new ArrayList<>(); - ids.add(""); // Replace with actual Skyflow ID 1 - ids.add(""); // Replace with actual Skyflow ID 2 - ids.add(""); // Replace with actual Skyflow ID 3 - - // Step 2: Create a DeleteRequest to define the delete operation - // The request specifies the table from which to delete the records and the IDs of the records to delete - DeleteRequest deleteRequest = DeleteRequest.builder() - .ids(ids) // List of Skyflow IDs to delete - .table("") // Replace with the actual table name from which to delete - .build(); - - // Step 3: Send the delete request to the Skyflow vault - DeleteResponse deleteResponse = skyflowClient.vault("").delete(deleteRequest); // Replace with your actual Vault ID - System.out.println(deleteResponse); // Print the response to confirm the delete result - - } catch (SkyflowException e) { - // Step 4: Handle any exceptions that occur during the delete operation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception stack trace for debugging purposes - } - } -} -``` - -### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/DeleteExample.java) of delete call: - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.DeleteRequest; -import com.skyflow.vault.data.DeleteResponse; - -import java.util.ArrayList; - -/** - * This example demonstrates how to delete records from a Skyflow vault using specified Skyflow IDs. - * - * 1. Initializes the Skyflow client with a given Vault ID. - * 2. Constructs a delete request by specifying the IDs of the records to delete. - * 3. Sends the delete request to the Skyflow vault to delete the specified records. - * 4. Prints the response to confirm the success or failure of the delete operation. - */ -public class DeleteExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Prepare a list of Skyflow IDs for the records to delete - // The list stores the Skyflow IDs of the records that need to be deleted from the vault - ArrayList ids = new ArrayList<>(); - ids.add("9cbf66df-6357-48f3-b77b-0f1acbb69280"); // Replace with actual Skyflow ID 1 - ids.add("ea74bef4-f27e-46fe-b6a0-a28e91b4477b"); // Replace with actual Skyflow ID 2 - ids.add("47700796-6d3b-4b54-9153-3973e281cafb"); // Replace with actual Skyflow ID 3 - - // Step 2: Create a DeleteRequest to define the delete operation - // The request specifies the table from which to delete the records and the IDs of the records to delete - DeleteRequest deleteRequest = DeleteRequest.builder() - .ids(ids) // List of Skyflow IDs to delete - .table("table1") // Replace with the actual table name from which to delete - .build(); - - // Step 3: Send the delete request to the Skyflow vault - DeleteResponse deleteResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").delete(deleteRequest); // Replace with your actual Vault ID - System.out.println(deleteResponse); // Print the response to confirm the delete result - - } catch (SkyflowException e) { - // Step 4: Handle any exceptions that occur during the delete operation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception stack trace for debugging purposes - } - } -} -``` - -Sample response: - -```json -{ - "deletedIds": [ - "9cbf66df-6357-48f3-b77b-0f1acbb69280", - "ea74bef4-f27e-46fe-b6a0-a28e91b4477b", - "47700796-6d3b-4b54-9153-3973e281cafb" - ] -} -``` - -## Query - -To retrieve data with SQL queries, use the `query` method. [`QueryRequest`](docs/api_reference.md#queryrequest) accepts a `query` string. Returns a [`QueryResponse`](docs/api_reference.md#queryresponse). - -### Construct a query request - -Refer to [Query your data](https://docs.skyflow.com/query-data/) and [Execute Query](https://docs.skyflow.com/record/#QueryService_ExecuteQuery) for guidelines and restrictions on supported SQL statements, operators, and keywords. - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.QueryRequest; -import com.skyflow.vault.data.QueryResponse; - -/** - * This example demonstrates how to execute a custom SQL query on a Skyflow vault, along with QueryRequest schema. - * - */ -public class QuerySchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Define the SQL query to execute on the Skyflow vault - // Replace "" with the actual SQL query you want to run - String query = ""; // Example: "SELECT * FROM table1 WHERE column1 = 'value'" - - // Step 2: Create a QueryRequest with the specified SQL query - QueryRequest queryRequest = QueryRequest.builder() - .query(query) // SQL query to execute - .build(); - - // Step 3: Execute the query request on the specified Skyflow vault - QueryResponse queryResponse = skyflowClient.vault("").query(queryRequest); // Replace with your actual Vault ID - System.out.println(queryResponse); // Print the response containing the query results - - } catch (SkyflowException e) { - // Step 4: Handle any exceptions that occur during the query execution - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception stack trace for debugging - } - } -} -``` - -### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/QueryExample.java) of query call - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.QueryRequest; -import com.skyflow.vault.data.QueryResponse; - -/** - * This example demonstrates how to execute a SQL query on a Skyflow vault to retrieve data. - * - * 1. Initializes the Skyflow client with the Vault ID. - * 2. Constructs a query request with a specified SQL query. - * 3. Executes the query against the Skyflow vault. - * 4. Prints the response from the query execution. - */ -public class QueryExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Define the SQL query - // Example query: Retrieve all records from the "cards" table with a specific skyflow_id - String query = "SELECT * FROM cards WHERE skyflow_id='3ea3861-x107-40w8-la98-106sp08ea83f'"; - - // Step 2: Create a QueryRequest with the SQL query - QueryRequest queryRequest = QueryRequest.builder() - .query(query) // SQL query to execute - .build(); - - // Step 3: Execute the query request on the specified Skyflow vault - QueryResponse queryResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").query(queryRequest); // Vault ID: 9f27764a10f7946fe56b3258e117 - System.out.println(queryResponse); // Print the query response (contains query results) - - } catch (SkyflowException e) { - // Step 4: Handle any exceptions that occur during the query execution - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception stack trace for debugging - } - } -} -``` - -Sample response: - -```json -{ - "fields": [ - { - "card_number": "XXXXXXXXXXXX1112", - "name": "S***ar", - "skyflowId": "3ea3861-x107-40w8-la98-106sp08ea83f", - "tokenizedData": null - } - ] -} -``` - -> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. - -## Upload File - -To upload files to a Skyflow vault, use the `uploadFile` method. [`FileUploadRequest`](docs/api_reference.md#fileuploadrequest) accepts the table name, column name, optional skyflow ID, and a file source (`fileObject`, `filePath`, or `base64`). Returns a [`FileUploadResponse`](docs/api_reference.md#fileuploadresponse). - -### Construct a file upload request - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.FileUploadRequest; -import com.skyflow.vault.data.FileUploadResponse; - -/** - * This example demonstrates how to upload a file to a Skyflow vault, along with the UploadFileRequest schema. - * - */ -public class UploadFileSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Specify file Object - File file = new File(""); - - // Step 2: Create an UploadFileRequest with the file details - FileUploadRequest uploadFileRequest = FileUploadRequest.builder() - .fileObject(file) // File object - .table("") // Vault table to upload into - .columnName("") // Column to assign to the uploaded file - .skyflowId("") // Skyflow id of the record - .build(); - - // Step 3: Execute the file upload request on the specified Skyflow vault - FileUploadResponse fileUploadResponse = skyflowClient.vault().uploadFile(uploadFileRequest); - System.out.println("File Upload Response: " + fileUploadResponse); - - } catch (SkyflowException e) { - // Step 4: Handle any exceptions that occur during the upload - System.out.println("Error occurred during file upload:"); - e.printStackTrace(); // Print the exception stack trace for debugging - } - } -} - -``` - -### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/FileUploadExample.java) of file upload call -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.data.FileUploadRequest; -import com.skyflow.vault.data.FileUploadResponse; - -/** - * This example demonstrates how to upload a file to a Skyflow vault. - * - * 1. Initializes the Skyflow client with the Vault ID. - * 2. Constructs a file upload request with the file path, table name, and file name. - * 3. Executes the upload request against the Skyflow vault. - * 4. Prints the response from the upload. - */ -public class UploadFileExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Specify file Object - File file = new File("test/sample.txt"); - - // Step 2: Create an UploadFileRequest with the file details - FileUploadRequest uploadFileRequest = FileUploadRequest.builder() - .fileObject(file) // File object - .table("cards") // Vault table to upload into - .columnName("file") // Column to assign to the uploaded file - .skyflowId("c9312531-2087-439a-bd26-74c41f24db83") // Skyflow id of the record - .build(); - - // Step 3: Execute the file upload request - FileUploadResponse uploadResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").uploadFile(uploadFileRequest); - System.out.println("File Upload Response: " + fileUploadResponse); - - } catch (SkyflowException e) { - // Step 4: Handle any exceptions during the upload - System.out.println("Error occurred during file upload:"); - e.printStackTrace(); // Print exception details for debugging - } - } -} - -``` - -Sample response: - -```json -{ - "skyflowId": "c9312531-2087-439a-bd26-74c41f24db83", - "errors": null -} -``` - -# Detect -Skyflow Detect enables you to deidentify and reidentify sensitive data in text and files, supporting advanced privacy-preserving workflows. - -`DetectController` is the class returned by `skyflowClient.detect()` and `skyflowClient.detect(vaultId)`. - -```java -// Uses the default (first configured) vault -DetectController detect = skyflowClient.detect(); - -// Uses a specific vault by ID -DetectController detect = skyflowClient.detect(""); -``` - -**Methods:** - -| Method | Parameters | Returns | Description | -|--------|-----------|---------|-------------| -| `deidentifyText(DeidentifyTextRequest)` | [`DeidentifyTextRequest`](docs/api_reference.md#deidentifytextrequest) | [`DeidentifyTextResponse`](docs/api_reference.md#deidentifytextresponse) | Deidentify sensitive entities in text | -| `reidentifyText(ReidentifyTextRequest)` | [`ReidentifyTextRequest`](docs/api_reference.md#reidentifytextrequest) | [`ReidentifyTextResponse`](docs/api_reference.md#reidentifytextresponse) | Restore original values from a deidentified text | -| `deidentifyFile(DeidentifyFileRequest)` | [`DeidentifyFileRequest`](docs/api_reference.md#deidentifyfilerequest) | [`DeidentifyFileResponse`](docs/api_reference.md#deidentifyfileresponse) | Deidentify sensitive data in a file | -| `getDetectRun(GetDetectRunRequest)` | [`GetDetectRunRequest`](docs/api_reference.md#getdetectrunrequest) | [`DeidentifyFileResponse`](docs/api_reference.md#deidentifyfileresponse) | Poll for the result of an async file deidentification | - -## Deidentify Text -To deidentify text, use the `deidentifyText` method. [`DeidentifyTextRequest`](docs/api_reference.md#deidentifytextrequest) accepts the text to deidentify along with optional entity types, regex lists, token format, and transformations. Returns a [`DeidentifyTextResponse`](docs/api_reference.md#deidentifytextresponse). - -### Construct an deidentify text request - -```java -import com.skyflow.enums.DetectEntities; -import com.skyflow.vault.detect.DateTransformation; -import com.skyflow.vault.detect.DeidentifyTextRequest; -import com.skyflow.vault.detect.TokenFormat; -import com.skyflow.vault.detect.Transformations; -import com.skyflow.vault.detect.DeidentifyTextResponse; - -import java.util.ArrayList; -import java.util.List; - -/** - * This example demonstrate to build deidentify text request. - */ -public class DeidentifyTextSchema { - - public static void main(String[] args) { - - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - - // Step 2: Configure the options for deidentify text - - // Replace with the entity you want to detect - List detectEntitiesList = new ArrayList<>(); - detectEntitiesList.add(DetectEntities.SSN); - - // Replace with the entity you want to detect with vault token - List vaultTokenList = new ArrayList<>(); - vaultTokenList.add(DetectEntities.CREDIT_CARD); - - // Replace with the entity you want to detect with entity only - List entityOnlyList = new ArrayList<>(); - entityOnlyList.add(DetectEntities.SSN); - - // Replace with the entity you want to detect with entity unique counter - List entityUniqueCounterList = new ArrayList<>(); - entityUniqueCounterList.add(DetectEntities.SSN); - - // Replace with the regex patterns you want to allow during deidentification - List allowRegexList = new ArrayList<>(); - allowRegexList.add(""); - - // Replace with the regex patterns you want to restrict during deidentification - List restrictRegexList = new ArrayList<>(); - restrictRegexList.add("YOUR_RESTRICT_REGEX_LIST"); - - // Configure Token Format - TokenFormat tokenFormat = TokenFormat.builder() - .vaultToken(vaultTokenList) - .entityOnly(entityOnlyList) - .entityUniqueCounter(entityUniqueCounterList) - .build(); - - // Configure Transformation - List detectEntitiesTransformationList = new ArrayList<>(); - detectEntitiesTransformationList.add(DetectEntities.DOB); // Replace with the entity you want to transform - - DateTransformation dateTransformation = new DateTransformation(20, 5, detectEntitiesTransformationList); - Transformations transformations = new Transformations(dateTransformation); - - // Step 3: Create a deidentify text request for the vault - DeidentifyTextRequest deidentifyTextRequest = DeidentifyTextRequest.builder() - .text("") // Replace with the text you want to deidentify - .entities(detectEntitiesList) - .allowRegexList(allowRegexList) - .restrictRegexList(restrictRegexList) - .tokenFormat(tokenFormat) - .transformations(transformations) - .build(); - - // Step 4: Use the Skyflow client to perform the deidentifyText operation - // Replace with your actual vault ID - DeidentifyTextResponse deidentifyTextResponse = skyflowClient.detect("").deidentifyText(deidentifyTextRequest); - - // Step 5: Print the response - System.out.println("Deidentify text Response: " + deidentifyTextResponse); - } -} - -``` - -## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/DeidentifyTextExample.java) of deidentify text: -```java -import java.util.ArrayList; -import java.util.List; - -import com.skyflow.enums.DetectEntities; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.detect.DateTransformation; -import com.skyflow.vault.detect.DeidentifyTextRequest; -import com.skyflow.vault.detect.DeidentifyTextResponse; -import com.skyflow.vault.detect.TokenFormat; -import com.skyflow.vault.detect.Transformations; - -/** - * Skyflow Deidentify Text Example - *

- * This example demonstrates how to use the Skyflow SDK to deidentify text data - * across multiple vaults. It includes: - * 1. Setting up credentials and vault configurations. - * 2. Creating a Skyflow client with multiple vaults. - * 3. Performing deidentify of text with various options. - * 4. Handling responses and errors. - */ - -public class DeidentifyTextExample { - public static void main(String[] args) throws SkyflowException { - - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - - // Step 2: Configuring the different options for deidentify - - // Replace with the entity you want to detect - List detectEntitiesList = new ArrayList<>(); - detectEntitiesList.add(DetectEntities.SSN); - detectEntitiesList.add(DetectEntities.CREDIT_CARD); - - // Replace with the entity you want to detect with vault token - List vaultTokenList = new ArrayList<>(); - vaultTokenList.add(DetectEntities.SSN); - vaultTokenList.add(DetectEntities.CREDIT_CARD); - - // Configure Token Format - TokenFormat tokenFormat = TokenFormat.builder() - .vaultToken(vaultTokenList) - .build(); - - // Configure Transformation for deidentified entities - List detectEntitiesTransformationList = new ArrayList<>(); - detectEntitiesTransformationList.add(DetectEntities.DOB); // Replace with the entity you want to transform - - DateTransformation dateTransformation = new DateTransformation(20, 5, detectEntitiesTransformationList); - Transformations transformations = new Transformations(dateTransformation); - - // Step 3: invoking Deidentify text on the vault - try { - // Create a deidentify text request for the vault - DeidentifyTextRequest deidentifyTextRequest = DeidentifyTextRequest.builder() - .text("My SSN is 123-45-6789 and my card is 4111 1111 1111 1111.") // Replace with your deidentify text - .entities(detectEntitiesList) - .tokenFormat(tokenFormat) - .transformations(transformations) - .build(); - // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id - DeidentifyTextResponse deidentifyTextResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").deidentifyText(deidentifyTextRequest); - - System.out.println("Deidentify text Response: " + deidentifyTextResponse); - } catch (SkyflowException e) { - System.err.println("Error occurred during deidentify: "); - e.printStackTrace(); // Print the exception for debugging purposes - } - } -} -``` - -Sample Response: -```json -{ - "processedText": "My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].", - "entities": [ - { - "token": "SSN_IWdexZe", - "value": "123-45-6789", - "textIndex": { - "start": 10, - "end": 21 - }, - "processedIndex": { - "start": 10, - "end": 23 - }, - "entity": "SSN", - "scores": { - "SSN": 0.9384 - } - }, - { - "token": "CREDIT_CARD_rUzMjdQ", - "value": "4111 1111 1111 1111", - "textIndex": { - "start": 37, - "end": 56 - }, - "processedIndex": { - "start": 39, - "end": 60 - }, - "entity": "CREDIT_CARD", - "scores": { - "CREDIT_CARD": 0.9051 - } - } - ], - "wordCount": 9, - "charCount": 57 -} -``` - -## Reidentify Text -To reidentify text, use the `reidentifyText` method. [`ReidentifyTextRequest`](docs/api_reference.md#reidentifytextrequest) accepts the redacted/deidentified text and optional entity lists controlling which entities to reveal, mask, or keep redacted. Returns a [`ReidentifyTextResponse`](docs/api_reference.md#reidentifytextresponse). - -### Construct an reidentify text request - -```java -import com.skyflow.enums.DetectEntities; -import com.skyflow.vault.detect.ReidentifyTextRequest; -import com.skyflow.vault.detect.ReidentifyTextResponse; - -import java.util.ArrayList; -import java.util.List; - -/** - * This example demonstrates how to build a reidentify text request. - */ -public class ReidentifyTextSchema { - public static void main(String[] args) { - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - - // Step 2: Configuring the different options for reidentify - List maskedEntity = new ArrayList<>(); - maskedEntity.add(DetectEntities.CREDIT_CARD); // Replace with the entity you want to mask - - List plainTextEntity = new ArrayList<>(); - plainTextEntity.add(DetectEntities.SSN); // Replace with the entity you want to keep in plain text - - // List redactedEntity = new ArrayList<>(); - // redactedEntity.add(DetectEntities.SSN); // Replace with the entity you want to redact - - - // Step 3: Create a reidentify text request with the configured entities - ReidentifyTextRequest reidentifyTextRequest = ReidentifyTextRequest.builder() - .text("My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].") // Replace with your deidentify text - .maskedEntities(maskedEntity) -// .redactedEntities(redactedEntity) - .plainTextEntities(plainTextEntity) - .build(); - - // Step 4: Invoke reidentify text on the vault - ReidentifyTextResponse reidentifyTextResponse = skyflowClient.detect("").reidentifyText(reidentifyTextRequest); - System.out.println("Reidentify text Response: " + reidentifyTextResponse); - } -} -``` - -## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/ReidentifyTextExample.java) of Reidentify text - -```java -import com.skyflow.enums.DetectEntities; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.detect.ReidentifyTextRequest; -import com.skyflow.vault.detect.ReidentifyTextResponse; - -import java.util.ArrayList; -import java.util.List; - -/** - * Skyflow Reidentify Text Example - *

- * This example demonstrates how to use the Skyflow SDK to reidentify text data - * across multiple vaults. It includes: - * 1. Setting up credentials and vault configurations. - * 2. Creating a Skyflow client with multiple vaults. - * 3. Performing reidentify of text with various options. - * 4. Handling responses and errors. - */ - -public class ReidentifyTextExample { - public static void main(String[] args) throws SkyflowException { - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - - // Step 2: Configuring the different options for reidentify - List maskedEntity = new ArrayList<>(); - maskedEntity.add(DetectEntities.CREDIT_CARD); // Replace with the entity you want to mask - - List plainTextEntity = new ArrayList<>(); - plainTextEntity.add(DetectEntities.SSN); // Replace with the entity you want to keep in plain text - - try { - // Step 3: Create a reidentify text request with the configured options - ReidentifyTextRequest reidentifyTextRequest = ReidentifyTextRequest.builder() - .text("My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].") // Replace with your deidentify text - .maskedEntities(maskedEntity) - .plainTextEntities(plainTextEntity) - .build(); - - // Step 4: Invoke Reidentify text on the vault - // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id - ReidentifyTextResponse reidentifyTextResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").reidentifyText(reidentifyTextRequest); - - // Handle the response from the reidentify text request - System.out.println("Reidentify text Response: " + reidentifyTextResponse); - } catch (SkyflowException e) { - System.err.println("Error occurred during reidentify : "); - e.printStackTrace(); - } - } -} -``` - -Sample Response: - -```json -{ - "processedText":"My SSN is 123-45-6789 and my card is XXXXX1111." -} -``` - -## Deidentify file -To deidentify files, use the `deidentifyFile` method. [`DeidentifyFileRequest`](docs/api_reference.md#deidentifyfilerequest) accepts a [`FileInput`](docs/api_reference.md#fileinput) and optional parameters controlling entity detection, masking, output format, and async wait time. Supports images, PDFs, audio, documents, spreadsheets, and presentations. Returns a [`DeidentifyFileResponse`](docs/api_reference.md#deidentifyfileresponse). - -### AudioBleep - -[`AudioBleep`](docs/api_reference.md#audiobleep) controls how detected sensitive audio segments are replaced with a bleep tone. Used in `DeidentifyFileRequest.builder().bleep(audioBleep)` for audio files. - -```java -import com.skyflow.vault.detect.AudioBleep; - -AudioBleep audioBleep = AudioBleep.builder() - .frequency(1000D) // bleep tone frequency in Hz - .gain(0.5D) // bleep tone gain (volume level) - .startPadding(0.2D) // silence padding before the bleep (seconds) - .stopPadding(0.2D) // silence padding after the bleep (seconds) - .build(); -``` - -### Construct an deidentify file request - -```java -import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; -import com.skyflow.enums.Env; -import com.skyflow.enums.LogLevel; -import com.skyflow.enums.MaskingMethod; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.detect.DeidentifyFileRequest; -import com.skyflow.vault.detect.DeidentifyFileResponse; - -import java.io.File; - -/** - * This example demonstrates how to build a deidentify file request. - */ - -public class DeidentifyFileSchema { - - public static void main(String[] args) { - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - - // Step 2: Create a deidentify file request with all options - - // Create file object - File file = new File(""); // Replace with the path to the file you want to deidentify - - // Create file input using the file object - FileInput fileInput = FileInput.builder() - .file(file) - // .filePath("") // Alternatively, you can use .filePath() - .build(); - - // Output configuration - String outputDirectory = ""; // Replace with the desired output directory to save the deidentified file - - // Entities to detect - // List detectEntities = new ArrayList<>(); - // detectEntities.add(DetectEntities.IP_ADDRESS); // Replace with the entities you want to detect - - // Image-specific options - // Boolean outputProcessedImage = true; // Include processed image in output - // Boolean outputOcrText = true; // Include OCR text in output - MaskingMethod maskingMethod = MaskingMethod.BLACKBOX; // Masking method for images - - // PDF-specific options - // Integer pixelDensity = 15; // Pixel density for PDF processing - // Integer maxResolution = 2000; // Max resolution for PDF - - // Audio-specific options - // Boolean outputProcessedAudio = true; // Include processed audio - // DetectOutputTranscriptions outputTanscription = DetectOutputTranscriptions.PLAINTEXT_TRANSCRIPTION; // Transcription type - - // Audio bleep configuration - // AudioBleep audioBleep = AudioBleep.builder() - // .frequency(5D) // Pitch in Hz - // .startPadding(7D) // Padding at start (seconds) - // .stopPadding(8D) // Padding at end (seconds) - // .build(); - - Integer waitTime = 20; // Max wait time for response (max 64 seconds) - - DeidentifyFileRequest deidentifyFileRequest = DeidentifyFileRequest.builder() - .file(fileInput) - .waitTime(waitTime) - .entities(detectEntities) - .outputDirectory(outputDirectory) - .maskingMethod(maskingMethod) - // .outputProcessedImage(outputProcessedImage) - // .outputOcrText(outputOcrText) - // .pixelDensity(pixelDensity) - // .maxResolution(maxResolution) - // .outputProcessedAudio(outputProcessedAudio) - // .outputTranscription(outputTanscription) - // .bleep(audioBleep) - .build(); - - - DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("").deidentifyFile(deidentifyFileRequest); - System.out.println("Deidentify file response: " + deidentifyFileResponse.toString()); - } -} -``` - -## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/DeidentifyFileExample.java) of Deidentify file - -```java -import java.io.File; - -import com.skyflow.enums.MaskingMethod; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.detect.DeidentifyFileRequest; -import com.skyflow.vault.detect.DeidentifyFileResponse; - -/** - * Skyflow Deidentify File Example - *

- * This example demonstrates how to use the Skyflow SDK to deidentify file - * It has all available options for deidentifying files. - * Supported file types: images (jpg, png, etc.), pdf, audio (mp3, wav), documents, spreadsheets, presentations, structured text. - * It includes: - * 1. Configure credentials - * 2. Set up vault configuration - * 3. Create a deidentify file request with all options - * 4. Call deidentifyFile to deidentify file. - * 5. Handle response and errors - */ -public class DeidentifyFileExample { - - public static void main(String[] args) throws SkyflowException { - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - try { - // Step 2: Create a deidentify file request with all options - - - // Create file object - File file = new File("sensitive-folder/personal-info.txt"); // Replace with the path to the file you want to deidentify - - // Create file input using the file object - FileInput fileInput = FileInput.builder() - .file(file) - // .filePath("") // Alternatively, you can use .filePath() - .build(); - - // Output configuration - String outputDirectory = "deidentified-file/"; // Replace with the desired output directory to save the deidentified file - - // Entities to detect - // List detectEntities = new ArrayList<>(); - // detectEntities.add(DetectEntities.IP_ADDRESS); // Replace with the entities you want to detect - - // Image-specific options - // Boolean outputProcessedImage = true; // Include processed image in output - // Boolean outputOcrText = true; // Include OCR text in output - MaskingMethod maskingMethod = MaskingMethod.BLACKBOX; // Masking method for images - - Integer waitTime = 20; // Max wait time for response (max 64 seconds) - - DeidentifyFileRequest deidentifyFileRequest = DeidentifyFileRequest.builder() - .file(fileInput) - .waitTime(waitTime) - .outputDirectory(outputDirectory) - .maskingMethod(maskingMethod) - .build(); - - // Step 3: Invoking deidentifyFile - // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id - DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").deidentifyFile(deidentifyFileRequest); - System.out.println("Deidentify file response: " + deidentifyFileResponse.toString()); - } catch (SkyflowException e) { - System.err.println("Error occurred during deidentify file: "); - e.printStackTrace(); - } - } -} - -``` - -Sample response: - -```json -{ - "file": { - "name": "deidentified.txt", - "size": 33, - "type": "", - "lastModified": 1751355183039 - }, - "fileBase64": "bXkgY2FyZCBudW1iZXIgaXMgW0NSRURJVF", - "type": "redacted_file", - "extension": "txt", - "wordCount": 11, - "charCount": 61, - "sizeInKb": 0, - "entities": [ - { - "file": "bmFtZTogW05BTUVfMV0gCm==", - "type": "entities", - "extension": "json" - } - ], - "runId": "undefined", - "status": "success" -} - -``` - -**Supported file types:** -- Documents: `doc`, `docx`, `pdf` -- PDFs: `pdf` -- Images: `bmp`, `jpeg`, `jpg`, `png`, `tif`, `tiff` -- Structured text: `json`, `xml` -- Spreadsheets: `csv`, `xls`, `xlsx` -- Presentations: `ppt`, `pptx` -- Audio: `mp3`, `wav` - -**Note:** -- Transformations cannot be applied to Documents, Images, or PDFs file formats. - -- The `waitTime` option must be ≤ 64 seconds; otherwise, an error is thrown. - -- If the API takes more than 64 seconds to process the file, it will return only the run ID in the response. - -Sample response (when the API takes more than 64 seconds): -```json -{ - "file": null, - "fileBase64": null, - "type": null, - "extension": null, - "wordCount": null, - "charCount": null, - "sizeInKb": null, - "durationInSeconds": null, - "pageCount": null, - "slideCount": null, - "entities": null, - "runId": "1273a8c6-c498-4293-a9d6-389864cd3a44", - "status": "IN_PROGRESS", - "errors": null -} -``` - -## Get run: -To retrieve the results of a previously started file deidentification operation, use the `getDetectRun` method. [`GetDetectRunRequest`](docs/api_reference.md#getdetectrunrequest) accepts the `runId` returned from a prior `deidentifyFile` call. Returns a [`DeidentifyFileResponse`](docs/api_reference.md#deidentifyfileresponse). - -### Construct an get run request - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.detect.DeidentifyFileResponse; -import com.skyflow.vault.detect.GetDetectRunRequest; - -/** - * Skyflow Get Detect Run Example - */ - -public class GetDetectRunSchema { - - public static void main(String[] args) { - try { - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - - // Step 2: Create a get detect run request - GetDetectRunRequest getDetectRunRequest = GetDetectRunRequest.builder() - .runId("") // Replace with the runId from deidentifyFile call - .build(); - - // Step 3: Call getDetectRun to poll for file processing results - // Replace with your actual vault ID - DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("").getDetectRun(getDetectRunRequest); - System.out.println("Get Detect Run Response: " + deidentifyFileResponse); - } catch (SkyflowException e) { - System.err.println("Error occurred during get detect run: "); - e.printStackTrace(); - } - } -} - -``` - -## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/GetDetectRunExample.java) of get run -```java -import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; -import com.skyflow.enums.Env; -import com.skyflow.enums.LogLevel; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.detect.DeidentifyFileResponse; -import com.skyflow.vault.detect.GetDetectRunRequest; - -/** - * Skyflow Get Detect Run Example - *

- * This example demonstrates how to: - * 1. Configure credentials - * 2. Set up vault configuration - * 3. Create a get detect run request - * 4. Call getDetectRun to poll for file processing results - * 5. Handle response and errors - */ -public class GetDetectRunExample { - public static void main(String[] args) throws SkyflowException { - // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. - try { - - // Step 2: Create a get detect run request - GetDetectRunRequest getDetectRunRequest = GetDetectRunRequest.builder() - .runId("e0038196-4a20-422b-bad7-e0477117f9bb") // Replace with the runId from deidentifyFile call - .build(); - - // Step 3: Call getDetectRun to poll for file processing results - // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id - DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").getDetectRun(getDetectRunRequest); - System.out.println("Get Detect Run Response: " + deidentifyFileResponse); - } catch (SkyflowException e) { - System.err.println("Error occurred during get detect run: "); - e.printStackTrace(); - } - } -} -``` - -Sample Response: - -```json -{ - "file": "bmFtZTogW05BTET0JfMV0K", - "type": "redacted_file", - "extension": "txt", - "wordCount": 11, - "charCount": 61, - "sizeInKb": 0.0, - "entities": [ - { - "file": "gW05BTUVfMV0gCmNhcmQ0K", - "type": "entities", - "extension": "json" - } - ], - "runId": "e0038196-4a20-422b-bad7-e0477117f9bb", - "status": "success" -} - -``` - -## Detect response types - -The Detect API returns structured objects for detected entities. See the API Reference for full attribute lists: [`EntityInfo`](docs/api_reference.md#entityinfo), [`TextIndex`](docs/api_reference.md#textindex), [`FileEntityInfo`](docs/api_reference.md#fileentityinfo), [`FileInfo`](docs/api_reference.md#fileinfo). - -### EntityInfo and TextIndex - -[`EntityInfo`](docs/api_reference.md#entityinfo) appears in `DeidentifyTextResponse.getEntities()`. Each entry includes the detected entity type, original value, replacement token, character positions ([`TextIndex`](docs/api_reference.md#textindex)), and confidence scores. - -```java -DeidentifyTextResponse response = skyflowClient.detect("").deidentifyText(request); - -for (EntityInfo entity : response.getEntities()) { - System.out.println("Entity : " + entity.getEntity()); - System.out.println("Value : " + entity.getValue()); - System.out.println("Token : " + entity.getToken()); - System.out.println("Start : " + entity.getTextIndex().getStart()); - System.out.println("End : " + entity.getTextIndex().getEnd()); - System.out.println("Score : " + entity.getScores().get(entity.getEntity())); -} -``` - -### FileEntityInfo and FileInfo - -[`FileEntityInfo`](docs/api_reference.md#fileentityinfo) appears in `DeidentifyFileResponse.getEntities()`. [`FileInfo`](docs/api_reference.md#fileinfo) is returned by `DeidentifyFileResponse.getFile()` and contains file metadata. - -## Detect enums - -See the API Reference for full value descriptions: [`TokenType`](docs/api_reference.md#tokentype), [`DeidentifyFileStatus`](docs/api_reference.md#deidentifyfilestatus), [`DetectOutputTranscriptions`](docs/api_reference.md#detectoutputtranscriptions), [`MaskingMethod`](docs/api_reference.md#maskingmethod), [`DetectEntities`](docs/api_reference.md#detectentities). - -### TokenType - -[`TokenType`](docs/api_reference.md#tokentype) controls how detected entities are tokenized. Used in `TokenFormat.builder()`. - -```java -import com.skyflow.enums.TokenType; - -TokenFormat tokenFormat = TokenFormat.builder() - .vaultToken(vaultTokenList) // uses VAULT_TOKEN - .entityOnly(entityOnlyList) // uses ENTITY_ONLY - .entityUniqueCounter(entityUniqueCounterList) // uses ENTITY_UNIQUE_COUNTER - .build(); -``` - -### DeidentifyFileStatus - -[`DeidentifyFileStatus`](docs/api_reference.md#deidentifyfilestatus) is returned in `DeidentifyFileResponse.getStatus()` to indicate async processing state. - -```java -import com.skyflow.enums.DeidentifyFileStatus; - -DeidentifyFileResponse response = skyflowClient.detect("").getDetectRun(request); -if (DeidentifyFileStatus.SUCCESS.value().equals(response.getStatus())) { - // safe to read response.getFile() -} else if (DeidentifyFileStatus.IN_PROGRESS.value().equals(response.getStatus())) { - // poll again using the runId -} -``` - -### DetectOutputTranscriptions - -[`DetectOutputTranscriptions`](docs/api_reference.md#detectoutputtranscriptions) controls the transcription format for audio file deidentification. - -```java -import com.skyflow.enums.DetectOutputTranscriptions; - -DeidentifyFileRequest request = DeidentifyFileRequest.builder() - .file(fileInput) - .outputTranscription(DetectOutputTranscriptions.TRANSCRIPTION) - .build(); -``` - -# Connections - -Skyflow Connections is a gateway service that uses tokenization to securely send and receive data between your systems and first- or third-party services. The [connections](https://github.com/skyflowapi/skyflow-java/tree/main/src/main/java/com/skyflow/vault/connection) module invokes both inbound and/or outbound connections. - -- **Inbound connections**: Act as intermediaries between your client and server, tokenizing sensitive data before it reaches your backend, ensuring downstream services handle only tokenized data. -- **Outbound connections**: Enable secure extraction of data from the vault and transfer it to third-party services via your backend server, such as processing checkout or card issuance flows. - -## ConnectionController - -`ConnectionController` is the class returned by `skyflowClient.connection()` and `skyflowClient.connection(connectionId)`. All connection operations are called on this object. - -```java -// Uses the default (first configured) connection -ConnectionController connection = skyflowClient.connection(); - -// Uses a specific connection by ID -ConnectionController connection = skyflowClient.connection(""); -``` - -**Methods:** - -| Method | Parameters | Returns | Description | -|--------|-----------|---------|-------------| -| `invoke(InvokeConnectionRequest)` | [`InvokeConnectionRequest`](docs/api_reference.md#invokeconnectionrequest) | [`InvokeConnectionResponse`](docs/api_reference.md#invokeconnectionresponse) | Invoke an inbound or outbound connection | - -## Invoke a connection - -To invoke a connection, use the `invoke` method of the Skyflow client. - -### Construct an invoke connection request - -```java -import com.skyflow.enums.RequestMethod; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.connection.InvokeConnectionRequest; -import com.skyflow.vault.connection.InvokeConnectionResponse; - -import java.util.HashMap; -import java.util.Map; - -/** - * This example demonstrates how to invoke an external connection using the Skyflow SDK, along with corresponding InvokeConnectionRequest schema. - * - */ -public class InvokeConnectionSchema { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Define the request body parameters - // These are the values you want to send in the request body - Map requestBody = new HashMap<>(); - requestBody.put("", ""); - requestBody.put("", ""); - - // Step 2: Define the request headers - // Add any required headers that need to be sent with the request - Map requestHeaders = new HashMap<>(); - requestHeaders.put("", ""); - requestHeaders.put("", ""); - - // Step 3: Define the path parameters - // Path parameters are part of the URL and typically used in RESTful APIs - Map pathParams = new HashMap<>(); - pathParams.put("", ""); - pathParams.put("", ""); - - // Step 4: Define the query parameters - // Query parameters are included in the URL after a '?' and are used to filter or modify the response - Map queryParams = new HashMap<>(); - queryParams.put("", ""); - queryParams.put("", ""); - - // Step 5: Build the InvokeConnectionRequest using the provided parameters - InvokeConnectionRequest invokeConnectionRequest = InvokeConnectionRequest.builder() - .method(RequestMethod.POST) // The HTTP method to use for the request (POST in this case) - .requestBody(requestBody) // The body of the request - .requestHeaders(requestHeaders) // The headers to include in the request - .pathParams(pathParams) // The path parameters for the URL - .queryParams(queryParams) // The query parameters to append to the URL - .build(); - - // Step 6: Invoke the connection using the request - // Replace "" with the actual connection ID you are using - InvokeConnectionResponse invokeConnectionResponse = skyflowClient.connection("").invoke(invokeConnectionRequest); - - // Step 7: Print the response from the invoked connection - // This response contains the result of the request sent to the external system - System.out.println(invokeConnectionResponse); - - } catch (SkyflowException e) { - // Step 8: Handle any exceptions that occur during the connection invocation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception stack trace for debugging - } - } -} -``` - -`method` accepts any [`RequestMethod`](docs/api_reference.md#requestmethod) value (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). See [`InvokeConnectionRequest`](docs/api_reference.md#invokeconnectionrequest) in the API Reference for all builder options. - -**pathParams, queryParams, requestHeader, requestBody** are the JSON objects represented as HashMaps, that will be sent through the connection integration url. - -### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/connection/InvokeConnectionExample.java) of invokeConnection - -```java -import com.skyflow.Skyflow; -import com.skyflow.config.ConnectionConfig; -import com.skyflow.config.Credentials; -import com.skyflow.enums.LogLevel; -import com.skyflow.enums.RequestMethod; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.connection.InvokeConnectionRequest; -import com.skyflow.vault.connection.InvokeConnectionResponse; - -import java.util.HashMap; -import java.util.Map; - -/** - * This example demonstrates how to invoke an external connection using the Skyflow SDK. - * It configures a connection, sets up the request, and sends a POST request to the external service. - * - * 1. Initialize Skyflow client with connection details. - * 2. Define the request body, headers, and method. - * 3. Execute the connection request. - * 4. Print the response from the invoked connection. - */ -public class InvokeConnectionExample { - public static void main(String[] args) { - try { - // Initialize Skyflow client - // Step 1: Set up credentials and connection configuration - // Load credentials from a JSON file (you need to provide the correct path) - Credentials credentials = new Credentials(); - credentials.setPath("/path/to/credentials.json"); - - // Define the connection configuration (URL and credentials) - ConnectionConfig connectionConfig = new ConnectionConfig(); - connectionConfig.setConnectionId(""); // Replace with actual connection ID - connectionConfig.setConnectionUrl("https://connection.url.com"); // Replace with actual connection URL - connectionConfig.setCredentials(credentials); // Set credentials for the connection - - // Initialize the Skyflow client with the connection configuration - Skyflow skyflowClient = Skyflow.builder() - .setLogLevel(LogLevel.DEBUG) // Set log level to DEBUG for detailed logs - .addConnectionConfig(connectionConfig) // Add connection configuration to client - .build(); // Build the Skyflow client instance - - // Step 2: Define the request body and headers - // Map for request body parameters - Map requestBody = new HashMap<>(); - requestBody.put("card_number", "4337-1696-5866-0865"); // Example card number - requestBody.put("ssn", "524-41-4248"); // Example SSN - - // Map for request headers - Map requestHeaders = new HashMap<>(); - requestHeaders.put("Content-Type", "application/json"); // Set content type for the request - - // Step 3: Build the InvokeConnectionRequest with required parameters - // Set HTTP method to POST, include the request body and headers - InvokeConnectionRequest invokeConnectionRequest = InvokeConnectionRequest.builder() - .method(RequestMethod.POST) // HTTP POST method - .requestBody(requestBody) // Add request body parameters - .requestHeaders(requestHeaders) // Add headers - .build(); // Build the request - - // Step 4: Invoke the connection and capture the response - // Replace "" with the actual connection ID - InvokeConnectionResponse invokeConnectionResponse = skyflowClient.connection("").invoke(invokeConnectionRequest); - - // Step 5: Print the response from the connection invocation - System.out.println(invokeConnectionResponse); // Print the response to the console - - } catch (SkyflowException e) { - // Step 6: Handle any exceptions that occur during the connection invocation - System.out.println("Error occurred: "); - e.printStackTrace(); // Print the exception stack trace for debugging - } - } -} -``` - -Sample response: - -```json -{ - "data": { - "card_number": "4337-1696-5866-0865", - "ssn": "524-41-4248" - }, - "metadata": { - "requestId": "4a3453b5-7aa4-4373-98d7-cf102b1f6f97" - } -} -``` - -# Authenticate with bearer tokens - -This section covers methods for generating and managing tokens to authenticate API calls: - -- **Generate a bearer token**: - Enable the creation of bearer tokens using service account credentials. These tokens, valid for 60 minutes, provide secure access to Vault services and management APIs based on the service account's permissions. Use this for general API calls when you only need basic authentication without additional context or role-based restrictions. -- **Generate a bearer token with context**: - Support embedding context values into bearer tokens, enabling dynamic access control and the ability to track end-user identity. These tokens include context claims and allow flexible authorization for Vault services. Use this when policies depend on specific contextual attributes or when tracking end-user identity is required. -- **Generate a scoped bearer token**: - Facilitate the creation of bearer tokens with role-specific access, ensuring permissions are limited to the operations allowed by the designated role. This is particularly useful for service accounts with multiple roles. Use this to enforce fine-grained role-based access control, ensuring tokens only grant permissions for a specific role. -- **Generate signed data tokens**: - Add an extra layer of security by digitally signing data tokens with the service account's private key. These signed tokens can be securely detokenized, provided the necessary bearer token and permissions are available. Use this to add cryptographic protection to sensitive data, enabling secure detokenization with verified integrity and authenticity. - -## Generate a bearer token - -The [Service Account](https://github.com/skyflowapi/skyflow-java/tree/main/src/main/java/com/skyflow/serviceaccount/util) Java module generates service account tokens using a service account credentials file, which is provided when a service account is created. The tokens generated by this module are valid for 60 minutes and can be used to make API calls to the [Data](https://docs.skyflow.com/record/) and [Management](https://docs.skyflow.com/management/) APIs, depending on the permissions assigned to the service account. - -The `BearerToken` utility class generates bearer tokens using a credentials JSON file. Alternatively, you can pass the credentials as a string. - -[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java): - -```java -/** - * Example program to generate a Bearer Token using Skyflow's BearerToken utility. - * The token can be generated in two ways: - * 1. Using the file path to a credentials.json file. - * 2. Using the JSON content of the credentials file as a string. - */ -public class BearerTokenGenerationExample { - public static void main(String[] args) { - // Variable to store the generated token - String token = null; - - // Example 1: Generate Bearer Token using a credentials.json file - try { - // Specify the full file path to the credentials.json file - String filePath = ""; - - // Check if the token is either not initialized or has expired - if (Token.isExpired(token)) { - // Create a BearerToken object using the credentials file - BearerToken bearerToken = BearerToken.builder() - .setCredentials(new File(filePath)) // Set credentials from the file path - .build(); - - // Generate a new Bearer Token - token = bearerToken.getBearerToken(); - } - - // Print the generated Bearer Token to the console - System.out.println("Generated Bearer Token (from file): " + token); - } catch (SkyflowException e) { - // Handle any exceptions encountered during the token generation process - e.printStackTrace(); - } - - // Example 2: Generate Bearer Token using the credentials JSON as a string - try { - // Provide the credentials JSON content as a string - String fileContents = ""; - - // Check if the token is either not initialized or has expired - if (Token.isExpired(token)) { - // Create a BearerToken object using the credentials string - BearerToken bearerToken = BearerToken.builder() - .setCredentials(fileContents) // Set credentials from the string - .build(); - - // Generate a new Bearer Token - token = bearerToken.getBearerToken(); - } - - // Print the generated Bearer Token to the console - System.out.println("Generated Bearer Token (from string): " + token); - } catch (SkyflowException e) { - // Handle any exceptions encountered during the token generation process - e.printStackTrace(); - } - } -} -``` - -## Generate bearer tokens with context - -**Context-aware authorization** embeds context values into a bearer token during its generation so you can reference those values in your policies. This enables more flexible access controls, such as helping you track end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization. - -A service account with the `context_id` identifier generates bearer tokens containing context information, represented as a JWT claim in a Skyflow-generated bearer token. Tokens generated from such service accounts include a `context_identifier` claim, are valid for 60 minutes, and can be used to make API calls to the Data and Management APIs, depending on the service account's permissions. - -The `setCtx()` method accepts either a **String** or a **`Map`**: - -**String context** — use when your policy references a single context value: - -```java -BearerToken token = BearerToken.builder() - .setCredentials(new File(filePath)) - .setCtx("user_12345") - .build(); -``` - -**JSON object context** — use when your policy needs multiple context values for conditional data access. Each key in the `Map` maps to a Skyflow CEL policy variable under `request.context.*`: - -```java -Map ctx = new HashMap<>(); -ctx.put("role", "admin"); -ctx.put("department", "finance"); -ctx.put("user_id", "user_12345"); - -BearerToken token = BearerToken.builder() - .setCredentials(new File(filePath)) - .setCtx(ctx) - .build(); -``` - -With the map above, your Skyflow policies can reference `request.context.role`, `request.context.department`, and `request.context.user_id` to make conditional access decisions. - -You can also set context on `Credentials` for automatic token generation: - -```java -// String context -Credentials credentials = new Credentials(); -credentials.setPath("path/to/credentials.json"); -credentials.setContext("user_12345"); - -// Map context -Map ctx = new HashMap<>(); -ctx.put("role", "admin"); -ctx.put("department", "finance"); -credentials.setContext(ctx); -``` - -> **Note:** `getContext()` returns `Object` — callers should use `instanceof` if they need to inspect the type. - -Context map keys must contain only alphanumeric characters and underscores (`[a-zA-Z0-9_]`). Invalid keys will throw a `SkyflowException`. - -[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java) - -See Skyflow's [context-aware authorization](https://docs.skyflow.com) and [conditional data access](https://docs.skyflow.com) docs for policy variable syntax like `request.context.*`. - -## Generate scoped bearer tokens - -A service account with multiple roles can generate bearer tokens with access limited to a specific role by specifying the appropriate `roleID`. This can be used to limit access to specific roles for services with multiple responsibilities, such as segregating access for billing and analytics. The generated bearer tokens are valid for 60 minutes and can only execute operations permitted by the permissions associated with the designated role. - -[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java): - -```java -import com.skyflow.errors.SkyflowException; -import com.skyflow.serviceaccount.util.BearerToken; - -import java.io.File; -import java.util.ArrayList; - -/** - * Example program to generate a Scoped Token using Skyflow's BearerToken utility. - * The token is generated by providing the file path to the credentials.json file - * and specifying roles associated with the token. - */ -public class ScopedTokenGenerationExample { - public static void main(String[] args) { - // Variable to store the generated scoped token - String scopedToken = null; - - // Example: Generate Scoped Token by specifying the credentials.json file path - try { - // Create a list of roles that the generated token will be scoped to - ArrayList roles = new ArrayList<>(); - roles.add("ROLE_ID"); // Add a specific role to the list (e.g., "ROLE_ID") - - // Specify the full file path to the service account's credentials.json file - String filePath = ""; - - // Create a BearerToken object using the credentials file and associated roles - BearerToken bearerToken = BearerToken.builder() - .setCredentials(new File(filePath)) // Set credentials using the credentials.json file - .setRoles(roles) // Set the roles that the token should be scoped to - .build(); // Build the BearerToken object - - // Retrieve the generated scoped token - scopedToken = bearerToken.getBearerToken(); - - // Print the generated scoped token to the console - System.out.println(scopedToken); - } catch (SkyflowException e) { - // Handle exceptions that may occur during token generation - e.printStackTrace(); - } - } -} -``` - -Notes: - -- You can pass either the file path of a service account key credentials file or the service account key credentials as a string to the `setCredentials` method of the `BearerTokenBuilder` class. -- If both a file path and a string are provided, the last method used takes precedence. -- To generate multiple bearer tokens concurrently using threads, refer to the following [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java). - -## Generate Signed Data Tokens - -Skyflow generates data tokens when sensitive data is inserted into the vault. These data tokens can be digitally signed -with the private key of the service account credentials, which adds an additional layer of protection. Signed tokens can -be detokenized by passing the signed data token and a bearer token generated from service account credentials. The -service account must have appropriate permissions and context to detokenize the signed data tokens. - -The `setCtx()` method on `SignedDataTokensBuilder` also accepts either a **String** or a **`Map`**, using the same format as bearer tokens: - -```java -// String context -SignedDataTokens signedToken = SignedDataTokens.builder() - .setCredentials(new File(filePath)) - .setCtx("user_12345") - .setTimeToLive(30) - .setDataTokens(dataTokens) - .build(); - -// JSON object context -Map ctx = new HashMap<>(); -ctx.put("role", "analyst"); -ctx.put("department", "research"); - -SignedDataTokens signedToken = SignedDataTokens.builder() - .setCredentials(new File(filePath)) - .setCtx(ctx) - .setTimeToLive(30) - .setDataTokens(dataTokens) - .build(); -``` - -[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java) - -Response: - -```json -[ - { - "dataToken": "5530-4316-0674-5748", - "signedDataToken": "signed_token_eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzLCpZjA" - } -] -``` - -Notes: - -- You can provide either the file path to a service account key credentials file or the service account key credentials as a string to the `setCredentials` method of the `SignedDataTokensBuilder` class. -- If both a file path and a string are passed to the `setCredentials` method, the most recently specified input takes precedence. -- The `time-to-live` (TTL) value should be specified in seconds. -- By default, the TTL value is set to 60 seconds. - -## Bearer token expiry edge case -When you use bearer tokens for authentication and API requests in SDKs, there's the potential for a token to expire after the token is verified as valid but before the actual API call is made, causing the request to fail unexpectedly due to the token's expiration. An error from this edge case would look something like this: - -```txt -message: Authentication failed. Bearer token is expired. Use a valid bearer token. See https://docs.skyflow.com/api-authentication/ -``` - -If you encounter this kind of error, retry the request. During the retry, the SDK detects that the previous bearer token has expired and generates a new one for the current and subsequent requests. - -#### [Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java): - -```java -package com.example.serviceaccount; - -import com.skyflow.Skyflow; -import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; -import com.skyflow.enums.Env; -import com.skyflow.enums.LogLevel; -import com.skyflow.enums.RedactionType; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.tokens.DetokenizeRequest; -import com.skyflow.vault.tokens.DetokenizeResponse; -import io.github.cdimascio.dotenv.Dotenv; -import java.util.ArrayList; - -/** - * This example demonstrates how to configure and use the Skyflow SDK - * to detokenize sensitive data stored in a Skyflow vault. - * It includes setting up credentials, configuring the vault, and - * making a detokenization request. The code also implements a retry - * mechanism to handle unauthorized access errors (HTTP 401). - */ -public class DetokenizeExample { - public static void main(String[] args) { - try { - // Setting up credentials for accessing the Skyflow vault - Credentials vaultCredentials = new Credentials(); - vaultCredentials.setCredentialsString(""); - - // Configuring the Skyflow vault with necessary details - VaultConfig vaultConfig = new VaultConfig(); - vaultConfig.setVaultId(""); // Vault ID - vaultConfig.setClusterId(""); // Cluster ID - vaultConfig.setEnv(Env.PROD); // Environment (e.g., DEV, PROD) - vaultConfig.setCredentials(vaultCredentials); // Setting credentials - - // Creating a Skyflow client instance with the configured vault - Skyflow skyflowClient = Skyflow.builder() - .setLogLevel(LogLevel.ERROR) // Setting log level to ERROR - .addVaultConfig(vaultConfig) // Adding vault configuration - .build(); - - // Attempting to detokenize data using the Skyflow client - try { - detokenizeData(skyflowClient); - } catch (SkyflowException e) { - // Retry detokenization if the error is due to unauthorized access (HTTP 401) - if (e.getHttpCode() == 401) { - detokenizeData(skyflowClient); - } else { - // Rethrow the exception for other error codes - throw e; - } - } - } catch (SkyflowException e) { - // Handling any exceptions that occur during the process - System.out.println("An error occurred: " + e.getMessage()); - } - } - - /** - * Method to detokenize data using the Skyflow client. - * It sends a detokenization request with a list of tokens and prints the response. - * - * @param skyflowClient The Skyflow client instance used for detokenization. - * @throws SkyflowException If an error occurs during the detokenization process. - */ - public static void detokenizeData(Skyflow skyflowClient) throws SkyflowException { - // Creating a list of tokens to be detokenized - ArrayList tokenList = new ArrayList<>(); - tokenList.add(""); // First token - tokenList.add(""); // Second token - - // Building a detokenization request with the token list and configuration - DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() - .tokens(tokenList) // Adding tokens to the request - .continueOnError(false) // Stop on error - .redactionType(RedactionType.PLAIN_TEXT) // Redaction type (e.g., PLAIN_TEXT) - .build(); - - // Sending the detokenization request and receiving the response - DetokenizeResponse detokenizeResponse = skyflowClient.vault().detokenize(detokenizeRequest); - - // Printing the detokenized response - System.out.println(detokenizeResponse); - } -} -``` - -# Client Management - -After the `Skyflow` client is built you can add, retrieve, update, or remove vault and connection configurations at runtime — without rebuilding the client. - -## Vault configuration management - -```java -import com.skyflow.config.VaultConfig; - -// Add a new vault at runtime -skyflowClient.addVaultConfig(newVaultConfig); - -// Retrieve the config for a specific vault -VaultConfig config = skyflowClient.getVaultConfig(""); - -// Update an existing vault config (match by vaultId) -skyflowClient.updateVaultConfig(updatedVaultConfig); - -// Remove a vault from the client -skyflowClient.removeVaultConfig(""); -``` - -## Connection configuration management - -```java -import com.skyflow.config.ConnectionConfig; - -// Add a new connection at runtime -skyflowClient.addConnectionConfig(newConnectionConfig); - -// Retrieve the config for a specific connection -ConnectionConfig config = skyflowClient.getConnectionConfig(""); - -// Update an existing connection config (match by connectionId) -skyflowClient.updateConnectionConfig(updatedConnectionConfig); - -// Remove a connection from the client -skyflowClient.removeConnectionConfig(""); -``` - -## Credentials and log level management - -```java -// Replace the Skyflow-level credentials used when vault/connection configs -// do not specify their own credentials -skyflowClient.updateSkyflowCredentials(newCredentials); - -// Update the log level after the client has been built -skyflowClient.updateLogLevel(LogLevel.DEBUG); - -// Read the current log level -LogLevel currentLevel = skyflowClient.getLogLevel(); -``` - -**Client management method reference:** - -| Method | Returns | Description | -|--------|---------|-------------| -| `addVaultConfig(VaultConfig)` | `Skyflow` | Add a vault configuration | -| `getVaultConfig(String vaultId)` | `VaultConfig` | Retrieve a vault configuration by ID | -| `updateVaultConfig(VaultConfig)` | `Skyflow` | Replace a vault configuration (matched by `vaultId`) | -| `removeVaultConfig(String vaultId)` | `Skyflow` | Remove a vault configuration | -| `addConnectionConfig(ConnectionConfig)` | `Skyflow` | Add a connection configuration | -| `getConnectionConfig(String connectionId)` | `ConnectionConfig` | Retrieve a connection configuration by ID | -| `updateConnectionConfig(ConnectionConfig)` | `Skyflow` | Replace a connection configuration | -| `removeConnectionConfig(String connectionId)` | `Skyflow` | Remove a connection configuration | -| `updateSkyflowCredentials(Credentials)` | `Skyflow` | Replace the client-level credentials | -| `updateLogLevel(LogLevel)` | `Skyflow` | Change the log level after initialization | -| `getLogLevel()` | `LogLevel` | Return the current log level | - -All mutating methods return the `Skyflow` instance for chaining and throw `SkyflowException` on validation errors. - -# Error Handling - -The SDK uses `SkyflowException` for all errors — both client-side validation errors and server-side API errors. - -## Catching SkyflowException - -Wrap SDK calls in a `try/catch` block and catch `SkyflowException` to handle Skyflow-specific errors separately from unexpected exceptions: - -```java -import com.skyflow.errors.SkyflowException; - -try { - InsertResponse response = skyflowClient.vault().insert(insertRequest); -} catch (SkyflowException e) { - System.err.println("Skyflow error:"); - System.err.println(" HTTP code : " + e.getHttpCode()); - System.err.println(" Message : " + e.getMessage()); - System.err.println(" Request ID: " + e.getRequestId()); - System.err.println(" Details : " + e.getDetails()); -} catch (Exception e) { - System.err.println("Unexpected error: " + e.getMessage()); -} -``` - -## SkyflowException properties - -| Property | Method | Description | -|---|---|---| -| HTTP status code | `getHttpCode()` | Integer status code (e.g. `400`, `404`, `500`). | -| Message | `getMessage()` | Human-readable description of the error. | -| HTTP status string | `getHttpStatus()` | Status string from the server (e.g. `"BAD_REQUEST"`). | -| gRPC code | `getGrpcCode()` | gRPC status code from the server. | -| Request ID | `getRequestId()` | The `x-request-id` header — useful for support escalations. | -| Details | `getDetails()` | `JsonArray` of additional error context from the server. Empty array for validation errors, `null` if the server response omitted the field. | - -**Validation errors** (missing table name, empty token list, etc.) are thrown before any network call: -- `httpCode` is always `400` -- `requestId` and `grpcCode` are `null` -- `details` is an empty array - -**API errors** are returned by the Skyflow server and have all fields populated from the response body and headers. - -# Logging - -The SDK provides logging with Java's built-in logging library. By default, the SDK's logging level is set to `LogLevel.ERROR`. This can be changed using the `setLogLevel(logLevel)` method, as shown below: - -Currently, the following five log levels are supported: +## Which package do I want? -- `DEBUG`**:** - When `LogLevel.DEBUG` is passed, logs at all levels will be printed (DEBUG, INFO, WARN, ERROR). -- `INFO`**:** - When `LogLevel.INFO` is passed, INFO logs for every event that occurs during SDK flow execution will be printed, along with WARN and ERROR logs. -- `WARN`**:** - When `LogLevel.WARN` is passed, only WARN and ERROR logs will be printed. -- `ERROR`**:** - When `LogLevel.ERROR` is passed, only ERROR logs will be printed. -- `OFF`**:** - `LogLevel.OFF` can be used to turn off all logging from the Skyflow Java SDK. +| Package | Artifact | README | Vault Type | Version line | +|---|---|---|---|---| +| **skyvault** | `com.skyflow:skyflow-java` | [skyvault/README.md](skyvault/README.md) | Privacy DB | 2.x | +| **flowvault** | `com.skyflow:skyflow-flowvault-java` | [flowvault/README.md](flowvault/README.md) | Flow DB | 1.x | -**Note:** The ranking of logging levels is as follows: `DEBUG` \< `INFO` \< `WARN` \< `ERROR` \< `OFF`. +`flowvault` shares auth/client setup with `skyvault` — both depend on the `common` module. -```java -import com.skyflow.Skyflow; -import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; -import com.skyflow.enums.Env; -import com.skyflow.enums.LogLevel; -import com.skyflow.errors.SkyflowException; +> **The two artifacts are versioned independently.** `flowvault` is a new SDK starting at `1.0.0`; its lower version number reflects a first release, not an older or lesser SDK than `skyvault` 2.x. Upgrade each on its own version line. -/** - * This example demonstrates how to configure the Skyflow client with custom log levels - * and authentication credentials (either token, credentials string, or other methods). - * It also shows how to configure a vault connection using specific parameters. - * - * 1. Set up credentials with a Bearer token or credentials string. - * 2. Define the Vault configuration. - * 3. Build the Skyflow client with the chosen configuration and set log level. - * 4. Example of changing the log level from ERROR (default) to INFO. - */ -public class ChangeLogLevel { - public static void main(String[] args) throws SkyflowException { - // Step 1: Set up credentials - either pass token or use credentials string - // In this case, we are using a Bearer token for authentication - Credentials credentials = new Credentials(); - credentials.setToken(""); // Replace with actual Bearer token +> Migrating from v1? See skyvault's **[Migration Guide](docs/migrate_to_v2.md)**. V1 is in maintenance mode and will reach End of Life on October 31, 2026. - // Step 2: Define the Vault configuration - // Configure the vault with necessary details like vault ID, cluster ID, and environment - VaultConfig config = new VaultConfig(); - config.setVaultId(""); // Replace with actual Vault ID (primary vault) - config.setClusterId(""); // Replace with actual Cluster ID (from vault URL) - config.setEnv(Env.PROD); // Set the environment (default is PROD) - config.setCredentials(credentials); // Set credentials for the vault (either token or credentials) +## Repository layout - // Step 3: Define additional Skyflow credentials (optional, if needed for credentials string) - // Create a JSON object to hold your Skyflow credentials - JsonObject credentialsObject = new JsonObject(); - credentialsObject.addProperty("clientId", ""); // Replace with your client ID - credentialsObject.addProperty("clientName", ""); // Replace with your client name - credentialsObject.addProperty("tokenUri", ""); // Replace with your token URI - credentialsObject.addProperty("keyId", ""); // Replace with your key ID - credentialsObject.addProperty("privateKey", ""); // Replace with your private key +The root `pom.xml` (`packaging=pom`) aggregates this Maven reactor: - // Convert the credentials object to a string format to be used for generating a Bearer Token - Credentials skyflowCredentials = new Credentials(); - skyflowCredentials.setCredentialsString(credentialsObject.toString()); // Set credentials string +- `common/` — shared client, credentials, config, and error-handling code used by both `skyvault` and `flowvault` +- `skyvault/` — the `skyflow-java` SDK ([README](skyvault/README.md)) +- `flowvault/` — the `skyflow-flowvault-java` SDK ([README](flowvault/README.md)) - // Step 4: Build the Skyflow client with the chosen configuration and log level - Skyflow skyflowClient = Skyflow.builder() - .addVaultConfig(config) // Add the Vault configuration - .addSkyflowCredentials(skyflowCredentials) // Use Skyflow credentials if no token is passed - .setLogLevel(LogLevel.INFO) // Set log level to INFO (default is ERROR) - .build(); // Build the Skyflow client +## Documentation - // Now, the Skyflow client is ready to use with the specified log level and credentials - System.out.println("Skyflow client has been successfully configured with log level: INFO."); - } -} -``` +- [skyvault API Reference](docs/api_reference.md) — full list of request builder methods, response getters, enums, and service-account utilities +- [Migrate from v1 to v2](docs/migrate_to_v2.md) -# Reporting a Vulnerability +## Reporting a Vulnerability If you discover a potential security issue in this project, please reach out to us at **security@skyflow.com**. Please do not create public GitHub issues or Pull Requests, as malicious actors could potentially view them. diff --git a/common/src/main/java/com/skyflow/BaseSkyflow.java b/common/src/main/java/com/skyflow/BaseSkyflow.java index a60dc02b..d02dc45a 100644 --- a/common/src/main/java/com/skyflow/BaseSkyflow.java +++ b/common/src/main/java/com/skyflow/BaseSkyflow.java @@ -69,7 +69,10 @@ protected static T resolveOrThrow(Map map, String key, ErrorLogs errorLog, ErrorMessage errorMessage) throws SkyflowException { T value = key != null ? map.get(key) : map.values().stream().findFirst().orElse(null); if (value == null) { - LogUtil.printErrorLog(errorLog.getLog()); + // The log line carries a %s1 placeholder for the id. Callers that resolve the single + // configured entry pass no key, so say so rather than emitting the raw placeholder. + LogUtil.printErrorLog(BaseUtils.parameterizedString( + errorLog.getLog(), key != null ? key : "not specified")); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), errorMessage.getMessage()); } return value; diff --git a/common/src/main/java/com/skyflow/errors/SkyflowException.java b/common/src/main/java/com/skyflow/errors/SkyflowException.java index 676bdd74..2b043f92 100644 --- a/common/src/main/java/com/skyflow/errors/SkyflowException.java +++ b/common/src/main/java/com/skyflow/errors/SkyflowException.java @@ -9,6 +9,33 @@ import java.util.List; import java.util.Map; +/** + * Exception thrown by all Skyflow SDK operations. + * + *

There are two broad categories of errors: + * + *

    + *
  • Validation errors — caught before any network call is made (e.g. missing table, + * empty token list). These always have {@code httpCode = 400} and an empty + * {@link #getDetails()} array. {@link #getRequestId()} and {@link #getGrpcCode()} are + * {@code null}. + *
  • API errors — returned by the Skyflow server. The HTTP status code, gRPC code, + * human-readable status string, error message, and request ID are all parsed from the + * response and available via the corresponding getters. + *
+ * + *

Typical error-handling pattern: + *

{@code
+ * try {
+ *     response = vault.insert(request);
+ * } catch (SkyflowException e) {
+ *     System.err.println("HTTP " + e.getHttpCode() + " — " + e.getMessage());
+ *     if (e.getRequestId() != null) {
+ *         System.err.println("Request ID: " + e.getRequestId());
+ *     }
+ * }
+ * }
+ */ public class SkyflowException extends Exception { private String requestId; private Integer grpcCode; @@ -33,6 +60,11 @@ public SkyflowException(String message, Throwable cause) { this.message = message; } + /** + * Constructs a validation error with a fixed HTTP 400 status. + * {@link #getDetails()} returns an empty array; {@link #getRequestId()} and + * {@link #getGrpcCode()} return {@code null}. + */ public SkyflowException(int code, String message) { super(message); this.httpCode = code; @@ -41,6 +73,13 @@ public SkyflowException(int code, String message) { this.details = new JsonArray(); } + /** + * Constructs an API error from an HTTP response. + * Parses the JSON error body to populate {@link #getMessage()}, {@link #getGrpcCode()}, + * {@link #getHttpStatus()}, and {@link #getDetails()}. The request ID is read from the + * {@code x-request-id} response header. If the body cannot be parsed, falls back to the + * raw body string as the message. + */ public SkyflowException(int httpCode, Throwable cause, Map> responseHeaders, String responseBody) { super(cause); this.httpCode = httpCode > 0 ? httpCode : 400; @@ -65,6 +104,10 @@ private void setResponseBody(String responseBody, Map> resp } } + /** + * Returns the {@code x-request-id} from the server response, useful for support escalations. + * {@code null} for validation errors that never reached the server. + */ public String getRequestId() { return requestId; } @@ -89,10 +132,20 @@ private void setHttpStatus() { this.httpStatus = statusElement == null ? null : statusElement.getAsString(); } + /** + * Returns the HTTP status code (e.g. 400, 404, 500). + * Defaults to 400 when the server returned a non-positive code, and 0 when the + * exception carries no HTTP code at all (e.g. it wraps a local failure). + */ public int getHttpCode() { return httpCode == null ? 0 : httpCode; } + /** + * Returns additional error details from the server response, or an empty array for + * validation errors. Never {@code null} for validation errors; may be {@code null} for + * API errors whose response body contained no {@code details} field. + */ public JsonArray getDetails() { return details; } @@ -112,10 +165,18 @@ private void setDetails(Map> responseHeaders) { } } + /** + * Returns the gRPC status code from the server response. + * {@code null} for validation errors and API responses that omit this field. + */ public Integer getGrpcCode() { return grpcCode; } + /** + * Returns the human-readable HTTP status string from the server response (e.g. + * {@code "Bad Request"}, {@code "Not Found"}). + */ public String getHttpStatus() { return httpStatus; } diff --git a/common/src/main/java/com/skyflow/vault/data/RequestContext.java b/common/src/main/java/com/skyflow/vault/data/RequestContext.java deleted file mode 100644 index 0cf6055b..00000000 --- a/common/src/main/java/com/skyflow/vault/data/RequestContext.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.skyflow.vault.data; - -import com.skyflow.enums.CustomHeaderKey; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -public final class RequestContext { - private final String operation; - private final Map headers = new HashMap<>(); - - public RequestContext(String operation) { - this.operation = operation; - } - - public String getOperation() { return operation; } - - public void addHeader(CustomHeaderKey key, String value) { - headers.put(key, value); - } - - public Map getHeaders() { - return Collections.unmodifiableMap(headers); - } -} diff --git a/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java b/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java index 83df09ee..1fb01e6e 100644 --- a/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java +++ b/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java @@ -138,6 +138,15 @@ public void testToStringWithNullFields() { Assert.assertTrue(str.contains("details: null")); } + // Regression: skyvault used to ship its own copy of this class whose getHttpCode() + // unboxed the null Integer and threw NPE for every constructor that takes no code. + @Test + public void testGetHttpCodeIsZeroWhenNoCodeWasSet() { + Assert.assertEquals(0, new SkyflowException("local failure").getHttpCode()); + Assert.assertEquals(0, new SkyflowException(new RuntimeException("boom")).getHttpCode()); + Assert.assertEquals(0, new SkyflowException("local failure", new RuntimeException("boom")).getHttpCode()); + } + @Test public void testZeroHttpCodeDefaultsTo400() { Map> headers = new HashMap<>(); diff --git a/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java b/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java deleted file mode 100644 index 0c6700d8..00000000 --- a/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.skyflow.vault.data; - -import com.skyflow.enums.CustomHeaderKey; -import org.junit.Assert; -import org.junit.Test; - -import java.util.Map; - -public class RequestContextTests { - - @Test - public void testGetOperationReturnsConstructorValue() { - RequestContext context = new RequestContext("INSERT"); - - Assert.assertEquals("INSERT", context.getOperation()); - } - - @Test - public void testNullOperation() { - RequestContext context = new RequestContext(null); - - Assert.assertNull(context.getOperation()); - } - - @Test - public void testGetHeadersReturnsEmptyMapByDefault() { - RequestContext context = new RequestContext("INSERT"); - - Assert.assertTrue(context.getHeaders().isEmpty()); - } - - @Test - public void testAddHeaderIsReflectedInGetHeaders() { - RequestContext context = new RequestContext("INSERT"); - context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value"); - - Map headers = context.getHeaders(); - - Assert.assertEquals(1, headers.size()); - Assert.assertEquals("account-id-value", headers.get(CustomHeaderKey.SkyflowAccountId)); - } - - @Test - public void testAddHeaderOverwritesExistingValueForSameKey() { - RequestContext context = new RequestContext("INSERT"); - context.addHeader(CustomHeaderKey.SkyflowAccountId, "first-value"); - context.addHeader(CustomHeaderKey.SkyflowAccountId, "second-value"); - - Assert.assertEquals(1, context.getHeaders().size()); - Assert.assertEquals("second-value", context.getHeaders().get(CustomHeaderKey.SkyflowAccountId)); - } - - @Test - public void testAddMultipleDistinctHeaders() { - RequestContext context = new RequestContext("DETOKENIZE"); - context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value"); - context.addHeader(CustomHeaderKey.SkyflowAccountName, "account-name-value"); - - Assert.assertEquals(2, context.getHeaders().size()); - } - - @Test(expected = UnsupportedOperationException.class) - public void testGetHeadersReturnsUnmodifiableMap() { - RequestContext context = new RequestContext("INSERT"); - - context.getHeaders().put(CustomHeaderKey.RequestIdHeader, "request-id-value"); - } -} diff --git a/flowvault/README.md b/flowvault/README.md new file mode 100644 index 00000000..442a1fbf --- /dev/null +++ b/flowvault/README.md @@ -0,0 +1,845 @@ +# Skyflow FlowVault Java SDK + +The `flowvault` module is a Skyflow Java SDK built for high-throughput vault operations. It shares its client, credentials, and configuration classes with the [skyvault SDK](../skyvault/README.md) (both depend on the `common` module) but exposes a different, narrower surface: **bulk** vault operations only. + +> Meant for **Flow DB** vaults. + +> **`flowvault` is a new SDK, versioned independently of `skyvault`.** It starts at `1.0.0` while `skyvault` (`com.skyflow:skyflow-java`) is at `2.x`. The two artifacts have separate version lines, so a lower `flowvault` version number does not mean it is older or behind — it is a first release, not a downgrade. Upgrade each artifact on its own. + +[![CI](https://img.shields.io/static/v1?label=CI&message=passing&color=green?style=plastic&logo=github)](https://github.com/skyflowapi/skyflow-java/actions) +[![License](https://img.shields.io/github/license/skyflowapi/skyflow-java)](https://github.com/skyflowapi/skyflow-java/blob/main/LICENSE) + +# Table of Contents + +- [Table of Contents](#table-of-contents) +- [Overview](#overview) +- [Install](#install) + - [Requirements](#requirements) + - [Configuration](#configuration) +- [Quickstart](#quickstart) +- [Authenticate](#authenticate) + - [Credential types](#credential-types) + - [Where credentials can be set](#where-credentials-can-be-set) + - [Generate a bearer token](#generate-a-bearer-token) + - [Context-aware and scoped tokens](#context-aware-and-scoped-tokens) +- [Initialize the client](#initialize-the-client) + - [VaultConfig reference](#vaultconfig-reference) + - [Skyflow.builder() reference](#skyflowbuilder-reference) + - [Timeouts and retries](#timeouts-and-retries) + - [Logging](#logging) +- [VaultController — Bulk operations](#vaultcontroller--bulk-operations) + - [Batching and concurrency](#batching-and-concurrency) +- [Bulk Insert](#bulk-insert) +- [Bulk Tokenize](#bulk-tokenize) +- [Bulk Detokenize](#bulk-detokenize) +- [Bulk Delete Tokens](#bulk-delete-tokens) +- [Custom Request Headers](#custom-request-headers) +- [Error Handling](#error-handling) + - [Two layers of errors](#two-layers-of-errors) + - [Per-record success and failure](#per-record-success-and-failure) + - [Catching SkyflowException](#catching-skyflowexception) + - [SkyflowException properties](#skyflowexception-properties) + - [Retrying the failed records](#retrying-the-failed-records) + +# Overview + +- Authenticate using a Skyflow service account, an API key, or a bearer token — see [Authenticate](#authenticate). +- Perform bulk Vault API operations — insert, tokenize, detokenize, and delete tokens — each with a synchronous and an async variant, built for high-throughput Flow DB workloads. +- **Per-record reporting, not all-or-nothing.** A bulk call succeeds as a call even when individual records fail; every response reports a summary plus the outcome of each individual record or token. See [Error Handling](#error-handling). + +# Install + +## Requirements + +- Java 8 and above + +## Configuration + +### Gradle users + +``` +implementation 'com.skyflow:skyflow-flowvault-java:1.0.0' +``` + +### Maven users + +```xml + + com.skyflow + skyflow-flowvault-java + 1.0.0 + +``` + +# Quickstart + +```java +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.vault.controller.VaultController; + +Credentials credentials = new Credentials(); +credentials.setApiKey(""); // or setToken / setCredentialsString / setPath + +VaultConfig vaultConfig = new VaultConfig(); +vaultConfig.setVaultId(""); +vaultConfig.setClusterId(""); // part of the vault URL, e.g. https://{clusterId}.vault.skyflowapis.com +vaultConfig.setEnv(Env.PROD); +vaultConfig.setCredentials(credentials); + +Skyflow skyflowClient = Skyflow.builder() + .addVaultConfig(vaultConfig) + .build(); + +// Returns the controller for the first configured vault +VaultController vault = skyflowClient.vault(); +``` + +`flowvault`'s `vault()` takes no arguments — it always resolves to the first vault added to the builder. Use one client per vault if you need to talk to more than one. + +# Authenticate + +Requests are authorized with Skyflow credentials that you attach to a `Credentials` object. `Credentials` comes from the shared `common` module, so it is the same class `skyvault` uses. + +## Credential types + +Set exactly one of the following on a `Credentials` instance. If you set more than one, **the last one set wins**. + +| Credential | Setter | What it is | +|---|---|---| +| API key | `setApiKey(String)` | A long-lived key that authenticates and authorizes requests to the API. Simplest option. | +| Bearer token | `setToken(String)` | A short-lived access token, typically generated from service account credentials. See [Generate a bearer token](#generate-a-bearer-token). | +| Credentials file path | `setPath(String)` | Filesystem path to a service account `credentials.json`. The SDK generates and refreshes bearer tokens from it. | +| Credentials string | `setCredentialsString(String)` | The contents of a service account `credentials.json` as a JSON string — use this when the credentials come from a secret store rather than a file. | + +Two optional modifiers apply when the SDK is generating tokens for you (that is, with `setPath` or `setCredentialsString`): + +| Setter | Description | +|---|---| +| `setRoles(ArrayList)` | Restrict the generated token to specific role IDs (a scoped token). | +| `setContext(String)` / `setContext(Map)` | Attach context to the generated token for context-aware authorization. | + +```java +// API key +Credentials apiKeyCredentials = new Credentials(); +apiKeyCredentials.setApiKey(""); + +// Bearer token you generated yourself +Credentials tokenCredentials = new Credentials(); +tokenCredentials.setToken(""); + +// Service account credentials file — the SDK handles token generation and refresh +Credentials fileCredentials = new Credentials(); +fileCredentials.setPath(""); + +// Service account credentials as a JSON string +Credentials stringCredentials = new Credentials(); +stringCredentials.setCredentialsString(""); +``` + +## Where credentials can be set + +Credentials resolve **most specific first**: + +1. **Per-vault** — `vaultConfig.setCredentials(credentials)`. Wins for that vault. +2. **Client-wide** — `Skyflow.builder().addSkyflowCredentials(credentials)`. Used by any vault that has none of its own. +3. **Environment** — if neither is provided, the SDK reads the `SKYFLOW_CREDENTIALS` environment variable. + +If none of the three yields credentials, the call fails with a `SkyflowException`. + +## Generate a bearer token + +If you would rather manage tokens yourself, `common` ships the same `BearerToken` utility as `skyvault`: + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.serviceaccount.util.BearerToken; + +import java.io.File; + +BearerToken token = BearerToken.builder() + .setCredentials(new File("")) // or setCredentials(credentialsJsonString) + .build(); + +String bearerToken = token.getBearerToken(); // cached and regenerated only when expired + +Credentials credentials = new Credentials(); +credentials.setToken(bearerToken); +``` + +`getBearerToken()` caches the token and only mints a new one once the current one has expired, so it is safe to call per request. + +## Context-aware and scoped tokens + +`BearerToken.builder()` also accepts `setCtx(String | Map)` for context-aware authorization and `setRoles(ArrayList)` for scoped tokens. Signed data tokens are available through `com.skyflow.serviceaccount.util.SignedDataTokens`. These utilities are identical to skyvault's — see [Authenticate with bearer tokens](../skyvault/README.md#authenticate-with-bearer-tokens) for worked examples of every variant. + +# Initialize the client + +`Skyflow` is the client. Build it once, keep it for the lifetime of your application, and get a `VaultController` from it with `vault()`. + +```java +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.controller.VaultController; + +public class InitFlowVaultClient { + public static void main(String[] args) throws SkyflowException { + // Step 1: Credentials — exactly one credential type + Credentials credentials = new Credentials(); + credentials.setPath(""); + + // Step 2: Vault configuration + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); // DEV, STAGE, SANDBOX, or PROD (default) + vaultConfig.setCredentials(credentials); + + // Optional: vault-level HTTP overrides + vaultConfig.setTimeout(120); // overall call timeout, in seconds + vaultConfig.setMaxRetries(2); // retries after the first failure + + // Step 3: Build the client + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.INFO) // default is ERROR + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Get the controller and issue bulk calls + VaultController vault = skyflowClient.vault(); + } +} +``` + +## VaultConfig reference + +| Setter | Type | Description | +|---|---|---| +| `setVaultId(String)` | required | The vault's ID. | +| `setClusterId(String)` | required | The cluster portion of the vault URL — `https://{clusterId}.vault.skyflowapis.com`. | +| `setEnv(Env)` | optional | `Env.DEV`, `Env.STAGE`, `Env.SANDBOX`, or `Env.PROD`. Defaults to `PROD`; passing `null` also resolves to `PROD`. | +| `setCredentials(Credentials)` | optional | Credentials for this vault. Falls back to client-wide credentials, then `SKYFLOW_CREDENTIALS`. | +| `setVaultUrl(String)` | optional | Full vault URL, when it cannot be derived from `clusterId` and `env`. | +| `setTimeout(Integer)` | optional | Overall call timeout in seconds, including retries. | +| `setConnectTimeout(Integer)` | optional | Per-attempt connection timeout, in seconds. | +| `setReadTimeout(Integer)` | optional | Per-attempt response-read timeout, in seconds. | +| `setWriteTimeout(Integer)` | optional | Per-attempt request-write timeout, in seconds. | +| `setMaxRetries(Integer)` | optional | Retry attempts after the first failure. | +| `setInitialRetryDelayMillis(Long)` | optional | Backoff before the first retry, in milliseconds. | +| `setMaxRetryDelayMillis(Long)` | optional | Ceiling the exponential backoff grows to, in milliseconds. | + +## Skyflow.builder() reference + +| Method | Description | +|---|---| +| `addVaultConfig(VaultConfig)` | Register a vault. The first one registered is what `vault()` returns. | +| `updateVaultConfig(VaultConfig)` | Update a registered vault in place. `null` fields mean "leave as is". | +| `removeVaultConfig(String vaultId)` | Unregister a vault. | +| `addSkyflowCredentials(Credentials)` | Client-wide credentials for vaults that don't set their own. | +| `setLogLevel(LogLevel)` | `DEBUG`, `INFO`, `WARN`, `ERROR` (default), or `OFF`. | +| `timeout(int)` / `connectTimeout(int)` / `readTimeout(int)` / `writeTimeout(int)` | Client-wide HTTP timeouts, in seconds. | +| `maxRetries(int)` / `initialRetryDelayMillis(long)` / `maxRetryDelayMillis(long)` | Client-wide retry policy. | +| `build()` | Produce the `Skyflow` client. | + +Every method throws `SkyflowException` on validation errors and returns the builder for chaining. + +## Timeouts and retries + +Each HTTP setting resolves **most specific first**: the value on `VaultConfig`, else the client-wide value on `Skyflow.builder()`, else the SDK default. Only `null` means "inherit" — an explicit `0` is a real value and overrides the level below it. + +| Setting | SDK default | +|---|---| +| `timeout` (overall call, incl. retries) | 60 s | +| `connectTimeout` / `readTimeout` / `writeTimeout` (per attempt) | 10 s (underlying HTTP client default) | +| `maxRetries` | `0` — retries are **opt-in**, so non-idempotent bulk writes are never replayed silently | +| `initialRetryDelayMillis` | 500 ms | +| `maxRetryDelayMillis` | 2000 ms | + +```java +// Client-wide policy, overridden for one vault +VaultConfig vaultConfig = new VaultConfig(); +vaultConfig.setVaultId(""); +vaultConfig.setClusterId(""); +vaultConfig.setCredentials(credentials); +vaultConfig.setTimeout(300); // this vault gets 300s... + +Skyflow skyflowClient = Skyflow.builder() + .timeout(60) // ...instead of the client-wide 60s + .maxRetries(3) // this vault inherits 3 retries + .initialRetryDelayMillis(500L) + .maxRetryDelayMillis(4000L) + .addVaultConfig(vaultConfig) + .build(); +``` + +## Logging + +The SDK logs through `java.util.logging` at `LogLevel.ERROR` by default. Levels rank `DEBUG` < `INFO` < `WARN` < `ERROR` < `OFF`; setting a level prints that level and everything above it. Change it with `Skyflow.builder().setLogLevel(LogLevel.DEBUG)`. + +# VaultController — Bulk operations + +`VaultController` is returned by `skyflowClient.vault()`. `flowvault` exposes these bulk vault operations: + +| Method | Parameters | Returns | Description | +|--------|-----------|---------|-------------| +| `bulkInsert(BulkInsertRequest)` | `BulkInsertRequest`, optional `BulkInsertOptions` | `BulkInsertResponse` | Insert many records, optionally across multiple tables, in one call | +| `bulkInsertAsync(BulkInsertRequest)` | same | `CompletableFuture` | Async variant of `bulkInsert` | +| `bulkTokenize(BulkTokenizeRequest)` | `BulkTokenizeRequest`, optional `BulkTokenizeOptions` | `BulkTokenizeResponse` | Tokenize many values, each against one or more named token groups | +| `bulkTokenizeAsync(BulkTokenizeRequest)` | same | `CompletableFuture` | Async variant of `bulkTokenize` | +| `bulkDetokenize(BulkDetokenizeRequest)` | `BulkDetokenizeRequest`, optional `BulkDetokenizeOptions` | `BulkDetokenizeResponse` | Detokenize many tokens, optionally with a redaction override per token group | +| `bulkDetokenizeAsync(BulkDetokenizeRequest)` | same | `CompletableFuture` | Async variant of `bulkDetokenize` | +| `bulkDeleteTokens(BulkDeleteTokensRequest)` | `BulkDeleteTokensRequest`, optional `BulkDeleteTokensOptions` | `BulkDeleteTokensResponse` | Delete many tokens in one call | +| `bulkDeleteTokensAsync(BulkDeleteTokensRequest)` | same | `CompletableFuture` | Async variant of `bulkDeleteTokens` | + +Each method also accepts an optional options object (`BulkInsertOptions`, `BulkTokenizeOptions`, `BulkDetokenizeOptions`, `BulkDeleteTokensOptions`) — see [Custom Request Headers](#custom-request-headers). + +A single bulk call accepts at most **10,000** records or tokens; anything larger is rejected up front with a `SkyflowException`. Under that ceiling the SDK splits the payload into batches and sends them concurrently, which is why errors from one call can carry different `requestId` values. + +Every bulk response has the same two-part shape: + +- a **summary** — totals for the call (e.g. `totalRecords` / `totalInserted` / `totalFailed` for insert) +- a **records** list — one entry per submitted record or token, in input order, each carrying its own `index`, `httpCode`, and `error` + +That per-record shape is the point of these APIs; see [Error Handling](#error-handling) for the full model. + +## Batching and concurrency + +Batch size and concurrency are configured **per operation** through environment variables — there is no builder or options API for them. Each value is read from the process environment first, then from a `.env` file in the working directory. + +| Operation | Batch size variable | Default | Max | Concurrency variable | Default | Max | +|-----------|--------------------|---------|-----|---------------------|---------|-----| +| Bulk insert | `INSERT_BATCH_SIZE` | 50 | 1000 | `INSERT_CONCURRENCY_LIMIT` | 1 | 10 | +| Bulk tokenize | `TOKENIZE_BATCH_SIZE` | 50 | 1000 | `TOKENIZE_CONCURRENCY_LIMIT` | 1 | 10 | +| Bulk detokenize | `DETOKENIZE_BATCH_SIZE` | 50 | 1000 | `DETOKENIZE_CONCURRENCY_LIMIT` | 1 | 10 | +| Bulk delete tokens | `DELETE_TOKENS_BATCH_SIZE` | 50 | 1000 | `DELETE_TOKENS_CONCURRENCY_LIMIT` | 1 | 10 | + +Concurrency defaults to **1**, so batches are sent one after another unless you raise the limit. + +How each value is resolved: + +- **Batch size** — `min(yourValue, max)`. Above the max, the SDK logs a warning and uses the max. Zero, negative, or non-numeric values log a warning and fall back to the default. +- **Concurrency** — `min(yourValue, max, batchCount)`, where `batchCount = ceil(itemCount / batchSize)`. Concurrency never exceeds the number of batches there are to run. Same warning-and-fallback behaviour for invalid values. + +Those warnings are emitted at `WARN`, which the default `ERROR` level hides — set `LogLevel.WARN` or below to see them (see [Logging](#logging)). + +For example, 500 records with `INSERT_BATCH_SIZE=100` and `INSERT_CONCURRENCY_LIMIT=10` produces 5 batches, all 5 in flight at once — the concurrency is capped to 5, not 10. + +```dotenv +# .env +INSERT_BATCH_SIZE=100 +INSERT_CONCURRENCY_LIMIT=5 +``` + +The 10,000-item ceiling per bulk call is a separate, fixed limit and is not configurable. + +# Bulk Insert + +Insert many records — even across different tables — in a single call. Each record is a `BulkInsertRequestRecord` with its own `data` and, optionally, its own `tableName` and `upsert`. + +**Note:** + +- `tableName` must be specified at exactly one level: either on the request (`BulkInsertRequest.builder().tableName(...)`) or on **every** record (`BulkInsertRequestRecord.builder().tableName(...)`) — not both, and not neither. +- `upsert` is optional, but wherever you supply it, it must sit at the same level as `tableName`. Request-level `tableName` pairs with request-level `upsert`; record-level `tableName` pairs with per-record `upsert`. +- `UpsertOptions` requires `uniqueColumns`. `updateType` accepts `"UPDATE"` (the default) or `"REPLACE"`. + +### Construct a bulk insert request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.UpsertOptions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BulkInsertExample { + public static void main(String[] args) throws SkyflowException { + // Step 1: Build each record. Here tableName lives on the records, so each one carries it. + Map record1Data = new HashMap<>(); + record1Data.put("card_number", "4111111111111111"); + record1Data.put("cardholder_name", "john doe"); + + BulkInsertRequestRecord record1 = BulkInsertRequestRecord.builder() + .tableName("table1") + .data(record1Data) + .build(); + + Map record2Data = new HashMap<>(); + record2Data.put("email", "jane.doe@example.com"); + + BulkInsertRequestRecord record2 = BulkInsertRequestRecord.builder() + .tableName("table2") + .data(record2Data) + // upsert sits at the record level here, matching where tableName sits + .upsert(UpsertOptions.builder() + .uniqueColumns(Arrays.asList("email")) + .updateType("UPDATE") + .build()) + .build(); + + List records = new ArrayList<>(); + records.add(record1); + records.add(record2); + + // Step 2: Build the BulkInsertRequest + BulkInsertRequest insertRequest = BulkInsertRequest.builder() + .records(records) + .build(); + + // Step 3: Perform the bulk insert + BulkInsertResponse insertResponse = vault.bulkInsert(insertRequest); + System.out.println(insertResponse); + } +} +``` + +To put the table name on the request instead, drop `tableName` from every record and build the request as: + +```java +BulkInsertRequest insertRequest = BulkInsertRequest.builder() + .tableName("table1") + .upsert(UpsertOptions.builder().uniqueColumns(Arrays.asList("email")).build()) + .records(records) + .build(); +``` + +### Async bulk insert + +```java +import java.util.concurrent.CompletableFuture; + +CompletableFuture future = vault.bulkInsertAsync(insertRequest); +future.thenAccept(response -> System.out.println(response)); +``` + +Sample response: + +```json +{ + "summary": { "totalRecords": 2, "totalInserted": 1, "totalFailed": 1 }, + "records": [ + { + "index": 0, + "requestId": null, + "tableName": "table1", + "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1", + "fields": { "card_number": "5484-7829-1702-9110", "cardholder_name": "b2308e2a-c1f5-469b-97b7-1f193159399b" }, + "hashedData": null, + "httpCode": 200, + "error": null + }, + { + "index": 1, + "requestId": "a1b2c3d4-...", + "tableName": "table2", + "skyflowId": null, + "fields": null, + "hashedData": null, + "httpCode": 400, + "error": "Insert failed. Column email is invalid." + } + ] +} +``` + +Accessors: `insertResponse.getSummary()`, `insertResponse.getRecords()`, and on each record `getIndex()`, `getTableName()`, `getSkyflowId()`, `getFields()`, `getHashedData()`, `getHttpCode()`, `getError()`, `getRequestId()`. + +Use `insertResponse.getRecordsToRetry()` to get back only the `BulkInsertRequestRecord`s worth resubmitting — see [Retrying the failed records](#retrying-the-failed-records). + +# Bulk Tokenize + +Tokenize many values in one call. Each value can be tokenized against one or more named token groups. + +### Construct a bulk tokenize request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BulkTokenizeExample { + public static void main(String[] args) throws SkyflowException { + BulkTokenizeRequestRecord record1 = BulkTokenizeRequestRecord.builder() + .value("4111111111111111") + .tokenGroupNames(Arrays.asList("card_number_cg")) + .build(); + + BulkTokenizeRequestRecord record2 = BulkTokenizeRequestRecord.builder() + .value("john.doe@example.com") + .tokenGroupNames(Arrays.asList("email_cg")) + .build(); + + List records = new ArrayList<>(); + records.add(record1); + records.add(record2); + + BulkTokenizeRequest tokenizeRequest = BulkTokenizeRequest.builder() + .records(records) + .build(); + + BulkTokenizeResponse tokenizeResponse = vault.bulkTokenize(tokenizeRequest); + System.out.println(tokenizeResponse); + } +} +``` + +`BulkTokenizeRequestRecord.builder()` also accepts `token(Object)` to supply your own token for the value instead of having the vault generate one. + +### Async bulk tokenize + +```java +CompletableFuture future = vault.bulkTokenizeAsync(tokenizeRequest); +``` + +Sample response: + +```json +{ + "summary": { "totalTokens": 2, "totalTokenized": 1, "totalPartial": 0, "totalFailed": 1 }, + "records": [ + { + "index": 0, + "value": "4111111111111111", + "tokens": [ + { "tokenGroupName": "card_number_cg", "token": "5479-4229-4622-1393", "httpCode": 200, "error": null, "requestId": null } + ] + }, + { + "index": 1, + "value": "john.doe@example.com", + "tokens": [ + { "tokenGroupName": "email_cg", "token": null, "httpCode": 400, "error": "Token group email_cg not found.", "requestId": "a1b2c3d4-..." } + ] + } + ] +} +``` + +Tokenize reports at **two** levels: one entry per input value in `records`, and inside each of those, one entry per requested token group in `tokens`. Because a single value can map to several token groups, the summary distinguishes fully tokenized values (`totalTokenized`), partially tokenized values where some groups succeeded and others failed (`totalPartial`), and fully failed values (`totalFailed`). The three always add up to `totalTokens`, which counts input values, not tokens produced. + +# Bulk Detokenize + +Detokenize many tokens in one call, optionally overriding the redaction applied per token group via `tokenGroupRedactions`. + +### Construct a bulk detokenize request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.TokenGroupRedactions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BulkDetokenizeExample { + public static void main(String[] args) throws SkyflowException { + List tokens = new ArrayList<>(Arrays.asList( + "5479-4229-4622-1393", + "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + )); + + // redaction is a free-form string understood by the vault (e.g. "PLAIN_TEXT", + // "MASKED", "REDACTED", "DEFAULT" — the same redaction types as skyvault's RedactionType enum) + TokenGroupRedactions redaction = TokenGroupRedactions.builder() + .tokenGroupName("card_number_cg") + .redaction("MASKED") + .build(); + + BulkDetokenizeRequest detokenizeRequest = BulkDetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(Arrays.asList(redaction)) + .build(); + + BulkDetokenizeResponse detokenizeResponse = vault.bulkDetokenize(detokenizeRequest); + System.out.println(detokenizeResponse); + } +} +``` + +### Async bulk detokenize + +```java +CompletableFuture future = vault.bulkDetokenizeAsync(detokenizeRequest); +``` + +Sample response: + +```json +{ + "summary": { "totalTokens": 2, "totalDetokenized": 1, "totalFailed": 1 }, + "records": [ + { + "index": 0, + "requestId": null, + "value": "4111111111111111", + "tokenGroupName": "card_number_cg", + "metadata": {}, + "httpCode": 200, + "token": "5479-4229-4622-1393", + "error": null + }, + { + "index": 1, + "requestId": "a1b2c3d4-...", + "value": null, + "tokenGroupName": null, + "metadata": null, + "httpCode": 404, + "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "error": "Token Not Found" + } + ] +} +``` + +Use `detokenizeResponse.getTokensToRetry()` to get back only the tokens worth resubmitting. + +# Bulk Delete Tokens + +Delete many tokens in one call. + +### Construct a bulk delete tokens request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class BulkDeleteTokensExample { + public static void main(String[] args) throws SkyflowException { + List tokens = new ArrayList<>(Arrays.asList( + "5479-4229-4622-1393", + "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + )); + + BulkDeleteTokensRequest deleteTokensRequest = BulkDeleteTokensRequest.builder() + .tokens(tokens) + .build(); + + BulkDeleteTokensResponse deleteTokensResponse = vault.bulkDeleteTokens(deleteTokensRequest); + System.out.println(deleteTokensResponse); + } +} +``` + +### Async bulk delete tokens + +```java +CompletableFuture future = vault.bulkDeleteTokensAsync(deleteTokensRequest); +``` + +Sample response: + +```json +{ + "summary": { "totalTokens": 2, "totalDeleted": 2, "totalFailed": 0 }, + "records": [ + { "index": 0, "token": "5479-4229-4622-1393", "httpCode": 200, "error": null, "requestId": null }, + { "index": 1, "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "httpCode": 200, "error": null, "requestId": null } + ] +} +``` + +Use `deleteTokensResponse.getTokensToRetry()` to get back only the tokens worth resubmitting. + +# Custom Request Headers + +To include custom HTTP headers on an outgoing bulk request, pass a `RequestInterceptor` via that operation's options object. The headers available are defined by the `CustomHeaderKey` enum: + +| `CustomHeaderKey` | HTTP header name | +|---|---| +| `SkyflowAccountId` | `x-skyflow-account-id` | +| `SkyflowAccountName` | `x-skyflow-account-name` | +| `RequestIdHeader` | `x-request-id` | + +```java +import com.skyflow.enums.CustomHeaderKey; +import com.skyflow.vault.data.BulkInsertOptions; + +BulkInsertOptions options = BulkInsertOptions.builder() + .interceptor(context -> context.addHeader(CustomHeaderKey.RequestIdHeader, "")) + .build(); + +BulkInsertResponse insertResponse = vault.bulkInsert(insertRequest, options); +``` + +The interceptor runs **once per batch**, not once per bulk call — so a value generated inside it (a fresh request id, say) differs between the batches a single bulk call is split into. + +The same pattern applies to every bulk operation, via its corresponding options class: + +| Operation | Options class | +|---|---| +| `bulkInsert` / `bulkInsertAsync` | `BulkInsertOptions` | +| `bulkTokenize` / `bulkTokenizeAsync` | `BulkTokenizeOptions` | +| `bulkDetokenize` / `bulkDetokenizeAsync` | `BulkDetokenizeOptions` | +| `bulkDeleteTokens` / `bulkDeleteTokensAsync` | `BulkDeleteTokensOptions` | + +# Error Handling + +## Two layers of errors + +This is the mental model to hold for every bulk operation: + +| Layer | What it covers | How you see it | +|---|---|---| +| **Request-level** | The call could not be made or the whole call failed: invalid request shape, missing credentials, auth failure, payload over the 10,000-item limit. | A thrown `SkyflowException`. No results at all. | +| **Record-level** | The call succeeded, but individual records or tokens inside it did not. | A returned response. **Nothing is thrown.** Each entry in `getRecords()` reports its own `httpCode` and `error`. | + +The second layer is what distinguishes `flowvault` from an all-or-nothing API: **a bulk call that returns normally can still contain failures, and a call where every single record failed also returns normally rather than throwing.** Checking only for a thrown exception will silently miss failed records — always read the summary and the per-record results. + +## Per-record success and failure + +Every bulk response exposes `getSummary()` and `getRecords()`. The records list has one entry per submitted item, in the order you submitted it, and each entry carries: + +| Field | Present on | Meaning | +|---|---|---| +| `getIndex()` | always | Position of this item in the payload you submitted — use it to line results back up with your input. | +| `getHttpCode()` | always | Per-item status. `2xx` for success; `4xx`/`5xx` for failure. | +| `getError()` | failures only | Error message for this item. `null` means this item succeeded. | +| `getRequestId()` | failures only | The `x-request-id` of the batch this item was in — quote it in support escalations. Items from the same batch share one id. | + +The success payload sits alongside those fields on the same object: `getSkyflowId()`/`getFields()` for insert, `getValue()`/`getTokenGroupName()`/`getMetadata()` for detokenize, `getTokens()` for tokenize, `getToken()` for delete. + +Summaries per operation: + +| Response | Summary type | Fields | +|---|---|---| +| `BulkInsertResponse` | `BulkSummary` | `totalRecords`, `totalInserted`, `totalFailed` | +| `BulkTokenizeResponse` | `TokenizeSummary` | `totalTokens`, `totalTokenized`, `totalPartial`, `totalFailed` | +| `BulkDetokenizeResponse` | `DetokenizeSummary` | `totalTokens`, `totalDetokenized`, `totalFailed` | +| `BulkDeleteTokensResponse` | `DeleteTokensSummary` | `totalTokens`, `totalDeleted`, `totalFailed` | + +The idiomatic way to consume a bulk response: + +```java +BulkInsertResponse response = vault.bulkInsert(insertRequest); + +System.out.println("inserted " + response.getSummary().getTotalInserted() + + " of " + response.getSummary().getTotalRecords()); + +for (BulkInsertResponseRecord record : response.getRecords()) { + if (record.getError() == null) { + System.out.println("row " + record.getIndex() + " -> " + record.getSkyflowId()); + } else { + System.err.println("row " + record.getIndex() + " failed [" + + record.getHttpCode() + "] " + record.getError() + + " (requestId " + record.getRequestId() + ")"); + } +} +``` + +For tokenize, the check is one level deeper, because a single value can partially succeed: + +```java +for (BulkTokenizeResponseRecord record : tokenizeResponse.getRecords()) { + for (TokenizeResponseToken token : record.getTokens()) { + if (token.getError() == null) { + System.out.println(record.getIndex() + "/" + token.getTokenGroupName() + + " -> " + token.getToken()); + } else { + System.err.println(record.getIndex() + "/" + token.getTokenGroupName() + + " failed [" + token.getHttpCode() + "] " + token.getError()); + } + } +} +``` + +## Catching SkyflowException + +`SkyflowException` covers the request-level layer only — client-side validation errors and whole-call API errors. It comes from `common`, so it is the same exception type `skyvault` throws. + +```java +import com.skyflow.errors.SkyflowException; + +try { + BulkInsertResponse response = vault.bulkInsert(insertRequest); + // reaching here means the CALL succeeded — individual records may still have failed +} catch (SkyflowException e) { + System.err.println("Skyflow error:"); + System.err.println(" HTTP code : " + e.getHttpCode()); + System.err.println(" Message : " + e.getMessage()); + System.err.println(" Request ID: " + e.getRequestId()); + System.err.println(" Details : " + e.getDetails()); +} catch (Exception e) { + System.err.println("Unexpected error: " + e.getMessage()); +} +``` + +For the async variants, the same exception arrives wrapped in a `CompletionException`: + +```java +vault.bulkInsertAsync(insertRequest) + .thenAccept(response -> System.out.println(response)) + .exceptionally(throwable -> { + System.err.println("bulk insert failed: " + throwable.getCause().getMessage()); + return null; + }); +``` + +## SkyflowException properties + +| Property | Method | Description | +|---|---|---| +| HTTP status code | `getHttpCode()` | Integer status code (e.g. `400`, `404`, `500`). | +| Message | `getMessage()` | Human-readable description of the error. | +| HTTP status string | `getHttpStatus()` | Status string from the server (e.g. `"BAD_REQUEST"`). | +| gRPC code | `getGrpcCode()` | gRPC status code from the server. | +| Request ID | `getRequestId()` | The `x-request-id` header — useful for support escalations. | +| Details | `getDetails()` | `JsonArray` of additional error context from the server. Empty array for validation errors, `null` if the server response omitted the field. | + +**Validation errors** (table name at the wrong level, empty token list, payload over 10,000 items, and similar) are thrown before any network call: + +- `httpCode` is always `400` +- `requestId` and `grpcCode` are `null` +- `details` is an empty array + +**API errors** are returned by the Skyflow server and have all fields populated from the response body and headers. + +## Retrying the failed records + +Because failures are reported per record, a partial failure can be retried without resubmitting the whole payload. Each response exposes a retry helper that filters its records down to the ones worth resending — **server-side failures (HTTP 500–599), excluding 529**, which is a permanent capacity-limit code: + +| Response | Helper | Returns | +|---|---|---| +| `BulkInsertResponse` | `getRecordsToRetry()` | `List` — your original record objects, ready to resubmit | +| `BulkTokenizeResponse` | `getRecordsToRetry()` | `List` — values with at least one retryable token-group failure | +| `BulkDetokenizeResponse` | `getTokensToRetry()` | `List` — the tokens to resubmit | +| `BulkDeleteTokensResponse` | `getTokensToRetry()` | `List` — the tokens to resubmit | + +```java +BulkInsertResponse response = vault.bulkInsert(insertRequest); + +List retryable = response.getRecordsToRetry(); +if (!retryable.isEmpty()) { + BulkInsertResponse retryResponse = vault.bulkInsert( + BulkInsertRequest.builder() + .tableName("table1") + .records(new ArrayList<>(retryable)) + .build()); +} +``` + +Client-side (`4xx`) failures are deliberately excluded — those need a fix to the data, not a retry. This is separate from the transport-level `maxRetries` setting in [Timeouts and retries](#timeouts-and-retries), which retries whole HTTP attempts and is off by default. diff --git a/flowvault/api-report/skyflow-flowvault-java.baseline.jar b/flowvault/api-report/skyflow-flowvault-java.baseline.jar new file mode 100644 index 00000000..d98e0eb7 Binary files /dev/null and b/flowvault/api-report/skyflow-flowvault-java.baseline.jar differ diff --git a/flowvault/pom.xml b/flowvault/pom.xml index 9b34fa4d..5a8f014f 100644 --- a/flowvault/pom.xml +++ b/flowvault/pom.xml @@ -11,7 +11,7 @@ skyflow-flowvault-java - 3.0.0-beta.13-dev.f72e2218 + 3.0.0-beta.13-dev.c7311820 jar ${project.groupId}:${project.artifactId} Skyflow V3 SDK for the Java programming language @@ -77,6 +77,96 @@ + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + shade-for-japicmp + package + + shade + + + true + with-common + + + com.skyflow:common + + + + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + 0.26.0 + + + + + ${project.basedir}/api-report/skyflow-flowvault-java.baseline.jar + + + + + ${project.build.directory}/${project.build.finalName}-with-common.jar + + + + protected + true + true + true + true + + + com.skyflow.Skyflow + com.skyflow.config + com.skyflow.enums + com.skyflow.errors + com.skyflow.serviceaccount.util + com.skyflow.vault.controller + com.skyflow.vault.data + + false + false + + + + + default-cli + verify + + cmp + + + + @@ -118,7 +208,13 @@ central true - true + + false diff --git a/flowvault/samples/pom.xml b/flowvault/samples/pom.xml index 390c0251..86d525a7 100644 --- a/flowvault/samples/pom.xml +++ b/flowvault/samples/pom.xml @@ -18,24 +18,10 @@ com.skyflow skyflow-flowvault-java - 3.0.0-beta.13-dev.0d4db0a + 3.0.0-beta.13-dev.18f8f1ba - - - central - https://prekarilabs.jfrog.io/artifactory/skyflow-java - - false - - - - maven_central - Maven Central - https://repo.maven.apache.org/maven2/ - - \ No newline at end of file diff --git a/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java b/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java index 48889218..84e8ff55 100644 --- a/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java +++ b/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java @@ -5,20 +5,20 @@ import com.skyflow.config.VaultConfig; import com.skyflow.enums.Env; import com.skyflow.enums.LogLevel; -import com.skyflow.enums.RedactionType; import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.tokens.DetokenizeData; -import com.skyflow.vault.tokens.DetokenizeRequest; -import com.skyflow.vault.tokens.DetokenizeResponse; -import io.github.cdimascio.dotenv.Dotenv; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; + import java.util.ArrayList; +import java.util.List; /** * This example demonstrates how to configure and use the Skyflow SDK * to detokenize sensitive data stored in a Skyflow vault. * It includes setting up credentials, configuring the vault, and - * making a detokenization request. The code also implements a retry - * mechanism to handle unauthorized access errors (HTTP 401). + * making a bulk detokenization request. The code also implements a retry + * mechanism to handle unauthorized access errors (HTTP 401), e.g. when + * the bearer token minted from the credentials has expired. */ public class BearerTokenExpiryExample { public static void main(String[] args) { @@ -60,27 +60,24 @@ public static void main(String[] args) { /** * Method to detokenize data using the Skyflow client. - * It sends a detokenization request with a list of tokens and prints the response. + * It sends a bulk detokenization request with a list of tokens and prints the response. * * @param skyflowClient The Skyflow client instance used for detokenization. * @throws SkyflowException If an error occurs during the detokenization process. */ public static void detokenizeData(Skyflow skyflowClient) throws SkyflowException { // Creating a list of tokens to be detokenized - DetokenizeData detokenizeDataToken1 = new DetokenizeData("", RedactionType.PLAIN_TEXT); - DetokenizeData detokenizeDataToken2 = new DetokenizeData(""); - ArrayList detokenizeDataList = new ArrayList<>(); - detokenizeDataList.add(detokenizeDataToken1); // First token - detokenizeDataList.add(detokenizeDataToken2); // Second token + List tokens = new ArrayList<>(); + tokens.add(""); // First token + tokens.add(""); // Second token - // Building a detokenization request with the token list and configuration - DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() - .detokenizeData(detokenizeDataList) // Adding tokens to the request - .continueOnError(false) // Stop on error + // Building a bulk detokenization request with the token list + BulkDetokenizeRequest detokenizeRequest = BulkDetokenizeRequest.builder() + .tokens(tokens) // Adding tokens to the request .build(); // Sending the detokenization request and receiving the response - DetokenizeResponse detokenizeResponse = skyflowClient.vault().detokenize(detokenizeRequest); + BulkDetokenizeResponse detokenizeResponse = skyflowClient.vault().bulkDetokenize(detokenizeRequest); // Printing the detokenized response System.out.println(detokenizeResponse); diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkDeleteTokensAsync.java b/flowvault/samples/src/main/java/com/example/vault/BulkDeleteTokensAsync.java new file mode 100644 index 00000000..786d6461 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkDeleteTokensAsync.java @@ -0,0 +1,93 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * This sample demonstrates how to perform an asynchronous bulk delete tokens operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating a list of tokens to delete + * 3. Building and executing an async bulk delete tokens request + * 4. Reading the per-token outcome and the summary from the response + * 5. Handling the delete response or errors using CompletableFuture + */ +public class BulkDeleteTokensAsync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials using credentials string + String credentialsString = ""; + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare list of tokens to delete. The SDK assigns each token an index from its + // position in this list and returns it on the matching response record, so results stay + // correlated even though the batches complete out of order. + List tokens = new ArrayList<>(); + tokens.add(""); + tokens.add(""); + + // Step 5: Build the bulk delete tokens request + BulkDeleteTokensRequest deleteTokensRequest = BulkDeleteTokensRequest.builder() + .tokens(tokens) + .build(); + + // Step 6: Execute the async bulk delete tokens operation and handle response using callbacks + CompletableFuture future = + skyflowClient.vault().bulkDeleteTokensAsync(deleteTokensRequest); + future.thenAccept(response -> { + System.out.println("Async bulk delete tokens resolved with response:\t" + response); + + // Successes and failures share one list: a record succeeded when its error is null. + // requestId identifies the API call an error came from and is set on failures only. + for (BulkDeleteTokensResponseRecord record : response.getRecords()) { + if (record.getError() == null) { + System.out.printf("[%d] %s deleted (%d)%n", + record.getIndex(), record.getToken(), record.getHttpCode()); + } else { + System.out.printf("[%d] %s failed (%d): %s [requestId=%s]%n", + record.getIndex(), record.getToken(), record.getHttpCode(), + record.getError(), record.getRequestId()); + } + } + + // Tokens that failed with a retryable status (5xx other than 529) can be resubmitted + if (!response.getTokensToRetry().isEmpty()) { + System.out.println("tokens to retry:\t" + response.getTokensToRetry()); + } + }).exceptionally(throwable -> { + System.err.println("Async bulk delete tokens rejected with error:\t" + throwable.getMessage()); + throw new CompletionException(throwable); + }); + } catch (SkyflowException e) { + // Step 7: Handle any synchronous errors that occur during setup + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkDeleteTokensSync.java b/flowvault/samples/src/main/java/com/example/vault/BulkDeleteTokensSync.java new file mode 100644 index 00000000..e009fc96 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkDeleteTokensSync.java @@ -0,0 +1,99 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; + +import java.util.ArrayList; +import java.util.List; + +/** + * This sample demonstrates how to perform a synchronous bulk delete tokens operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating a list of tokens to delete + * 3. Building and executing a bulk delete tokens request + * 4. Reading the per-token outcome and the summary from the response + * 5. Handling the delete response or any potential errors + */ +public class BulkDeleteTokensSync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials using credentials string + String credentialsString = ""; + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare list of tokens to delete. The SDK assigns each token an index from its + // position in this list and returns it on the matching response record, so results stay + // correlated even though large requests are split into batches that run concurrently. + List tokens = new ArrayList<>(); + tokens.add(""); + tokens.add(""); + + // Step 5: Build the bulk delete tokens request + BulkDeleteTokensRequest deleteTokensRequest = BulkDeleteTokensRequest.builder() + .tokens(tokens) + .build(); + + // Step 6: Execute the bulk delete tokens operation and print the response + BulkDeleteTokensResponse deleteTokensResponse = + skyflowClient.vault().bulkDeleteTokens(deleteTokensRequest); + System.out.println(deleteTokensResponse); + + // Step 7: Read the summary. totalTokens counts the tokens you submitted, and the other + // two classify each one, so together they sum to that count. + System.out.println("total tokens:\t" + deleteTokensResponse.getSummary().getTotalTokens()); + System.out.println("deleted:\t" + deleteTokensResponse.getSummary().getTotalDeleted()); + System.out.println("failed:\t\t" + deleteTokensResponse.getSummary().getTotalFailed()); + + // Step 8: Walk the per-token outcomes. Successes and failures share one list: a record + // succeeded when its error is null, and its index is the token's position in the request + // you submitted. requestId identifies the API call an error came from and is set on + // failures only. + for (BulkDeleteTokensResponseRecord record : deleteTokensResponse.getRecords()) { + if (record.getError() == null) { + System.out.printf("[%d] %s deleted (%d)%n", + record.getIndex(), record.getToken(), record.getHttpCode()); + } else { + System.out.printf("[%d] %s failed (%d): %s [requestId=%s]%n", + record.getIndex(), record.getToken(), record.getHttpCode(), + record.getError(), record.getRequestId()); + } + } + + // Step 9: Optionally retry the tokens that failed with a retryable status (5xx other + // than 529). A token that simply does not exist fails with a 4xx, so it is not included. + List tokensToRetry = deleteTokensResponse.getTokensToRetry(); + if (!tokensToRetry.isEmpty()) { + System.out.println("retrying:\t" + tokensToRetry); + BulkDeleteTokensResponse retryResponse = skyflowClient.vault().bulkDeleteTokens( + BulkDeleteTokensRequest.builder().tokens(tokensToRetry).build()); + System.out.println("retry response:\t" + retryResponse); + } + } catch (SkyflowException e) { + // Step 10: Handle any errors that occur during the process + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeAsync.java b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeAsync.java new file mode 100644 index 00000000..762c523b --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeAsync.java @@ -0,0 +1,112 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.TokenizeResponseToken; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * This sample demonstrates how to perform an asynchronous bulk tokenize operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating a list of records, each with a value and one or more token group names + * 3. Building and executing an async bulk tokenize request + * 4. Reading the per-token-group outcome for each value + * 5. Handling the tokenize response or errors using CompletableFuture + */ +public class BulkTokenizeAsync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials using credentials string + String credentialsString = ""; + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Specify the token groups to tokenize each value against + List tokenGroupNames = new ArrayList<>(); + tokenGroupNames.add(""); + tokenGroupNames.add(""); + + // Step 5: Prepare the records to tokenize. The SDK assigns each record an index from its + // position in this list and returns it on the matching response record, so results stay + // correlated even though the batches complete out of order. + List records = new ArrayList<>(); + records.add(BulkTokenizeRequestRecord.builder() + .value("") + .tokenGroupNames(tokenGroupNames) + .build()); + records.add(BulkTokenizeRequestRecord.builder() + .value("") + // Optional: supply your own token instead of having one generated (BYOT). + // A BYOT record must name exactly one token group. + // .token("") + .tokenGroupNames(tokenGroupNames) + .build()); + + // Step 6: Build the bulk tokenize request + BulkTokenizeRequest tokenizeRequest = BulkTokenizeRequest.builder() + .records(records) + .build(); + + // Step 7: Execute the async bulk tokenize operation and handle response using callbacks + CompletableFuture future = + skyflowClient.vault().bulkTokenizeAsync(tokenizeRequest); + future.thenAccept(response -> { + System.out.println("Async bulk tokenize resolved with response:\t" + response); + + // Each value reports one entry per token group, so a value can partially succeed. + // requestId identifies the API call an error came from and is set on failures only. + for (BulkTokenizeResponseRecord record : response.getRecords()) { + for (TokenizeResponseToken token : record.getTokens()) { + if (token.getError() == null) { + System.out.printf("[%d] group '%s' -> %s%n", + record.getIndex(), token.getTokenGroupName(), token.getToken()); + } else { + System.out.printf("[%d] group '%s' failed (%d): %s [requestId=%s]%n", + record.getIndex(), token.getTokenGroupName(), + token.getHttpCode(), token.getError(), token.getRequestId()); + } + } + } + + // Records that failed with a retryable status (5xx other than 529) come back + // unchanged and can be resubmitted as-is. + if (!response.getRecordsToRetry().isEmpty()) { + System.out.println("records to retry:\t" + response.getRecordsToRetry().size()); + } + }).exceptionally(throwable -> { + System.err.println("Async bulk tokenize rejected with error:\t" + throwable.getMessage()); + throw new CompletionException(throwable); + }); + } catch (SkyflowException e) { + // Step 8: Handle any synchronous errors that occur during setup + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeSync.java b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeSync.java new file mode 100644 index 00000000..9690f423 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkTokenizeSync.java @@ -0,0 +1,115 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.TokenizeResponseToken; + +import java.util.ArrayList; +import java.util.List; + +/** + * This sample demonstrates how to perform a synchronous bulk tokenize operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating a list of records, each with a value and one or more token group names + * 3. Building and executing a bulk tokenize request + * 4. Reading the per-token-group outcome for each value + * 5. Handling the tokenize response or any potential errors + */ +public class BulkTokenizeSync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials using credentials string + String credentialsString = ""; + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Specify the token groups to tokenize each value against + List tokenGroupNames = new ArrayList<>(); + tokenGroupNames.add(""); + tokenGroupNames.add(""); + + // Step 5: Prepare the records to tokenize. The SDK assigns each record an index from its + // position in this list and returns it on the matching response record, so results stay + // correlated even though large requests are split into batches that run concurrently. + List records = new ArrayList<>(); + records.add(BulkTokenizeRequestRecord.builder() + .value("") + .tokenGroupNames(tokenGroupNames) + .build()); + records.add(BulkTokenizeRequestRecord.builder() + .value("") + // Optional: supply your own token instead of having one generated (BYOT). + // A BYOT record must name exactly one token group. + // .token("") + .tokenGroupNames(tokenGroupNames) + .build()); + + // Step 6: Build the bulk tokenize request + BulkTokenizeRequest tokenizeRequest = BulkTokenizeRequest.builder() + .records(records) + .build(); + + // Step 7: Execute the bulk tokenize operation and print the response + BulkTokenizeResponse tokenizeResponse = skyflowClient.vault().bulkTokenize(tokenizeRequest); + System.out.println(tokenizeResponse); + + // Step 8: Read the summary. totalTokens counts the values you submitted; the other three + // classify each value by how its token groups fared, so together they sum to that count. + System.out.println("total values:\t" + tokenizeResponse.getSummary().getTotalTokens()); + System.out.println("tokenized:\t" + tokenizeResponse.getSummary().getTotalTokenized()); + System.out.println("partial:\t" + tokenizeResponse.getSummary().getTotalPartial()); + System.out.println("failed:\t\t" + tokenizeResponse.getSummary().getTotalFailed()); + + // Step 9: Walk the results. Each value reports one entry per token group, so a value can + // partially succeed: some groups return a token while others return an error. requestId + // identifies the API call an error came from and is set on failures only. + for (BulkTokenizeResponseRecord record : tokenizeResponse.getRecords()) { + for (TokenizeResponseToken token : record.getTokens()) { + if (token.getError() == null) { + System.out.printf("[%d] group '%s' -> %s%n", + record.getIndex(), token.getTokenGroupName(), token.getToken()); + } else { + System.out.printf("[%d] group '%s' failed (%d): %s [requestId=%s]%n", + record.getIndex(), token.getTokenGroupName(), + token.getHttpCode(), token.getError(), token.getRequestId()); + } + } + } + + // Step 10: Optionally retry the records that failed with a retryable status (5xx other + // than 529). Your original records come back unchanged and can be resubmitted as-is. + List recordsToRetry = tokenizeResponse.getRecordsToRetry(); + if (!recordsToRetry.isEmpty()) { + BulkTokenizeResponse retryResponse = skyflowClient.vault().bulkTokenize( + BulkTokenizeRequest.builder().records(recordsToRetry).build()); + System.out.println("retry response:\t" + retryResponse); + } + } catch (SkyflowException e) { + // Step 11: Handle any errors that occur during the process + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java b/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java index 05941f8b..4dc4d67c 100644 --- a/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java +++ b/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java @@ -9,7 +9,7 @@ import com.skyflow.vault.data.BulkInsertRequest; import com.skyflow.vault.data.BulkInsertRequestRecord; import com.skyflow.vault.data.BulkInsertResponse; -import com.skyflow.vault.data.InsertOptions; +import com.skyflow.vault.data.BulkInsertOptions; import com.skyflow.vault.data.InsertRequestRecord; import com.skyflow.vault.data.UpsertOptions; @@ -24,8 +24,8 @@ * This sample demonstrates how to attach custom headers to outgoing requests via a request * interceptor on the options object. * - *

Available keys on {@link CustomHeaderKey}: {@code SkyflowAccountId} ({@code x-skyflow-account-id}), - * {@code SkyflowAccountName} ({@code x-skyflow-account-name}) and {@code RequestIdHeader} + *

Available keys on {@link CustomHeaderKey}: {@code SKYFLOW_ACCOUNT_ID} ({@code x-skyflow-account-id}), + * {@code SKYFLOW_ACCOUNT_NAME} ({@code x-skyflow-account-name}) and {@code REQUEST_ID_HEADER} * ({@code x-request-id}). * *

The interceptor runs once per batch, so a per-request value such as a request id is generated @@ -83,9 +83,9 @@ public static void main(String[] args) { .build(); // Step 7: Attach a custom header through the interceptor - InsertOptions options = InsertOptions.builder() + BulkInsertOptions options = BulkInsertOptions.builder() .interceptor(ctx -> { - ctx.addHeader(CustomHeaderKey.RequestIdHeader, getRequestId()); // pass the request id here + ctx.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, getRequestId()); // pass the request id here }) .build(); diff --git a/flowvault/src/main/java/com/skyflow/Skyflow.java b/flowvault/src/main/java/com/skyflow/Skyflow.java index 64551cb9..5c2e682c 100644 --- a/flowvault/src/main/java/com/skyflow/Skyflow.java +++ b/flowvault/src/main/java/com/skyflow/Skyflow.java @@ -40,10 +40,30 @@ public VaultConfig getVaultConfig() { return (VaultConfig) array[0]; } + /** + * Updates a vault's configuration on an already-built client. + *

+ * BaseSkyflow.updateVaultConfig goes straight to the template, bypassing the builder's own + * override, so the flowvault-specific fields have to be carried across here too — otherwise + * a vaultUrl or HTTP setting supplied through this entry point would be silently dropped + * while the same call on the builder honoured it. + */ + @Override + public Skyflow updateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + super.updateVaultConfig(vaultConfig); + this.builder.carryVaultOverrides(vaultConfig); + return this; + } + public VaultController vault() throws SkyflowException { return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); } + public VaultController vault(String vaultId) throws SkyflowException { + return resolveOrThrow(this.builder.vaultClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder { private final LinkedHashMap vaultClientsMap = new LinkedHashMap<>(); // Client-wide HTTP config. Resolution per vault, most specific first: diff --git a/common/src/main/java/com/skyflow/enums/CustomHeaderKey.java b/flowvault/src/main/java/com/skyflow/enums/CustomHeaderKey.java similarity index 63% rename from common/src/main/java/com/skyflow/enums/CustomHeaderKey.java rename to flowvault/src/main/java/com/skyflow/enums/CustomHeaderKey.java index 8d3285ae..9a3077f2 100644 --- a/common/src/main/java/com/skyflow/enums/CustomHeaderKey.java +++ b/flowvault/src/main/java/com/skyflow/enums/CustomHeaderKey.java @@ -1,9 +1,9 @@ package com.skyflow.enums; public enum CustomHeaderKey { - SkyflowAccountId("x-skyflow-account-id"), - SkyflowAccountName("x-skyflow-account-name"), - RequestIdHeader("x-request-id"); + SKYFLOW_ACCOUNT_ID("x-skyflow-account-id"), + SKYFLOW_ACCOUNT_NAME("x-skyflow-account-name"), + REQUEST_ID_HEADER("x-request-id"); private final String value; diff --git a/flowvault/src/main/java/com/skyflow/utils/Utils.java b/flowvault/src/main/java/com/skyflow/utils/Utils.java index e4261a00..d6011dec 100644 --- a/flowvault/src/main/java/com/skyflow/utils/Utils.java +++ b/flowvault/src/main/java/com/skyflow/utils/Utils.java @@ -541,6 +541,32 @@ public static List handleBulkDetokenizeBatchExcept return allRecords; } + /** + * Best available description of a failure that never reached the API. + * + *

The generated client wraps transport failures as "Network error executing HTTP request", + * which says nothing about what actually went wrong, and the future wraps that again. Walk down + * to the innermost cause so the caller sees the real problem - for a mistyped cluster id that is + * {@code java.net.UnknownHostException: : nodename nor servname provided, or not known} + * rather than the generic wrapper. Mirrors the order bulk insert and detokenize already use. + */ + private static String describeTransportFailure(Throwable ex, Throwable cause) { + String message = null; + if (cause != null && cause.getMessage() != null) { + message = cause.getMessage(); + } + if (cause != null && cause.getLocalizedMessage() != null) { + message = cause.getLocalizedMessage(); + } + if (cause != null && cause.getCause() != null) { + message = cause.getCause().toString(); + } + if (message == null || message.trim().isEmpty()) { + message = ex.getMessage(); + } + return message; + } + public static List handleBulkDeleteTokensBatchException( Throwable ex, com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest batch, @@ -595,9 +621,10 @@ public static List handleBulkDeleteTokensBatchEx } } else { // a transport-level failure never reached the API, so there is no id to report + String message = describeTransportFailure(ex, cause); for (int position = 0; position < batchTokens.size(); position++) { errorRecords.add(new BulkDeleteTokensResponseRecord( - startIndex + position, tokenAt(batchTokens, position), 500, ex.getMessage())); + startIndex + position, tokenAt(batchTokens, position), 500, message)); } } return errorRecords; @@ -609,18 +636,15 @@ private static String tokenAt(List tokens, int position) { private static BulkDeleteTokensResponseRecord createDeleteTokensErrorRecord( Map recordMap, int index, String requestedToken, String requestId) { - int code = 500; - if (recordMap.containsKey("http_code")) { - code = (Integer) recordMap.get("http_code"); - } else if (recordMap.containsKey("httpCode")) { - code = (Integer) recordMap.get("httpCode"); - } else if (recordMap.containsKey("statusCode")) { - code = (Integer) recordMap.get("statusCode"); - } - String message = recordMap.containsKey("error") ? (String) recordMap.get("error") : - recordMap.containsKey("message") ? (String) recordMap.get("message") : "Unknown error"; - Object echoedToken = recordMap.get("value"); - String token = (echoedToken instanceof String) ? (String) echoedToken : requestedToken; + // Read through the shared helpers rather than casting: recordMap holds deserialised JSON, + // so a status can arrive as Double or String depending on the parser, and a blind + // (Integer) cast would turn a real API error into a ClassCastException. + int code = readHttpCode(recordMap, 500); + String message = readErrorMessage(recordMap); + String token = readString(recordMap, "value"); + if (token == null) { + token = requestedToken; + } return new BulkDeleteTokensResponseRecord(index, token, code, message, requestId); } @@ -645,7 +669,7 @@ public static List handleBulkTokenizeBatchException( } else { // a transport-level failure never reached the API, so there is no id to report httpCode = 500; - message = ex.getMessage(); + message = describeTransportFailure(ex, cause); } // a batch-level failure fails every token group of every value in that batch List errorRecords = new ArrayList<>(); @@ -719,10 +743,6 @@ private static String extractBatchErrorMessage(ApiClientApiException apiExceptio return apiException.getMessage(); } - private static BulkTokenizeRequestRecord recordAt(List records, int position) { - return (records != null && position < records.size()) ? records.get(position) : null; - } - public static BulkInsertResponse formatBulkInsertResponse(V1InsertResponse response, int batch, int batchSize, Map> headers) { BulkInsertResponse formattedResponse = null; List records = new ArrayList<>(); diff --git a/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java b/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java index 39565674..667028ce 100644 --- a/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java +++ b/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java @@ -205,6 +205,37 @@ public static void validateInsertRequest(InsertRequest insertRequest) throws Sky } } } + validateInsertRecordTokens(record.getTokens()); + } + } + + // Tokens are optional on an insert record, but when supplied the map must not be empty and + // every entry must have a non-blank key and value — mirroring the checks on data above. + private static void validateInsertRecordTokens(Map tokens) throws SkyflowException { + if (tokens == null) { + return; + } + if (tokens.isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_TOKENS.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokens.getMessage()); + } + for (String key : tokens.keySet()) { + if (key == null || key.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_KEY_IN_TOKENS.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyKeyInTokens.getMessage()); + } + Object value = tokens.get(key); + if (value == null || value.toString().trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_VALUE_IN_TOKENS.getLog(), + InterfaceName.INSERT.getName(), key + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyValueInTokens.getMessage()); + } } } diff --git a/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java b/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java index d405e1b4..5ece5377 100644 --- a/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java +++ b/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java @@ -460,7 +460,7 @@ private List> deleteTokensBatchFutur for (int batchIndex = 0; batchIndex < batches.size(); batchIndex++) { final int index = batchIndex; com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest batch = batches.get(index); - RequestContext ctx = new RequestContext("DELETE_TOKENS"); + RequestContext ctx = new RequestContext("DELETE_TOKENS", batchIndex, batches.size()); if (interceptor != null) interceptor.intercept(ctx); CompletableFuture future = CompletableFuture .supplyAsync(() -> processDeleteTokensBatch(batch, ctx), executor) @@ -612,12 +612,14 @@ private List> tokenizeBatchFutures( // batches are contiguous but not uniformly sized - a batch is cut short when it would // otherwise repeat a value - so track where each one starts rather than deriving it int nextStartIndex = 0; + int batchPosition = 0; for (List batchRecords : batches) { final int startIndex = nextStartIndex; nextStartIndex += batchRecords.size(); + final int batchIndex = batchPosition++; com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest batch = Utils.getBulkTokenizeRequestBody(batchRecords, this.getVaultConfig().getVaultId()); - RequestContext ctx = new RequestContext("TOKENIZE"); + RequestContext ctx = new RequestContext("TOKENIZE", batchIndex, batches.size()); if (interceptor != null) interceptor.intercept(ctx); CompletableFuture future = CompletableFuture .supplyAsync(() -> processTokenizeBatch(batch, ctx), executor) @@ -830,7 +832,7 @@ private List> detokenizeBatchFutures( for (int batchIndex = 0; batchIndex < batches.size(); batchIndex++) { com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest batch = batches.get(batchIndex); int batchNumber = batchIndex; - RequestContext ctx = new RequestContext("DETOKENIZE"); + RequestContext ctx = new RequestContext("DETOKENIZE", batchIndex, batches.size()); if (interceptor != null) interceptor.intercept(ctx); CompletableFuture future = CompletableFuture .supplyAsync(() -> processDetokenizeBatch(batch, ctx), executor) @@ -862,7 +864,7 @@ private List> insertBatchFutures( for (int batchIndex = 0; batchIndex < batches.size(); batchIndex++) { List batch = batches.get(batchIndex); int batchNumber = batchIndex; - RequestContext ctx = new RequestContext("INSERT"); + RequestContext ctx = new RequestContext("INSERT", batchIndex, batches.size()); if (interceptor != null) interceptor.intercept(ctx); CompletableFuture future = CompletableFuture .supplyAsync(() -> insertBatch( diff --git a/flowvault/src/main/java/com/skyflow/vault/data/RequestContext.java b/flowvault/src/main/java/com/skyflow/vault/data/RequestContext.java new file mode 100644 index 00000000..a5752f3a --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/RequestContext.java @@ -0,0 +1,47 @@ +package com.skyflow.vault.data; + +import com.skyflow.enums.CustomHeaderKey; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class RequestContext { + /** Reported when the caller's request was not split into batches. */ + private static final int NOT_BATCHED = -1; + + private final String operation; + private final int batchIndex; + private final int totalBatches; + private final Map headers = new HashMap<>(); + + public RequestContext(String operation) { + this(operation, NOT_BATCHED, NOT_BATCHED); + } + + public RequestContext(String operation, int batchIndex, int totalBatches) { + this.operation = operation; + this.batchIndex = batchIndex; + this.totalBatches = totalBatches; + } + + public String getOperation() { return operation; } + + /** + * Zero-based position of this batch within the caller's request, or -1 when the operation was + * not batched. Lets an interceptor tag each batch distinctly — a per-batch correlation id, for + * instance — instead of seeing an identical context for every one. + */ + public int getBatchIndex() { return batchIndex; } + + /** Total number of batches the request was split into, or -1 when it was not batched. */ + public int getTotalBatches() { return totalBatches; } + + public void addHeader(CustomHeaderKey key, String value) { + headers.put(key, value); + } + + public Map getHeaders() { + return Collections.unmodifiableMap(headers); + } +} diff --git a/common/src/main/java/com/skyflow/vault/data/RequestInterceptor.java b/flowvault/src/main/java/com/skyflow/vault/data/RequestInterceptor.java similarity index 100% rename from common/src/main/java/com/skyflow/vault/data/RequestInterceptor.java rename to flowvault/src/main/java/com/skyflow/vault/data/RequestInterceptor.java diff --git a/flowvault/src/test/java/com/skyflow/ClientLifecycleScenarioTests.java b/flowvault/src/test/java/com/skyflow/ClientLifecycleScenarioTests.java new file mode 100644 index 00000000..0a39efb5 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/ClientLifecycleScenarioTests.java @@ -0,0 +1,212 @@ +package com.skyflow; + +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.controller.VaultController; +import org.junit.Assert; +import org.junit.Test; + +/** + * The four client lifecycle scenarios, mirrored by the ClientOperationsExample sample. + * + *

Each scenario runs twice: once through {@code SkyflowClientBuilder} and once through the built + * {@code Skyflow} client. The two go down different code paths — BaseSkyflow.updateVaultConfig calls + * the template directly and skips the builder's own override — and a bug that dropped the + * flowvault-specific fields on the client path only was found exactly this way. + * + *

Unlike the sample, these run inside the com.skyflow package, so they can assert against the + * controller's own config rather than only the stored copy. + */ +public class ClientLifecycleScenarioTests { + + private static final String VAULT_ID = "vault1"; + private static final String NOT_IN_CONFIG_LIST = "VaultId is missing from the config"; + + private static VaultConfig config(String clusterId) { + VaultConfig config = new VaultConfig(); + config.setVaultId(VAULT_ID); + config.setClusterId(clusterId); + config.setEnv(Env.DEV); + return config; + } + + /** An update carrying a new cluster, env and timeout. clusterId is resent because the incoming + * config is validated on its own before being merged. */ + private static VaultConfig update(String clusterId, Env env, Integer timeout) { + VaultConfig update = config(clusterId); + update.setEnv(env); + update.setTimeout(timeout); + return update; + } + + // ── A: add -> update -> delete -> vault() must fail ─────────────────────── + + @Test + public void testScenarioA_viaBuilder_addUpdateDeleteThenVaultFails() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config("cluster1")); + + // add + Assert.assertEquals("cluster1", builder.build().getVaultConfig(VAULT_ID).getClusterId()); + // update - no error + builder.updateVaultConfig(update("cluster2", Env.PROD, 30)); + Assert.assertEquals("cluster2", builder.build().getVaultConfig(VAULT_ID).getClusterId()); + Assert.assertEquals(Env.PROD, builder.build().getVaultConfig(VAULT_ID).getEnv()); + Assert.assertEquals(Integer.valueOf(30), builder.build().getVaultConfig(VAULT_ID).getTimeout()); + // delete + builder.removeVaultConfig(VAULT_ID); + Assert.assertNull(builder.build().getVaultConfig(VAULT_ID)); + + // vault() -> vault id not found + Skyflow client = builder.build(); + try { + client.vault(); + Assert.fail("vault() must fail once the vault is removed"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + @Test + public void testScenarioA_viaClient_addUpdateDeleteThenVaultFails() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(config("cluster1")).build(); + + client.updateVaultConfig(update("cluster2", Env.PROD, 30)); + Assert.assertEquals("cluster2", client.getVaultConfig(VAULT_ID).getClusterId()); + Assert.assertEquals(Integer.valueOf(30), client.getVaultConfig(VAULT_ID).getTimeout()); + + client.removeVaultConfig(VAULT_ID); + Assert.assertNull(client.getVaultConfig(VAULT_ID)); + + try { + client.vault(); + Assert.fail("vault() must fail once the vault is removed"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + // ── B: add -> delete -> update must throw ───────────────────────────────── + + @Test + public void testScenarioB_viaBuilder_addDeleteThenUpdateThrows() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addVaultConfig(config("cluster1")) + .removeVaultConfig(VAULT_ID); + + try { + builder.updateVaultConfig(update("cluster2", Env.PROD, 30)); + Assert.fail("updating a removed vault must throw, not silently re-create it"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + + // the failed update must not have resurrected the vault + Assert.assertNull(builder.build().getVaultConfig(VAULT_ID)); + } + + @Test + public void testScenarioB_viaClient_addDeleteThenUpdateThrows() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(config("cluster1")).build(); + client.removeVaultConfig(VAULT_ID); + + try { + client.updateVaultConfig(update("cluster2", Env.PROD, 30)); + Assert.fail("updating a removed vault must throw, not silently re-create it"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + + Assert.assertNull(client.getVaultConfig(VAULT_ID)); + try { + client.vault(); + Assert.fail("vault() must still fail after the rejected update"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + // ── C: add -> update -> vault() carries the latest config ───────────────── + + @Test + public void testScenarioC_viaBuilder_vaultAfterUpdateHasLatest() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config("cluster1")); + + builder.updateVaultConfig(update("cluster2", Env.PROD, 15)); + VaultController vault = builder.build().vault(); + + Assert.assertEquals("cluster2", vault.getVaultConfig().getClusterId()); + Assert.assertEquals(Env.PROD, vault.getVaultConfig().getEnv()); + Assert.assertEquals(Integer.valueOf(15), vault.getVaultConfig().getTimeout()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.com", vault.currentVaultURL); + vault.updateExecutorInHTTP(); + Assert.assertEquals(15_000, vault.sharedHttpClient.callTimeoutMillis()); + } + + @Test + public void testScenarioC_viaClient_vaultAfterUpdateHasLatest() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(config("cluster1")).build(); + + client.updateVaultConfig(update("cluster2", Env.PROD, 15)); + VaultController vault = client.vault(); + + Assert.assertEquals("cluster2", vault.getVaultConfig().getClusterId()); + Assert.assertEquals(Env.PROD, vault.getVaultConfig().getEnv()); + Assert.assertEquals(Integer.valueOf(15), vault.getVaultConfig().getTimeout()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.com", vault.currentVaultURL); + vault.updateExecutorInHTTP(); + Assert.assertEquals(15_000, vault.sharedHttpClient.callTimeoutMillis()); + } + + // ── D: add -> vault() -> update -> vault() carries the latest config ────── + + @Test + public void testScenarioD_viaBuilder_heldControllerSeesTheUpdate() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config("cluster1")); + VaultController held = builder.build().vault(); + Assert.assertEquals("cluster1", held.getVaultConfig().getClusterId()); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.dev", held.currentVaultURL); + + builder.updateVaultConfig(update("cluster2", Env.PROD, 45)); + VaultController after = builder.build().vault(); + + Assert.assertSame("the reference taken before the update must still be current", held, after); + Assert.assertEquals("cluster2", held.getVaultConfig().getClusterId()); + Assert.assertEquals(Integer.valueOf(45), held.getVaultConfig().getTimeout()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.com", held.currentVaultURL); + } + + @Test + public void testScenarioD_viaClient_heldControllerSeesTheUpdate() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(config("cluster1")).build(); + VaultController held = client.vault(); + Assert.assertEquals("cluster1", held.getVaultConfig().getClusterId()); + + client.updateVaultConfig(update("cluster2", Env.PROD, 45)); + + Assert.assertSame("the reference taken before the update must still be current", + held, client.vault()); + Assert.assertEquals("cluster2", held.getVaultConfig().getClusterId()); + Assert.assertEquals(Integer.valueOf(45), held.getVaultConfig().getTimeout()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.com", held.currentVaultURL); + held.updateExecutorInHTTP(); + Assert.assertEquals(45_000, held.sharedHttpClient.callTimeoutMillis()); + } + + // ── the vaultUrl variant of C/D, since it resolves differently to clusterId ── + + @Test + public void testScenarioD_viaClient_heldControllerSeesANewVaultUrl() throws SkyflowException { + VaultConfig initial = config("cluster1"); + initial.setVaultUrl("https://first.example.com"); + Skyflow client = Skyflow.builder().addVaultConfig(initial).build(); + VaultController held = client.vault(); + Assert.assertEquals("https://first.example.com", held.currentVaultURL); + + VaultConfig update = config("cluster1"); + update.setVaultUrl("https://second.example.com"); + client.updateVaultConfig(update); + + Assert.assertEquals("https://second.example.com", held.currentVaultURL); + } +} diff --git a/flowvault/src/test/java/com/skyflow/SkyflowTests.java b/flowvault/src/test/java/com/skyflow/SkyflowTests.java index 062989cc..7986e68a 100644 --- a/flowvault/src/test/java/com/skyflow/SkyflowTests.java +++ b/flowvault/src/test/java/com/skyflow/SkyflowTests.java @@ -161,6 +161,79 @@ public void testRemoveVaultConfig_nonExistentVaultIdThrows() { } } + // ── updateVaultConfig validates the incoming config, not the merged result ── + + @Test + public void testUpdateVaultConfig_partialUpdateWithoutClusterIdOrVaultUrlIsRejected() throws SkyflowException { + // mergeVaultConfig only copies non-null fields across, which implies "send just what you + // want to change". But updateVaultConfigTemplate validates the INCOMING config first, and + // validateVaultConfiguration requires clusterId or vaultUrl - so a partial update is + // rejected even though the merge would have preserved the existing values. + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + VaultConfig partial = new VaultConfig(); + partial.setVaultId("vault1"); + partial.setTimeout(30); + + try { + builder.updateVaultConfig(partial); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains("clusterId")); + } + } + + @Test + public void testUpdateVaultConfig_rejectedPartialUpdateChangesNothing() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + VaultConfig partial = new VaultConfig(); + partial.setVaultId("vault1"); + partial.setTimeout(30); + try { + builder.updateVaultConfig(partial); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException expected) { + // asserted above + } + + Skyflow client = builder.build(); + Assert.assertEquals("cluster1", client.getVaultConfig("vault1").getClusterId()); + Assert.assertNull("the rejected timeout must not have been applied", + client.getVaultConfig("vault1").getTimeout()); + } + + @Test + public void testUpdateVaultConfig_partialUpdateIsAcceptedWhenClusterIdIsRepeated() throws SkyflowException { + // The workaround: resend clusterId even when it is not changing. + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + VaultConfig update = new VaultConfig(); + update.setVaultId("vault1"); + update.setClusterId("cluster1"); + update.setTimeout(30); + + Skyflow client = builder.updateVaultConfig(update).build(); + + Assert.assertEquals(Integer.valueOf(30), client.getVaultConfig("vault1").getTimeout()); + Assert.assertEquals("cluster1", client.getVaultConfig("vault1").getClusterId()); + } + + @Test + public void testUpdateVaultConfig_vaultUrlAloneSatisfiesTheRequirement() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + VaultConfig update = new VaultConfig(); + update.setVaultId("vault1"); + update.setVaultUrl("https://custom.example.com"); + update.setTimeout(30); + + Skyflow client = builder.updateVaultConfig(update).build(); + + Assert.assertEquals("https://custom.example.com", client.vault().currentVaultURL); + Assert.assertEquals(Integer.valueOf(30), client.getVaultConfig("vault1").getTimeout()); + } + // ── Client management lifecycles ───────────────────────────────────────── // Whole add/update/remove sequences, asserting both the stored config and the controller // behind vault() stay in step at every stage. @@ -369,6 +442,73 @@ public void testVault_throwsWhenNoConfigExists() { } } + // ── vault(vaultId) ──────────────────────────────────────────────────────── + + @Test + public void testVaultById_returnsTheControllerForTheConfiguredId() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + Assert.assertSame(client.vault(), client.vault("vault1")); + } + + @Test + public void testVaultById_selectsTheMatchingVaultAmongSeveral() throws SkyflowException { + VaultConfig first = buildConfig("vault1", "cluster1"); + first.setVaultUrl("https://first.example.com"); + VaultConfig second = buildConfig("vault2", "cluster2"); + second.setVaultUrl("https://second.example.com"); + Skyflow client = Skyflow.builder().addVaultConfig(first).addVaultConfig(second).build(); + + Assert.assertEquals("https://first.example.com", client.vault("vault1").currentVaultURL); + Assert.assertEquals("https://second.example.com", client.vault("vault2").currentVaultURL); + } + + @Test + public void testVaultById_nullIdResolvesToTheFirstConfiguredVault() throws SkyflowException { + Skyflow client = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .addVaultConfig(buildConfig("vault2", "cluster2")) + .build(); + Assert.assertSame(client.vault(), client.vault(null)); + } + + @Test + public void testVaultById_throwsForUnknownVaultId() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + try { + client.vault("vault-unknown"); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testVaultById_throwsWhenNoConfigExists() { + try { + Skyflow.builder().build().vault("vault1"); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testVaultById_removedVaultThrowsWhileOthersStillResolve() throws SkyflowException { + Skyflow client = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .addVaultConfig(buildConfig("vault2", "cluster2")) + .build(); + client.removeVaultConfig("vault1"); + + Assert.assertNotNull(client.vault("vault2")); + try { + client.vault("vault1"); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + // ── getVaultConfig ──────────────────────────────────────────────────────── @Test diff --git a/flowvault/src/test/java/com/skyflow/UpdatePropagationTests.java b/flowvault/src/test/java/com/skyflow/UpdatePropagationTests.java index db0d28db..68c58fe4 100644 --- a/flowvault/src/test/java/com/skyflow/UpdatePropagationTests.java +++ b/flowvault/src/test/java/com/skyflow/UpdatePropagationTests.java @@ -176,6 +176,63 @@ public void testUpdateVaultConfig_vaultLevelCredentialsStillBeatClientWide() thr Assert.assertEquals("vault-token", after.token); } + // ── the SAME must hold via the built client, not just the builder ───────── + // BaseSkyflow.updateVaultConfig bypasses the builder's override, so both entry points need + // covering. A sample calling client.updateVaultConfig(...) is what exposed this gap. + + @Test + public void testUpdateVaultConfigOnClient_carriesHttpSettings() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setTimeout(30); + update.setMaxRetries(4); + client.updateVaultConfig(update); + + Assert.assertEquals(Integer.valueOf(30), client.getVaultConfig("vault1").getTimeout()); + Assert.assertEquals(Integer.valueOf(4), client.getVaultConfig("vault1").getMaxRetries()); + } + + @Test + public void testUpdateVaultConfigOnClient_httpSettingsReachTheHttpClient() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setTimeout(30); + client.updateVaultConfig(update); + + VaultController vault = client.vault(); + vault.updateExecutorInHTTP(); + Assert.assertEquals(30_000, vault.sharedHttpClient.callTimeoutMillis()); + } + + @Test + public void testUpdateVaultConfigOnClient_carriesVaultUrl() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setVaultUrl("https://first.example.com"); + Skyflow client = Skyflow.builder().addVaultConfig(config).build(); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setVaultUrl("https://second.example.com"); + client.updateVaultConfig(update); + + Assert.assertEquals("https://second.example.com", client.getVaultConfig("vault1").getVaultUrl()); + Assert.assertEquals("https://second.example.com", client.vault().currentVaultURL); + } + + @Test + public void testUpdateVaultConfigOnClient_retryDelaysAreCarried() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setInitialRetryDelayMillis(250L); + update.setMaxRetryDelayMillis(4000L); + client.updateVaultConfig(update); + + Assert.assertEquals(Long.valueOf(250L), client.getVaultConfig("vault1").getInitialRetryDelayMillis()); + Assert.assertEquals(Long.valueOf(4000L), client.getVaultConfig("vault1").getMaxRetryDelayMillis()); + } + // ── Credentials updates reach every controller ─────────────────────────── @Test diff --git a/flowvault/src/test/java/com/skyflow/utils/RequestFidelityTests.java b/flowvault/src/test/java/com/skyflow/utils/RequestFidelityTests.java index 13e2506e..42b3dac9 100644 --- a/flowvault/src/test/java/com/skyflow/utils/RequestFidelityTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/RequestFidelityTests.java @@ -528,9 +528,9 @@ private static com.skyflow.generated.rest.types.V1Upsert upsertWire(String updat // ── insert: tokens map ─────────────────────────────────────────────────── @Test - public void testBulkInsert_emptyTokensMapIsOmittedFromWire_knownGap() { - // KNOWN GAP: an explicitly-set-but-empty tokens map is dropped rather than sent as {}. - // Pinning current behavior — a caller cannot distinguish "no tokens" from "empty tokens". + public void testBulkInsert_emptyTokensMapIsOmittedFromWire() { + // Validations.validateInsertRequest rejects an explicitly-set-but-empty tokens map before + // the body builder runs; this pins the builder's own behavior when called directly. Map data = new HashMap<>(); data.put("name", "john"); BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() diff --git a/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java b/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java index e5e56857..753487d8 100644 --- a/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java @@ -215,6 +215,45 @@ public void testTokenize_rejectedRequestWithNoBodyStillStampsEveryGroup() { Assert.assertEquals(REQ_ID_A, tokens.get(1).getRequestId()); } + @Test + public void testTokenize_transportFailureReportsTheInnermostCause() { + // a mistyped cluster id surfaces as UnknownHostException three levels down: the future + // wraps ApiClientException("Network error..."), which wraps the real cause. Reporting the + // wrapper tells the caller nothing, so the innermost cause must win. + java.net.UnknownHostException dns = new java.net.UnknownHostException( + "badcluster.skyvault.skyflowapis.dev: nodename nor servname provided, or not known"); + Throwable ex = new RuntimeException( + new com.skyflow.generated.rest.core.ApiClientException( + "Network error executing HTTP request", dns)); + + List records = Utils.handleBulkTokenizeBatchException( + ex, Collections.singletonList(tokenizeRecord("v0", "g1")), 0); + + String error = records.get(0).getTokens().get(0).getError(); + Assert.assertTrue("expected the DNS failure, got: " + error, + error.contains("UnknownHostException")); + Assert.assertTrue(error.contains("badcluster.skyvault.skyflowapis.dev")); + } + + @Test + public void testDelete_transportFailureReportsTheInnermostCause() { + java.net.UnknownHostException dns = new java.net.UnknownHostException( + "badcluster.skyvault.skyflowapis.dev: nodename nor servname provided, or not known"); + Throwable ex = new RuntimeException( + new com.skyflow.generated.rest.core.ApiClientException( + "Network error executing HTTP request", dns)); + + List records = + Utils.handleBulkDeleteTokensBatchException(ex, deleteBatch("t0", "t1"), 0, 50); + + Assert.assertEquals(2, records.size()); + for (BulkDeleteTokensResponseRecord record : records) { + Assert.assertTrue("expected the DNS failure, got: " + record.getError(), + record.getError().contains("UnknownHostException")); + Assert.assertEquals(Integer.valueOf(500), record.getHttpCode()); + } + } + @Test public void testTokenize_transportFailureHasNoRequestId() { // never reached the API, so there is no call to point at diff --git a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java index d220084a..4e034bfd 100644 --- a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java @@ -1985,4 +1985,61 @@ public void testFormatBulkTokenizeResponse_emptyResponseReturnsNull() { // Tests for getQueryRequestBody / buildQueryResponse / getGetRequestBody / buildGetResponse // were removed: get and query Utils helpers no longer exist (bulk-only module). + // ── deleteTokens error records must survive any JSON number type ────────── + // recordMap holds deserialised JSON: Gson gives Double for numbers bound to Object, Jackson + // gives Integer or Long by magnitude. A blind (Integer) cast turned a real API error into a + // ClassCastException, so each representation is covered here. + + private static BulkDeleteTokensResponseRecord deleteError(Object httpCode) { + Map recordMap = new HashMap<>(); + if (httpCode != null) { + recordMap.put("http_code", httpCode); + } + recordMap.put("error", "Token not found"); + recordMap.put("value", "tok-1"); + Map body = new HashMap<>(); + body.put("tokens", Collections.singletonList(recordMap)); + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("tok-1")) + .build(); + List records = Utils.handleBulkDeleteTokensBatchException( + new RuntimeException(new ApiClientApiException("delete failed", 500, body)), + batch, 0, 50); + return records.get(0); + } + + @Test + public void testDeleteTokensErrorRecord_acceptsIntegerHttpCode() { + Assert.assertEquals(Integer.valueOf(404), deleteError(404).getHttpCode()); + } + + @Test + public void testDeleteTokensErrorRecord_acceptsDoubleHttpCode() { + // Gson maps a JSON number to Double when the target type is Object. + Assert.assertEquals(Integer.valueOf(404), deleteError(404.0d).getHttpCode()); + } + + @Test + public void testDeleteTokensErrorRecord_acceptsLongHttpCode() { + Assert.assertEquals(Integer.valueOf(404), deleteError(404L).getHttpCode()); + } + + @Test + public void testDeleteTokensErrorRecord_acceptsStringHttpCode() { + Assert.assertEquals(Integer.valueOf(404), deleteError("404").getHttpCode()); + } + + @Test + public void testDeleteTokensErrorRecord_fallsBackTo500WhenTheCodeIsUnusable() { + Assert.assertEquals(Integer.valueOf(500), deleteError("not-a-number").getHttpCode()); + Assert.assertEquals(Integer.valueOf(500), deleteError(null).getHttpCode()); + } + + @Test + public void testDeleteTokensErrorRecord_keepsTheErrorAndEchoedToken() { + BulkDeleteTokensResponseRecord record = deleteError(404); + Assert.assertEquals("Token not found", record.getError()); + Assert.assertEquals("tok-1", record.getToken()); + } } diff --git a/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java b/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java index 44a58686..7c20526a 100644 --- a/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java @@ -568,6 +568,79 @@ public void testValidateInsertRequest_validRequestWithTokens() { } } + @Test + public void testValidateInsertRequest_emptyTokensMapThrows() { + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").tokens(new HashMap<>()).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.EmptyTokens.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_nullKeyInTokensThrows() { + Map tokens = new HashMap<>(); + tokens.put(null, "tok-abc"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").tokens(tokens).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_blankKeyInTokensThrows() { + Map tokens = new HashMap<>(); + tokens.put(" ", "tok-abc"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").tokens(tokens).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_nullValueInTokensThrows() { + Map tokens = new HashMap<>(); + tokens.put("name", null); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").tokens(tokens).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.EmptyValueInTokens.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_blankValueInTokensThrows() { + Map tokens = new HashMap<>(); + tokens.put("name", " "); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").tokens(tokens).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.EmptyValueInTokens.getMessage(), e.getMessage()); + } + } + // ── validateDetokenizeRequest ───────────────────────────────────────────── @Test diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java index f23fd2d2..72918e46 100644 --- a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java @@ -197,14 +197,14 @@ public void testBulkInsert_interceptorAddsCustomHeader() throws Exception { records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); - RequestInterceptor interceptor = ctx -> ctx.addHeader(CustomHeaderKey.SkyflowAccountId, "acct-123"); + RequestInterceptor interceptor = ctx -> ctx.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "acct-123"); BulkInsertOptions options = BulkInsertOptions.builder().interceptor(interceptor).build(); controller.bulkInsert(request, options); ArgumentCaptor captor = ArgumentCaptor.forClass(RequestOptions.class); Mockito.verify(mockRaw).insert(any(), captor.capture()); - Assert.assertEquals("acct-123", captor.getValue().getHeaders().get(CustomHeaderKey.SkyflowAccountId.toString())); + Assert.assertEquals("acct-123", captor.getValue().getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID.toString())); } // ── bulkDetokenize ──────────────────────────────────────────────────────── @@ -741,7 +741,7 @@ public void intercept(com.skyflow.vault.data.RequestContext context) { callNumber = contexts.size(); contexts.add(context); } - context.addHeader(CustomHeaderKey.SkyflowAccountId, "batch-" + callNumber); + context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "batch-" + callNumber); } int callCount() { @@ -763,11 +763,25 @@ private static void assertInterceptorRanOncePerBatch(CountingInterceptor interce } Assert.assertEquals(EXPECTED_BATCH_COUNT, identities.size()); + // Each context must also report where its batch sits in the request, so an interceptor can + // tag batches apart (per-batch correlation id, "batch 3 of 12" logging). Every index in + // 0..n-1 must appear exactly once, and every context must agree on the total. + java.util.Set batchIndexes = new java.util.HashSet<>(); + for (com.skyflow.vault.data.RequestContext ctx : interceptor.contexts()) { + Assert.assertEquals("totalBatches must be the real batch count", + EXPECTED_BATCH_COUNT, ctx.getTotalBatches()); + Assert.assertTrue("batchIndex out of range: " + ctx.getBatchIndex(), + ctx.getBatchIndex() >= 0 && ctx.getBatchIndex() < EXPECTED_BATCH_COUNT); + batchIndexes.add(ctx.getBatchIndex()); + } + Assert.assertEquals("every batch position must appear exactly once", + EXPECTED_BATCH_COUNT, batchIndexes.size()); + // The header the interceptor set on each context must reach that batch's RequestOptions. Assert.assertEquals(EXPECTED_BATCH_COUNT, capturedOptions.size()); java.util.Set headerValues = new java.util.HashSet<>(); for (RequestOptions options : capturedOptions) { - String value = options.getHeaders().get(CustomHeaderKey.SkyflowAccountId.toString()); + String value = options.getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID.toString()); Assert.assertNotNull("Interceptor header missing on a batch", value); headerValues.add(value); } diff --git a/flowvault/src/test/java/com/skyflow/vault/data/RequestContextTests.java b/flowvault/src/test/java/com/skyflow/vault/data/RequestContextTests.java new file mode 100644 index 00000000..6d93125d --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/data/RequestContextTests.java @@ -0,0 +1,119 @@ +package com.skyflow.vault.data; + +import com.skyflow.enums.CustomHeaderKey; + +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +/** + * The interceptor context: its operation, its custom headers, and the batch position. + * + *

Batch position matters without it every batch of a bulk call presents an + * identical context, so a caller cannot tag them apart — no per-batch correlation id, no + * "batch 3 of 12" logging. + */ +public class RequestContextTests { + + @Test + public void testBatchedConstructor_reportsThePosition() { + RequestContext context = new RequestContext("INSERT", 2, 5); + + Assert.assertEquals("INSERT", context.getOperation()); + Assert.assertEquals(2, context.getBatchIndex()); + Assert.assertEquals(5, context.getTotalBatches()); + } + + @Test + public void testSingleArgConstructor_reportsNotBatched() { + // Kept for source compatibility; -1 distinguishes "not batched" from "the first batch". + RequestContext context = new RequestContext("INSERT"); + + Assert.assertEquals("INSERT", context.getOperation()); + Assert.assertEquals(-1, context.getBatchIndex()); + Assert.assertEquals(-1, context.getTotalBatches()); + } + + @Test + public void testFirstBatchIsZeroNotMinusOne() { + Assert.assertEquals(0, new RequestContext("INSERT", 0, 1).getBatchIndex()); + } + + @Test + public void testHeadersStillWorkAlongsideTheBatchPosition() { + RequestContext context = new RequestContext("DETOKENIZE", 1, 3); + context.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, "req-" + context.getBatchIndex()); + + Assert.assertEquals("req-1", context.getHeaders().get(CustomHeaderKey.REQUEST_ID_HEADER)); + } + + @Test + public void testHeadersRemainUnmodifiable() { + RequestContext context = new RequestContext("INSERT", 0, 1); + try { + context.getHeaders().put(CustomHeaderKey.REQUEST_ID_HEADER, "x"); + Assert.fail("the exposed header map must not be mutable"); + } catch (UnsupportedOperationException expected) { + Assert.assertTrue(true); + } + } + + // ── operation and headers (moved here with the class, from common) ────────── + @Test + public void testGetOperationReturnsConstructorValue() { + RequestContext context = new RequestContext("INSERT"); + + Assert.assertEquals("INSERT", context.getOperation()); + } + + @Test + public void testNullOperation() { + RequestContext context = new RequestContext(null); + + Assert.assertNull(context.getOperation()); + } + + @Test + public void testGetHeadersReturnsEmptyMapByDefault() { + RequestContext context = new RequestContext("INSERT"); + + Assert.assertTrue(context.getHeaders().isEmpty()); + } + + @Test + public void testAddHeaderIsReflectedInGetHeaders() { + RequestContext context = new RequestContext("INSERT"); + context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "account-id-value"); + + Map headers = context.getHeaders(); + + Assert.assertEquals(1, headers.size()); + Assert.assertEquals("account-id-value", headers.get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID)); + } + + @Test + public void testAddHeaderOverwritesExistingValueForSameKey() { + RequestContext context = new RequestContext("INSERT"); + context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "first-value"); + context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "second-value"); + + Assert.assertEquals(1, context.getHeaders().size()); + Assert.assertEquals("second-value", context.getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID)); + } + + @Test + public void testAddMultipleDistinctHeaders() { + RequestContext context = new RequestContext("DETOKENIZE"); + context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "account-id-value"); + context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_NAME, "account-name-value"); + + Assert.assertEquals(2, context.getHeaders().size()); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetHeadersReturnsUnmodifiableMap() { + RequestContext context = new RequestContext("INSERT"); + + context.getHeaders().put(CustomHeaderKey.REQUEST_ID_HEADER, "request-id-value"); + } +} diff --git a/common/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java b/flowvault/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java similarity index 87% rename from common/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java rename to flowvault/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java index d7bd3193..0fc7bf56 100644 --- a/common/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java @@ -8,12 +8,12 @@ public class RequestInterceptorTests { @Test public void testInterceptMutatesRequestContext() { - RequestInterceptor interceptor = context -> context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value"); + RequestInterceptor interceptor = context -> context.addHeader(CustomHeaderKey.SKYFLOW_ACCOUNT_ID, "account-id-value"); RequestContext context = new RequestContext("INSERT"); interceptor.intercept(context); - Assert.assertEquals("account-id-value", context.getHeaders().get(CustomHeaderKey.SkyflowAccountId)); + Assert.assertEquals("account-id-value", context.getHeaders().get(CustomHeaderKey.SKYFLOW_ACCOUNT_ID)); } @Test diff --git a/scripts/contract-snapshot-update.sh b/scripts/contract-snapshot-update.sh index bd33397b..9c4c5314 100755 --- a/scripts/contract-snapshot-update.sh +++ b/scripts/contract-snapshot-update.sh @@ -1,27 +1,56 @@ #!/usr/bin/env bash -# Regenerates the skyvault contract-testing baseline (api-report/skyflow-java.baseline.jar) +# Regenerates a module's contract-testing baseline (/api-report/*.baseline.jar) # from the CURRENT working tree and overwrites the committed snapshot. # -# Run this after an intentional public API change to v2, review the resulting -# git diff on the jar (a new binary blob) alongside your code change, and commit -# both together. This is the only way the committed baseline should ever change - -# japicmp never pulls a published version for this comparison. +# Run this after an intentional public API change, review the resulting git diff on +# the jar (a new binary blob) alongside your code change, and commit both together. +# This is the only way a committed baseline should ever change - japicmp never pulls +# a published version for the comparison. +# +# scripts/contract-snapshot-update.sh skyvault # regenerate one module +# scripts/contract-snapshot-update.sh flowvault +# scripts/contract-snapshot-update.sh # both +# +# Prefer naming the module you actually changed. Jar archives embed timestamps, so +# regenerating a module whose API did not change still produces different bytes and +# a spurious diff on a binary file - which is exactly the thing a reviewer cannot +# eyeball. Only the baseline you intend to move should appear in the commit. set -euo pipefail cd "$(dirname "$0")/.." -mvn -B package -pl common,v2 -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true - -SHADED_JAR=$(ls skyvault/target/skyflow-java-*-with-common.jar | head -n1) +# module -> artifactId, which is also the baseline jar's name +declare -A ARTIFACTS=( + [skyvault]="skyflow-java" + [flowvault]="skyflow-flowvault-java" +) -if [ -z "$SHADED_JAR" ]; then - echo "Error: could not find a built skyvault/target/skyflow-java-*-with-common.jar. Did the build succeed?" - exit 1 +MODULES=("$@") +if [ ${#MODULES[@]} -eq 0 ]; then + MODULES=(skyvault flowvault) fi -mkdir -p skyvault/api-report -cp "$SHADED_JAR" skyvault/api-report/skyflow-java.baseline.jar +for MODULE in "${MODULES[@]}"; do + ARTIFACT="${ARTIFACTS[$MODULE]:-}" + if [ -z "$ARTIFACT" ]; then + echo "Error: unknown module '$MODULE'. Expected one of: ${!ARTIFACTS[*]}" + exit 1 + fi + + echo "=== $MODULE ===" + mvn -B package -pl "common,$MODULE" -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true + + # the comparison-only jar, which merges com.skyflow:common into the module + SHADED_JAR=$(ls "$MODULE"/target/"$ARTIFACT"-*-with-common.jar 2>/dev/null | head -n1) + if [ -z "$SHADED_JAR" ]; then + echo "Error: could not find $MODULE/target/$ARTIFACT-*-with-common.jar. Did the build succeed?" + exit 1 + fi + + mkdir -p "$MODULE/api-report" + cp "$SHADED_JAR" "$MODULE/api-report/$ARTIFACT.baseline.jar" + echo "Updated $MODULE/api-report/$ARTIFACT.baseline.jar from $SHADED_JAR" +done echo "--------------------------" -echo "Updated skyvault/api-report/skyflow-java.baseline.jar from $SHADED_JAR" -echo "Review the diff and commit this file alongside your API change." +echo "Review the diff and commit the baseline jar(s) alongside your API change." diff --git a/scripts/current_module_version.sh b/scripts/current_module_version.sh new file mode 100755 index 00000000..0bab3458 --- /dev/null +++ b/scripts/current_module_version.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Prints 's own current (skipping the inherited +# block, which has its own tag), with any existing -dev. +# suffix stripped. Read-only - never modifies the pom. +# +# Used by internal releases to get a module's base version without touching +# git tags at all, so it can never accidentally pick up another module's tag. +set -euo pipefail + +Module=$1 +PomFile="$Module/pom.xml" + +raw_line=$(awk ' + //,/<\/parent>/ { next } + // { print; exit } +' "$PomFile") + +version=$(echo "$raw_line" | sed -E 's#.*([^<]+).*#\1#') +version=$(echo "$version" | sed -E 's/-dev\.[0-9a-f]+$//') + +echo "$version" diff --git a/skyvault/README.md b/skyvault/README.md new file mode 100644 index 00000000..e996553a --- /dev/null +++ b/skyvault/README.md @@ -0,0 +1,3151 @@ +# Skyflow Java + +> **This SDK brings flexible auth, multi-vault support, builder patterns, native data types, and rich error diagnostics.** +> +> Meant for **Privacy DB** vaults. +> +> Migrating from v1? See the **[Migration Guide](../docs/migrate_to_v2.md)** for step-by-step instructions. V1 is in maintenance mode and will reach End of Life on October 31, 2026. + +The Skyflow Java SDK is designed to help with integrating Skyflow into a Java backend. + +[![CI](https://img.shields.io/static/v1?label=CI&message=passing&color=green?style=plastic&logo=github)](https://github.com/skyflowapi/skyflow-java/actions) +[![GitHub release](https://img.shields.io/github/v/release/skyflowapi/skyflow-java.svg)](https://mvnrepository.com/artifact/com.skyflow/skyflow-java) +[![License](https://img.shields.io/github/license/skyflowapi/skyflow-java)](https://github.com/skyflowapi/skyflow-java/blob/main/LICENSE) + +# Table of Contents + +- [Table of Contents](#table-of-contents) +- [Overview](#overview) +- [Install](#install) + - [Requirements](#requirements) + - [Configuration](#configuration) + - [Gradle users](#gradle-users) + - [Maven users](#maven-users) +- [API Reference](../docs/api_reference.md) +- [Migration from v1 to v2](../docs/migrate_to_v2.md) +- [Quickstart](#quickstart) + - [Authenticate](#authenticate) + - [Initialize the client](#initialize-the-client) + - [Insert data into the vault](#insert-data-into-the-vault) +- [Vault](#vault) + - [VaultController](#vaultcontroller) + - [Insert data into the vault](#insert-data-into-the-vault-1) + - [Detokenize](#detokenize) + - [DetokenizeRecordResponse](#detokenizerecordresponse) + - [Tokenize](#tokenize) + - [Get](#get) + - [Get by skyflow IDS](#get-by-skyflow-ids) + - [Get tokens](#get-tokens) + - [Get by column name and column values](#get-by-column-name-and-column-values) + - [Redaction types](#redaction-types) + - [Update](#update) + - [Delete](#delete) + - [Query](#query) + - [Upload File](#upload-file) + +- [Detect](#detect) + - [Deidentify Text](#deidentify-text) + - [Reidentify Text](#reidentify-text) + - [Deidentify File](#deidentify-file) + - [Get Run](#get-run) + - [Detect response types](#detect-response-types) + - [Detect enums](#detect-enums) +- [Connections](#connections) + - [ConnectionController](#connectioncontroller) + - [Invoke a connection](#invoke-a-connection) +- [Client Management](#client-management) +- [Authenticate with bearer tokens](#authenticate-with-bearer-tokens) + - [Generate a bearer token](#generate-a-bearer-token) + - [Generate bearer tokens with context](#generate-bearer-tokens-with-context) + - [Generate scoped bearer tokens](#generate-scoped-bearer-tokens) + - [Generate signed data tokens](#generate-signed-data-tokens) + - [Bearer token expiry edge case](#bearer-token-expiry-edge-case) +- [Error Handling](#error-handling) + - [Catching SkyflowException](#catching-skyflowexception) + - [SkyflowException properties](#skyflowexception-properties) +- [Logging](#logging) +- [Reporting a Vulnerability](#reporting-a-vulnerability) + +# Overview + +- Authenticate using a Skyflow service account and generate bearer tokens for secure access. +- Perform Vault API operations such as inserting, retrieving, and tokenizing sensitive data with ease. +- Invoke connections to third-party APIs without directly handling sensitive data, ensuring compliance and data protection. + +> [!TIP] +> Looking for the full list of request builder methods, response getters, enums, helper class APIs, and service-account utilities? See the **[API Reference](../docs/api_reference.md)**. + +# Install + +## Requirements + +- Java 8 and above (tested with Java 8) + +## Configuration + +--- + +### Gradle users + +Add this dependency to your project's `build.gradle` file: + +``` +implementation 'com.skyflow:skyflow-java:2.0.0' +``` + +### Maven users + +Add this dependency to your project's `pom.xml` file: + +```xml + + com.skyflow + skyflow-java + 2.0.0 + +``` + +--- + +# Migrate from v1 to v2 + +Upgrading from v1? See the dedicated migration guide: **[../docs/migrate_to_v2.md](../docs/migrate_to_v2.md)** + +# Quickstart + +Get started quickly with the essential steps: authenticate, initialize the client, and perform a basic vault operation. This section provides a minimal setup to help you integrate the SDK efficiently. + +### Authenticate + +You can use an API key to authenticate and authorize requests to an API. For authenticating via bearer tokens and different supported bearer token types, refer to the [Authenticate with bearer tokens](#authenticate-with-bearer-tokens) section. + +```java +// create a new credentials object +Credentials credentials = new Credentials(); +credentials.setApiKey(""); // add your API key in credentials +``` + +### Initialize the client + +To get started, you must first initialize the skyflow client. While initializing the skyflow client, you can specify different types of credentials. + +1. **API keys** + A unique identifier used to authenticate and authorize requests to an API. + +2. **Bearer tokens** + A temporary access token used to authenticate API requests, typically included in the Authorization header. + +3. **Service account credentials file path** + The file path pointing to a JSON file containing credentials for a service account, used for secure API access. + +4. **Service account credentials string (JSON formatted)** + A JSON-formatted string containing service account credentials, often used as an alternative to a file for programmatic authentication. + +Note: Only one type of credential can be used at a time. If multiple credentials are provided, the last one added will take precedence. + +```java +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +/** + * Example program to initialize the Skyflow client with various configurations. + * The Skyflow client facilitates secure interactions with the Skyflow vault, + * such as securely managing sensitive data. + */ +public class InitSkyflowClient { + public static void main(String[] args) throws SkyflowException { + // Step 1: Define the primary credentials for authentication. + // Note: Only one type of credential can be used at a time. You can choose between: + // - API key + // - Bearer token + // - A credentials string (JSON-formatted) + // - A file path to a credentials file. + + // Initialize primary credentials using a Bearer token for authentication. + Credentials primaryCredentials = new Credentials(); + primaryCredentials.setToken(""); // Replace with your actual authentication token. + + // Step 2: Configure the primary vault details. + // VaultConfig stores all necessary details to connect to a specific Skyflow vault. + VaultConfig primaryConfig = new VaultConfig(); + primaryConfig.setVaultId(""); // Replace with your primary vault's ID. + primaryConfig.setClusterId(""); // Replace with the cluster ID (part of the vault URL, e.g., https://{clusterId}.vault.skyflowapis.com). + primaryConfig.setEnv(Env.PROD); // Set the environment (PROD, SANDBOX, STAGE, DEV). + primaryConfig.setCredentials(primaryCredentials); // Attach the primary credentials to this vault configuration. + + // Step 3: Create credentials as a JSON object (if a Bearer Token is not provided). + // Demonstrates an alternate approach to authenticate with Skyflow using a credentials object. + JsonObject credentialsObject = new JsonObject(); + credentialsObject.addProperty("clientId", ""); // Replace with your Client ID. + credentialsObject.addProperty("clientName", ""); // Replace with your Client Name. + credentialsObject.addProperty("tokenUri", ""); // Replace with the Token URI. + credentialsObject.addProperty("keyId", ""); // Replace with your Key ID. + credentialsObject.addProperty("privateKey", ""); // Replace with your Private Key. + + // Step 4: Convert the JSON object to a string and use it as credentials. + // This approach allows the use of dynamically generated or pre-configured credentials. + Credentials skyflowCredentials = new Credentials(); + skyflowCredentials.setCredentialsString(credentialsObject.toString()); // Converts JSON object to string for use as credentials. + + // Step 5: Define secondary credentials (API key-based authentication as an example). + // Demonstrates a different type of authentication mechanism for Skyflow vaults. + Credentials secondaryCredentials = new Credentials(); + secondaryCredentials.setApiKey(""); // Replace with your API Key for authentication. + + // Step 6: Configure the secondary vault details. + // A secondary vault configuration can be used for operations involving multiple vaults. + VaultConfig secondaryConfig = new VaultConfig(); + secondaryConfig.setVaultId(""); // Replace with your secondary vault's ID. + secondaryConfig.setClusterId(""); // Replace with the corresponding cluster ID. + secondaryConfig.setEnv(Env.SANDBOX); // Set the environment for this vault. + secondaryConfig.setCredentials(secondaryCredentials); // Attach the secondary credentials to this configuration. + + // Step 7: Define tertiary credentials using a path to a credentials JSON file. + // This method demonstrates an alternative authentication method. + Credentials tertiaryCredentials = new Credentials(); + tertiaryCredentials.setPath(""); // Replace with the path to your credentials file. + + // Step 8: Configure the tertiary vault details. + VaultConfig tertiaryConfig = new VaultConfig(); + tertiaryConfig.setVaultId(""); // Replace with the tertiary vault ID. + tertiaryConfig.setClusterId(""); // Replace with the corresponding cluster ID. + tertiaryConfig.setEnv(Env.STAGE); // Set the environment for this vault. + tertiaryConfig.setCredentials(tertiaryCredentials); // Attach the tertiary credentials. + + // Step 9: Build and initialize the Skyflow client. + // Skyflow client is configured with multiple vaults and credentials. + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.INFO) // Set log level for debugging or monitoring purposes. + .addVaultConfig(primaryConfig) // Add the primary vault configuration. + .addVaultConfig(secondaryConfig) // Add the secondary vault configuration. + .addVaultConfig(tertiaryConfig) // Add the tertiary vault configuration. + .addSkyflowCredentials(skyflowCredentials) // Add JSON-formatted credentials if applicable. + .build(); + + // The Skyflow client is now fully initialized. + // Use the `skyflowClient` object to perform secure operations such as: + // - Inserting data + // - Retrieving data + // - Deleting data + // within the configured Skyflow vaults. + } +} +``` + +Notes: + +- If both Skyflow common credentials and individual credentials at the configuration level are specified, the individual credentials at the configuration level will take precedence. +- If neither Skyflow common credentials nor individual configuration-level credentials are provided, the SDK attempts to retrieve credentials from the `SKYFLOW_CREDENTIALS` environment variable. +- All Vault operations require a client instance. +- `Credentials.setContext()` accepts either a `String` or a `Map` for context-aware authorization. See [Generate bearer tokens with context](#generate-bearer-tokens-with-context) for full usage. + +### Insert data into the vault + +To insert data into your vault, use the `insert` method. The `InsertRequest` class creates an insert request, which includes the values to be inserted as a list of records. Below is a simple example to get started. For advanced options, check out [Insert data into the vault](#insert-data-into-the-vault-1) section. + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.InsertResponse; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This example demonstrates how to insert sensitive data (e.g., card information) into a Skyflow vault using the Skyflow client. + * + * 1. Initializes the Skyflow client. + * 2. Prepares a record with sensitive data (e.g., card number and cardholder name). + * 3. Creates an insert request for inserting the data into the Skyflow vault. + * 4. Prints the response of the insert operation. + */ +public class InsertExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize data to be inserted into the Skyflow vault + ArrayList> insertData = new ArrayList<>(); + + // Create a HashMap for a single record with card number and cardholder name as fields + HashMap insertRecord = new HashMap<>(); + insertRecord.put("card_number", "4111111111111111"); // Replace with actual card number (sensitive data) + insertRecord.put("cardholder_name", "john doe"); // Replace with actual cardholder name (sensitive data) + + // Add the created record to the list of data to be inserted + insertData.add(insertRecord); + + // Step 2: Build the InsertRequest object with the table name and data to insert + InsertRequest insertRequest = InsertRequest.builder() + .table("table1") // Specify the table in the vault where the data will be inserted + .values(insertData) // Attach the data (records) to be inserted + .returnTokens(true) // Specify if tokens should be returned upon successful insertion + .build(); // Build the insert request object + + // Step 3: Perform the insert operation using the Skyflow client + InsertResponse insertResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").insert(insertRequest); + // Replace the vault ID "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID + + // Step 4: Print the response from the insert operation + System.out.println(insertResponse); + } catch (SkyflowException e) { + // Step 5: Handle any exceptions that may occur during the insert operation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the stack trace for debugging purposes + } + } +} +``` + +Skyflow returns tokens for the record that was just inserted. + +```json +{ + "insertedFields": [ + { + "card_number": "5484-7829-1702-9110", + "requestIndex": "0", + "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1", + "cardholder_name": "b2308e2a-c1f5-469b-97b7-1f193159399b" + } + ], + "errors": [] +} +``` + +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + +# Vault + +The [Vault](https://github.com/skyflowapi/skyflow-java/tree/main/samples/src/main/java/com/example/vault) module performs operations on the vault, including inserting records, detokenizing tokens, and retrieving tokens associated with a `skyflow_id`. + +## VaultController + +`VaultController` is the class returned by `skyflowClient.vault()` and `skyflowClient.vault(vaultId)`. All vault operations are called on this object. + +```java +// Uses the default (first configured) vault +VaultController vault = skyflowClient.vault(); + +// Uses a specific vault by ID +VaultController vault = skyflowClient.vault(""); +``` + +**Methods:** + +| Method | Parameters | Returns | Description | +|--------|-----------|---------|-------------| +| `insert(InsertRequest)` | [`InsertRequest`](../docs/api_reference.md#insertrequest) | [`InsertResponse`](../docs/api_reference.md#insertresponse) | Insert one or more records | +| `detokenize(DetokenizeRequest)` | [`DetokenizeRequest`](../docs/api_reference.md#detokenizerequest) | [`DetokenizeResponse`](../docs/api_reference.md#detokenizeresponse) | Detokenize tokens to their original values | +| `tokenize(TokenizeRequest)` | [`TokenizeRequest`](../docs/api_reference.md#tokenizerequest) | [`TokenizeResponse`](../docs/api_reference.md#tokenizeresponse) | Tokenize sensitive values | +| `get(GetRequest)` | [`GetRequest`](../docs/api_reference.md#getrequest) | [`GetResponse`](../docs/api_reference.md#getresponse) | Retrieve records by Skyflow ID or column value | +| `update(UpdateRequest)` | [`UpdateRequest`](../docs/api_reference.md#updaterequest) | [`UpdateResponse`](../docs/api_reference.md#updateresponse) | Update a record by Skyflow ID | +| `delete(DeleteRequest)` | [`DeleteRequest`](../docs/api_reference.md#deleterequest) | [`DeleteResponse`](../docs/api_reference.md#deleteresponse) | Delete records by Skyflow ID | +| `query(QueryRequest)` | [`QueryRequest`](../docs/api_reference.md#queryrequest) | [`QueryResponse`](../docs/api_reference.md#queryresponse) | Execute a SQL query | +| `uploadFile(FileUploadRequest)` | [`FileUploadRequest`](../docs/api_reference.md#fileuploadrequest) | [`FileUploadResponse`](../docs/api_reference.md#fileuploadresponse) | Upload a file to a vault column | + +All methods throw `SkyflowException` on error. + +## Insert data into the vault + +Apart from using the `insert` method to insert data into your vault covered in [Quickstart](#quickstart), you can also specify options in [`InsertRequest`](../docs/api_reference.md#insertrequest), such as returning tokenized data, upserting records, or continuing the operation in case of errors. Returns an [`InsertResponse`](../docs/api_reference.md#insertresponse). + +### Construct an insert request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.InsertResponse; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Example program to demonstrate inserting data into a Skyflow vault, along with corresponding InsertRequest schema. + * + */ +public class InsertSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Prepare the data to be inserted into the Skyflow vault + ArrayList> insertData = new ArrayList<>(); + + // Create the first record with field names and their respective values + HashMap insertRecord1 = new HashMap<>(); + insertRecord1.put("", ""); // Replace with actual field name and value + insertRecord1.put("", ""); // Replace with actual field name and value + + // Create the second record with field names and their respective values + HashMap insertRecord2 = new HashMap<>(); + insertRecord2.put("", ""); // Replace with actual field name and value + insertRecord2.put("", ""); // Replace with actual field name and value + + // Add the records to the list of data to be inserted + insertData.add(insertRecord1); + insertData.add(insertRecord2); + + // Step 2: Build an InsertRequest object with the table name and the data to insert + InsertRequest insertRequest = InsertRequest.builder() + .table("") // Replace with the actual table name in your Skyflow vault + .values(insertData) // Attach the data to be inserted + .build(); + + // Step 3: Use the Skyflow client to perform the insert operation + InsertResponse insertResponse = skyflowClient.vault("").insert(insertRequest); + // Replace with your actual vault ID + + // Print the response from the insert operation + System.out.println("Insert Response: " + insertResponse); + } catch (SkyflowException e) { + // Step 4: Handle any exceptions that occur during the insert operation + System.out.println("Error occurred while inserting data: "); + e.printStackTrace(); // Print the stack trace for debugging + } + } +} +``` + +### Insert call [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/InsertExample.java) with `continueOnError` option + +The `continueOnError` flag is a boolean that determines whether insert operation should proceed despite encountering partial errors. Set to `true` to allow the process to continue even if some errors occur. + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.InsertResponse; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This example demonstrates how to insert multiple records into a Skyflow vault using the Skyflow client. + * + * 1. Initializes the Skyflow client. + * 2. Prepares multiple records with sensitive data (e.g., card number and cardholder name). + * 3. Creates an insert request with the records to insert into the Skyflow vault. + * 4. Specifies options to continue on error and return tokens. + * 5. Prints the response of the insert operation. + */ +public class InsertExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list to hold the data records to be inserted into the vault + ArrayList> insertData = new ArrayList<>(); + + // Step 2: Create the first record with card number and cardholder name + HashMap insertRecord1 = new HashMap<>(); + insertRecord1.put("card_number", "4111111111111111"); // Replace with actual card number (sensitive data) + insertRecord1.put("cardholder_name", "john doe"); // Replace with actual cardholder name (sensitive data) + + // Step 3: Create the second record with card number and cardholder name + HashMap insertRecord2 = new HashMap<>(); + insertRecord2.put("card_number", "4111111111111111"); // Ensure field name matches ("card_number") + insertRecord2.put("cardholder_name", "jane doe"); // Replace with actual cardholder name (sensitive data) + + // Step 4: Add the records to the insertData list + insertData.add(insertRecord1); + insertData.add(insertRecord2); + + // Step 5: Build the InsertRequest object with the data records to insert + InsertRequest insertRequest = InsertRequest.builder() + .table("table1") // Specify the table in the vault where data will be inserted + .values(insertData) // Attach the data records to be inserted + .returnTokens(true) // Specify if tokens should be returned upon successful insertion + .continueOnError(true) // Specify to continue inserting records even if an error occurs for some records + .build(); // Build the insert request object + + // Step 6: Perform the insert operation using the Skyflow client + InsertResponse insertResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").insert(insertRequest); + // Replace the vault ID "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID + + // Step 7: Print the response from the insert operation + System.out.println(insertResponse); + } catch (SkyflowException e) { + // Step 8: Handle any exceptions that may occur during the insert operation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the stack trace for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "insertedFields": [ + { + "card_number": "5484-7829-1702-9110", + "requestIndex": "0", + "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1", + "cardholder_name": "b2308e2a-c1f5-469b-97b7-1f193159399b" + } + ], + "errors": [ + { + "requestIndex": "1", + "error": "Insert failed. Column card_number is invalid. Specify a valid column." + } + ] +} +``` + +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + +### Insert call example with `upsert` option + +An upsert operation checks for a record based on a unique column's value. If a match exists, the record is updated; otherwise, a new record is inserted. + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.InsertResponse; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This example demonstrates how to insert or upsert a record into a Skyflow vault using the Skyflow client, with the option to return tokens. + * + * 1. Initializes the Skyflow client. + * 2. Prepares a record to insert or upsert (e.g., cardholder name). + * 3. Creates an insert request with the data to be inserted or upserted into the Skyflow vault. + * 4. Specifies the field (cardholder_name) for upsert operations. + * 5. Prints the response of the insert or upsert operation. + */ +public class UpsertExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list to hold the data records for the insert/upsert operation + ArrayList> upsertData = new ArrayList<>(); + + // Step 2: Create a record with the field 'cardholder_name' to insert or upsert + HashMap upsertRecord = new HashMap<>(); + upsertRecord.put("cardholder_name", "jane doe"); // Replace with the actual cardholder name + + // Step 3: Add the record to the upsertData list + upsertData.add(upsertRecord); + + // Step 4: Build the InsertRequest object with the upsertData + InsertRequest insertRequest = InsertRequest.builder() + .table("table1") // Specify the table in the vault where data will be inserted/upserted + .values(upsertData) // Attach the data records to be inserted/upserted + .returnTokens(true) // Specify if tokens should be returned upon successful operation + .upsert("cardholder_name") // Specify the field to be used for upsert operations (e.g., cardholder_name) + .build(); // Build the insert request object + + // Step 5: Perform the insert/upsert operation using the Skyflow client + InsertResponse insertResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").insert(insertRequest); + // Replace the vault ID "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID + + // Step 6: Print the response from the insert/upsert operation + System.out.println(insertResponse); + } catch (SkyflowException e) { + // Step 7: Handle any exceptions that may occur during the insert/upsert operation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the stack trace for debugging purposes + } + } +} +``` + +Skyflow returns tokens, with `upsert` support, for the record you just inserted. + +```json +{ + "insertedFields": [ + { + "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1", + "cardholder_name": "73ce45ce-20fd-490e-9310-c1d4f603ee83" + } + ], + "errors": [] +} +``` + +## Detokenize + +To retrieve tokens from your vault, use the `detokenize` method. [`DetokenizeRequest`](../docs/api_reference.md#detokenizerequest) requires a list of detokenization data as input. Returns a [`DetokenizeResponse`](../docs/api_reference.md#detokenizeresponse). + +### Construct a detokenize request + +Each entry in the detokenize list is a [`DetokenizeData`](../docs/api_reference.md#detokenizedata) object pairing a token with its desired redaction type. See the [API Reference](../docs/api_reference.md#detokenizerequest) for all `DetokenizeRequest` builder options. + +```java +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.tokens.DetokenizeRequest; +import com.skyflow.vault.tokens.DetokenizeResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to detokenize sensitive data from tokens stored in a Skyflow vault, along with corresponding DetokenizeRequest schema. + * + */ +public class DetokenizeSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of tokens to be detokenized (replace with actual tokens) + ArrayList detokenizeData1 = new ArrayList<>(); + DetokenizeData detokenizeDataRecord1 = new DetokenizeData("", RedactionType.PLAIN_TEXT); // Replace with a token to detokenize with PLAIN_TEXT redaction + DetokenizeData detokenizeDataRecord2 = new DetokenizeData("", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction + detokenizeData1.add(detokenizeDataRecord1); + detokenizeData1.add(detokenizeDataRecord2); + + // Step 2: Create the DetokenizeRequest object with the tokens and redaction type + DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() + .detokenizeData(detokenizeData1) // Specify detokenize data with specified redaction types + .continueOnError(true) // Continue even if one token cannot be detokenized + .build(); // Build the detokenization request + + // Step 3: Call the Skyflow vault to detokenize the provided tokens + DetokenizeResponse detokenizeResponse = skyflowClient.vault("").detokenize(detokenizeRequest); + // Replace with your actual Skyflow vault ID + + // Step 4: Print the detokenization response, which contains the detokenized data + System.out.println(detokenizeResponse); + } catch (SkyflowException e) { + // Step 5: Handle any errors that occur during the detokenization process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Notes: + +- `redactionType` defaults to [`RedactionType.PLAIN_TEXT`](#redaction-types). +- `continueOnError` defaults to `true`. + +### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/DetokenizeExample.java) of a detokenize call: + +```java +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.tokens.DetokenizeRequest; +import com.skyflow.vault.tokens.DetokenizeResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to detokenize sensitive data from tokens stored in a Skyflow vault. + * + * 1. Initializes the Skyflow client. + * 2. Creates a list of tokens (e.g., credit card tokens) that represent the sensitive data. + * 3. Builds a detokenization request using the provided tokens and specifies how the redacted data should be returned. + * 4. Calls the Skyflow vault to detokenize the tokens and retrieves the detokenized data. + * 5. Prints the detokenization response, which contains the detokenized values or errors. + */ +public class DetokenizeExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of tokens to be detokenized (replace with actual token values) + ArrayList detokenizeData1 = new ArrayList<>(); + DetokenizeData detokenizeDataRecord1 = new DetokenizeData("9738-1683-0486-1480", RedactionType.PLAIN_TEXT); // Replace with a token to detokenize with PLAIN_TEXT redaction + DetokenizeData detokenizeDataRecord2 = new DetokenizeData("6184-6357-8409-6668", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction + detokenizeData1.add(detokenizeDataRecord1); + detokenizeData1.add(detokenizeDataRecord2); + + // Step 2: Create the DetokenizeRequest object with the tokens and redaction type + DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() + .detokenizeData(detokenizeData1) // Specify detokenize data with specified redaction types + .continueOnError(true) // Continue even if one token cannot be detokenized + .build(); // Build the detokenization request + + // Step 3: Call the Skyflow vault to detokenize the provided tokens + DetokenizeResponse detokenizeResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").detokenize(detokenizeRequest); + // Replace "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID + + // Step 4: Print the detokenization response, which contains the detokenized data + System.out.println(detokenizeResponse); + } catch (SkyflowException e) { + // Step 5: Handle any errors that occur during the detokenization process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "detokenizedFields": [{ + "token": "9738-1683-0486-1480", + "value": "4111111111111115", + "type": "STRING", + }, { + "token": "6184-6357-8409-6668", + "value": "4111111111111119", + "type": "STRING", + }], + "errors": [] +} + +``` + +### DetokenizeRecordResponse + +`DetokenizeResponse.getDetokenizedFields()` and `DetokenizeResponse.getErrors()` each return a `List`. Use this class to read individual token results: + +```java +DetokenizeResponse detokenizeResponse = skyflowClient.vault("").detokenize(detokenizeRequest); + +for (DetokenizeRecordResponse record : detokenizeResponse.getDetokenizedFields()) { + System.out.println("Token : " + record.getToken()); + System.out.println("Value : " + record.getValue()); + System.out.println("Type : " + record.getType()); + System.out.println("ReqID : " + record.getRequestId()); +} + +for (DetokenizeRecordResponse err : detokenizeResponse.getErrors()) { + System.out.println("Failed token : " + err.getToken()); + System.out.println("Error : " + err.getError()); +} +``` + +See [`DetokenizeRecordResponse`](../docs/api_reference.md#detokenizerecordresponse) in the API Reference for the full attribute list. + +### An example of a detokenize call with `continueOnError` option: + +```java +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.tokens.DetokenizeRequest; +import com.skyflow.vault.tokens.DetokenizeResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to detokenize sensitive data (e.g., credit card numbers) from tokens in a Skyflow vault. + * + * 1. Initializes the Skyflow client. + * 2. Creates a list of tokens (e.g., credit card tokens) to be detokenized. + * 3. Builds a detokenization request with the tokens and specifies the redaction type for the detokenized data. + * 4. Calls the Skyflow vault to detokenize the tokens and retrieves the detokenized data. + * 5. Prints the detokenization response, which includes the detokenized values or errors. + */ +public class DetokenizeExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of tokens to be detokenized (replace with actual token values) + // Step 1: Initialize a list of tokens to be detokenized (replace with actual token values) + ArrayList detokenizeData1 = new ArrayList<>(); + DetokenizeData detokenizeDataRecord1 = new DetokenizeData("9738-1683-0486-1480", RedactionType.PLAIN_TEXT); // Replace with a token to detokenize with PLAIN_TEXT redaction + DetokenizeData detokenizeDataRecord2 = new DetokenizeData("6184-6357-8409-6668", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction + DetokenizeData detokenizeDataRecord2 = new DetokenizeData("4914-9088-2814-384", RedactionType.PLAIN_TEXT); // Replace with another token to detokenize with PLAIN_TEXT redaction + + detokenizeData1.add(detokenizeDataRecord1); + detokenizeData1.add(detokenizeDataRecord2); + + // Step 2: Create the DetokenizeRequest object with the tokens and redaction type + DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() + .detokenizeData(detokenizeData1) // Specify detokenize data with specified redaction types + .continueOnError(true) // Continue even if one token cannot be detokenized + .build(); // Build the detokenization request + + // Step 3: Call the Skyflow vault to detokenize the provided tokens + DetokenizeResponse detokenizeResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").detokenize(detokenizeRequest); + // Replace "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID + + // Step 4: Print the detokenization response, which contains the detokenized data or errors + System.out.println(detokenizeResponse); + } catch (SkyflowException e) { + // Step 5: Handle any errors that occur during the detokenization process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "detokenizedFields": [{ + "token": "9738-1683-0486-1480", + "value": "4111111111111115", + "type": "STRING", + }, { + "token": "6184-6357-8409-6668", + "value": "4111111111111119", + "type": "STRING", + }], + "errors": [{ + "token": "4914-9088-2814-384", + "error": "Token Not Found", + }] +} +``` + +## Tokenize + +Tokenization replaces sensitive data with unique identifier tokens. This approach protects sensitive information by securely storing the original data while allowing the use of tokens within your application. + +To tokenize data, use the `tokenize` method. [`TokenizeRequest`](../docs/api_reference.md#tokenizerequest) accepts a list of [`ColumnValue`](../docs/api_reference.md#columnvalue) objects. Returns a [`TokenizeResponse`](../docs/api_reference.md#tokenizeresponse). + +### Construct a tokenize request + +Each entry in the tokenize list is a [`ColumnValue`](../docs/api_reference.md#columnvalue) object pairing a value with its column group. See the [API Reference](../docs/api_reference.md#tokenizerequest) for all `TokenizeRequest` builder options. + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.tokens.ColumnValue; +import com.skyflow.vault.tokens.TokenizeRequest; +import com.skyflow.vault.tokens.TokenizeResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to tokenize sensitive data (e.g., credit card information) using the Skyflow client, along with corresponding TokenizeRequest schema. + * + */ +public class TokenizeSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of column values to be tokenized (replace with actual sensitive data) + ArrayList columnValues = new ArrayList<>(); + + // Step 2: Create column values for each sensitive data field (e.g., card number and cardholder name) + ColumnValue columnValue1 = ColumnValue.builder().value("").columnGroup("").build(); // Replace and with actual data + ColumnValue columnValue2 = ColumnValue.builder().value("").columnGroup("").build(); // Replace and with actual data + + // Add the created column values to the list + columnValues.add(columnValue1); + columnValues.add(columnValue2); + + // Step 3: Build the TokenizeRequest with the column values + TokenizeRequest tokenizeRequest = TokenizeRequest.builder().values(columnValues).build(); + + // Step 4: Call the Skyflow vault to tokenize the sensitive data + TokenizeResponse tokenizeResponse = skyflowClient.vault("").tokenize(tokenizeRequest); + // Replace with your actual Skyflow vault ID + + // Step 5: Print the tokenization response, which contains the generated tokens or errors + System.out.println(tokenizeResponse); + } catch (SkyflowException e) { + // Step 6: Handle any errors that occur during the tokenization process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/TokenizeExample.java) of Tokenize call: + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.tokens.ColumnValue; +import com.skyflow.vault.tokens.TokenizeRequest; +import com.skyflow.vault.tokens.TokenizeResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to tokenize sensitive data (e.g., credit card information) using the Skyflow client. + * + * 1. Initializes the Skyflow client. + * 2. Creates a column value for sensitive data (e.g., credit card number). + * 3. Builds a tokenize request with the column value to be tokenized. + * 4. Sends the request to the Skyflow vault for tokenization. + * 5. Prints the tokenization response, which includes the token or errors. + */ +public class TokenizeExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of column values to be tokenized (replace with actual sensitive data) + ArrayList columnValues = new ArrayList<>(); + + // Step 2: Create a column value for the sensitive data (e.g., card number with its column group) + ColumnValue columnValue = ColumnValue.builder() + .value("4111111111111111") // Replace with the actual sensitive data (e.g., card number) + .columnGroup("card_number_cg") // Replace with the actual column group name + .build(); + + // Add the created column value to the list + columnValues.add(columnValue); + + // Step 3: Build the TokenizeRequest with the column value + TokenizeRequest tokenizeRequest = TokenizeRequest.builder().values(columnValues).build(); + + // Step 4: Call the Skyflow vault to tokenize the sensitive data + TokenizeResponse tokenizeResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").tokenize(tokenizeRequest); + // Replace "9f27764a10f7946fe56b3258e117" with your actual Skyflow vault ID + + // Step 5: Print the tokenization response, which contains the generated token or any errors + System.out.println(tokenizeResponse); + } catch (SkyflowException e) { + // Step 6: Handle any errors that occur during the tokenization process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "tokens": [5479-4229-4622-1393] +} +``` + +## Get + +To retrieve data using Skyflow IDs or unique column values, use the `get` method. [`GetRequest`](../docs/api_reference.md#getrequest) accepts parameters such as table name, redaction type, Skyflow IDs, column names, and column values. `ids` and `columnName`/`columnValues` are mutually exclusive. Returns a [`GetResponse`](../docs/api_reference.md#getresponse). + +### Construct a get request + +```java +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.GetRequest; +import com.skyflow.vault.data.GetResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to retrieve data from the Skyflow vault using different methods, along with corresponding GetRequest schema. + * + */ +public class GetSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of Skyflow IDs to retrieve records (replace with actual Skyflow IDs) + ArrayList ids = new ArrayList<>(); + ids.add(""); // Replace with actual Skyflow ID + ids.add(""); // Replace with actual Skyflow ID + + // Step 2: Create a GetRequest to retrieve records by Skyflow ID without returning tokens + GetRequest getByIdRequest = GetRequest.builder() + .ids(ids) + .table("") // Replace with the actual table name + .returnTokens(false) // Set to false to avoid returning tokens + .redactionType(RedactionType.PLAIN_TEXT) // Redact data as plain text + .build(); + + // Send the request to the Skyflow vault and retrieve the records + GetResponse getByIdResponse = skyflowClient.vault("").get(getByIdRequest); // Replace with actual Vault ID + System.out.println(getByIdResponse); + + // Step 3: Create another GetRequest to retrieve records by Skyflow ID with tokenized values + GetRequest getTokensRequest = GetRequest.builder() + .ids(ids) + .table("") // Replace with the actual table name + .returnTokens(true) // Set to true to return tokenized values + .build(); + + // Send the request to the Skyflow vault and retrieve the tokenized records + GetResponse getTokensResponse = skyflowClient.vault("").get(getTokensRequest); // Replace with actual Vault ID + System.out.println(getTokensResponse); + + // Step 4: Create a GetRequest to retrieve records based on specific column values + ArrayList columnValues = new ArrayList<>(); + columnValues.add(""); // Replace with the actual column value + columnValues.add(""); // Replace with the actual column value + + GetRequest getByColumnRequest = GetRequest.builder() + .table("") // Replace with the actual table name + .columnName("") // Replace with the column name + .columnValues(columnValues) // Add the list of column values to filter by + .redactionType(RedactionType.PLAIN_TEXT) // Redact data as plain text + .build(); + + // Send the request to the Skyflow vault and retrieve the records filtered by column values + GetResponse getByColumnResponse = skyflowClient.vault("").get(getByColumnRequest); // Replace with actual Vault ID + System.out.println(getByColumnResponse); + } catch (SkyflowException e) { + // Step 5: Handle any errors that occur during the retrieval process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +### Get by skyflow IDs + +Retrieve specific records using `skyflow_ids`. Ideal for fetching exact records when IDs are known. + +#### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/GetExample.java) of a get call to retrieve data using Redaction type: + +```java +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.GetRequest; +import com.skyflow.vault.data.GetResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to retrieve data from the Skyflow vault using a list of Skyflow IDs. + * + * 1. Initializes the Skyflow client with a given vault ID. + * 2. Creates a request to retrieve records based on Skyflow IDs. + * 3. Specifies that the response should not return tokens. + * 4. Uses plain text redaction type for the retrieved records. + * 5. Prints the response to display the retrieved records. + */ +public class GetExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of Skyflow IDs (replace with actual Skyflow IDs) + ArrayList ids = new ArrayList<>(); + ids.add("a581d205-1969-4350-acbe-a2a13eb871a6"); // Replace with actual Skyflow ID + ids.add("5ff887c3-b334-4294-9acc-70e78ae5164a"); // Replace with actual Skyflow ID + + // Step 2: Create a GetRequest to retrieve records based on Skyflow IDs + // The request specifies: + // - `ids`: The list of Skyflow IDs to retrieve + // - `table`: The table from which the records will be retrieved + // - `returnTokens`: Set to false, meaning tokens will not be returned in the response + // - `redactionType`: Set to PLAIN_TEXT, meaning the retrieved records will have data redacted as plain text + GetRequest getByIdRequest = GetRequest.builder() + .ids(ids) + .table("table1") // Replace with the actual table name + .returnTokens(false) // Set to false to avoid returning tokens + .redactionType(RedactionType.PLAIN_TEXT) // Redact data as plain text + .build(); + + // Step 3: Send the request to the Skyflow vault and retrieve the records + GetResponse getByIdResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").get(getByIdRequest); // Replace with actual Vault ID + System.out.println(getByIdResponse); // Print the response to the console + + } catch (SkyflowException e) { + // Step 4: Handle any errors that occur during the data retrieval process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "data": [ + { + "card_number": "4555555555555553", + "email": "john.doe@gmail.com", + "name": "john doe", + "skyflowId": "a581d205-1969-4350-acbe-a2a13eb871a6" + }, + { + "card_number": "4555555555555559", + "email": "jane.doe@gmail.com", + "name": "jane doe", + "skyflowId": "5ff887c3-b334-4294-9acc-70e78ae5164a" + } + ], + "errors": [] +} +``` + +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + +### Get tokens + +Return tokens for records. Ideal for securely processing sensitive data while maintaining data privacy. + +#### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/getExample.java) of get call to retrieve tokens using Skyflow IDs: + +```java +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.GetRequest; +import com.skyflow.vault.data.GetResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to retrieve data from the Skyflow vault and return tokens along with the records. + * + * 1. Initializes the Skyflow client with a given vault ID. + * 2. Creates a request to retrieve records based on Skyflow IDs and ensures tokens are returned. + * 3. Prints the response to display the retrieved records along with the tokens. + */ +public class GetExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of Skyflow IDs (replace with actual Skyflow IDs) + ArrayList ids = new ArrayList<>(); + ids.add("a581d205-1969-4350-acbe-a2a13eb871a6"); // Replace with actual Skyflow ID + ids.add("5ff887c3-b334-4294-9acc-70e78ae5164a"); // Replace with actual Skyflow ID + + // Step 2: Create a GetRequest to retrieve records based on Skyflow IDs + // The request specifies: + // - `ids`: The list of Skyflow IDs to retrieve + // - `table`: The table from which the records will be retrieved + // - `returnTokens`: Set to true, meaning tokens will be included in the response + GetRequest getTokensRequest = GetRequest.builder() + .ids(ids) + .table("table1") // Replace with the actual table name + .returnTokens(true) // Set to true to include tokens in the response + .build(); + + // Step 3: Send the request to the Skyflow vault and retrieve the records with tokens + GetResponse getTokensResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").get(getTokensRequest); // Replace with actual Vault ID + System.out.println(getTokensResponse); // Print the response to the console + + } catch (SkyflowException e) { + // Step 4: Handle any errors that occur during the data retrieval process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "data": [ + { + "card_number": "3998-2139-0328-0697", + "email": "c9a6c9555060@82c092e7.bd52", + "name": "82c092e7-74c0-4e60-bd52-c9a6c9555060", + "skyflowId": "a581d205-1969-4350-acbe-a2a13eb871a6" + }, + { + "card_number": "3562-0140-8820-7499", + "email": "6174366e2bc6@59f82e89.93fc", + "name": "59f82e89-138e-4f9b-93fc-6174366e2bc6", + "skyflowId": "5ff887c3-b334-4294-9acc-70e78ae5164a" + } + ], + "errors": [] +} +``` + +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + +### Get By column name and column values + +Retrieve records by unique column values. Ideal for querying data without knowing Skyflow IDs, using alternate unique identifiers. + +#### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/GetExample.java) of get call to retrieve data using column name and column values: + +```java +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.GetRequest; +import com.skyflow.vault.data.GetResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to retrieve data from the Skyflow vault based on column values. + * + * 1. Initializes the Skyflow client with a given vault ID. + * 2. Creates a request to retrieve records based on specific column values (e.g., email addresses). + * 3. Prints the response to display the retrieved records after redacting sensitive data based on the specified redaction type. + */ +public class GetExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Initialize a list of column values (email addresses in this case) + ArrayList columnValues = new ArrayList<>(); + columnValues.add("john.doe@gmail.com"); // Example email address + columnValues.add("jane.doe@gmail.com"); // Example email address + + // Step 2: Create a GetRequest to retrieve records based on column values + // The request specifies: + // - `table`: The table from which the records will be retrieved + // - `columnName`: The column to filter the records by (e.g., "email") + // - `columnValues`: The list of values to match in the specified column + // - `redactionType`: Defines how sensitive data should be redacted (set to PLAIN_TEXT here) + GetRequest getByColumnRequest = GetRequest.builder() + .table("table1") // Replace with the actual table name + .columnName("email") // The column name to filter by (e.g., "email") + .columnValues(columnValues) // The list of column values to match + .redactionType(RedactionType.PLAIN_TEXT) // Set the redaction type (e.g., PLAIN_TEXT) + .build(); + + // Step 3: Send the request to the Skyflow vault and retrieve the records + GetResponse getByColumnResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").get(getByColumnRequest); // Replace with actual Vault ID + System.out.println(getByColumnResponse); // Print the response to the console + + } catch (SkyflowException e) { + // Step 4: Handle any errors that occur during the data retrieval process + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "data": [ + { + "card_number": "4555555555555553", + "email": "john.doe@gmail.com", + "name": "john doe", + "skyflowId": "a581d205-1969-4350-acbe-a2a13eb871a6" + }, + { + "card_number": "4555555555555559", + "email": "jane.doe@gmail.com", + "name": "jane doe", + "skyflowId": "5ff887c3-b334-4294-9acc-70e78ae5164a" + } + ], + "errors": [] +} +``` + +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + +### Redaction types + +See [`RedactionType`](../docs/api_reference.md#redactiontype) in the API Reference for all available values and their descriptions. + +## Update + +To update data in your vault, use the `update` method. [`UpdateRequest`](../docs/api_reference.md#updaterequest) accepts the table name, data map, optional tokens, `returnTokens`, and `tokenMode`. Returns an [`UpdateResponse`](../docs/api_reference.md#updateresponse) with the `skyflow_id` and (when `returnTokens=true`) a token per updated column. + +### Construct an update request + +```java +import com.skyflow.enums.TokenMode; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.UpdateRequest; +import com.skyflow.vault.data.UpdateResponse; + +import java.util.HashMap; + +/** + * This example demonstrates how to update records in the Skyflow vault by providing new data and/or tokenized values, along with corresponding UpdateRequest schema. + * + */ +public class UpdateSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Prepare the data to update in the vault + // Use a HashMap to store the data that will be updated in the specified table + HashMap data = new HashMap<>(); + data.put("skyflow_id", ""); // Skyflow ID for identifying the record to update + data.put("", ""); // Example of a column name and its value to update + data.put("", ""); // Another example of a column name and its value to update + + // Step 2: Prepare the tokens (if necessary) for certain columns that require tokenization + // Use a HashMap to specify columns that need tokens in the update request + HashMap tokens = new HashMap<>(); + tokens.put("", ""); // Example of a column name that should be tokenized + + // Step 3: Create an UpdateRequest to specify the update operation + // The request includes the table name, token mode, data, tokens, and the returnTokens flag + UpdateRequest updateRequest = UpdateRequest.builder() + .table("") // Replace with the actual table name to update + .tokenMode(TokenMode.ENABLE) // Specifies the tokenization mode (ENABLE means tokenization is applied) + .data(data) // The data to update in the record + .tokens(tokens) // The tokens associated with specific columns + .returnTokens(true) // Specify whether to return tokens in the response + .build(); + + // Step 4: Send the request to the Skyflow vault and update the record + UpdateResponse updateResponse = skyflowClient.vault("").update(updateRequest); // Replace with actual Vault ID + System.out.println(updateResponse); // Print the response to confirm the update result + + } catch (SkyflowException e) { + // Step 5: Handle any errors that occur during the update operation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/UpdateExample.java) of update call + +```java +import com.skyflow.enums.TokenMode; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.UpdateRequest; +import com.skyflow.vault.data.UpdateResponse; + +import java.util.HashMap; + +/** + * This example demonstrates how to update a record in the Skyflow vault with specified data and tokens. + * + * 1. Initializes the Skyflow client with a given vault ID. + * 2. Constructs an update request with data to modify and tokens to include. + * 3. Sends the request to update the record in the vault. + * 4. Prints the response to confirm the success or failure of the update operation. + */ +public class UpdateExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Prepare the data to update in the vault + // A HashMap is used to store the data that will be updated in the specified table + HashMap data = new HashMap<>(); + data.put("skyflow_id", "5b699e2c-4301-4f9f-bcff-0a8fd3057413"); // Skyflow ID identifies the record to update + data.put("name", "john doe"); // Updating the "name" column with a new value + data.put("card_number", "4111111111111115"); // Updating the "card_number" column with a new value + + // Step 2: Prepare the tokens to include in the update request + // Tokens can be included to update sensitive data with tokenized values + HashMap tokens = new HashMap<>(); + tokens.put("name", "72b8ffe3-c8d3-4b4f-8052-38b2a7405b5a"); // Tokenized value for the "name" column + + // Step 3: Create an UpdateRequest to define the update operation + // The request specifies the table name, token mode, data, and tokens for the update + UpdateRequest updateRequest = UpdateRequest.builder() + .table("table1") // Replace with the actual table name to update + .tokenMode(TokenMode.ENABLE) // Token mode enabled to allow tokenization of sensitive data + .data(data) // The data to update in the record + .tokens(tokens) // The tokenized values for sensitive columns + .build(); + + // Step 4: Send the update request to the Skyflow vault + UpdateResponse updateResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").update(updateRequest); // Replace with your actual Vault ID + System.out.println(updateResponse); // Print the response to confirm the update result + + } catch (SkyflowException e) { + // Step 5: Handle any exceptions that occur during the update operation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception stack trace for debugging purposes + } + } +} +``` + +Sample response: + +- When `returnTokens` is set to `true` + +```json +{ + "skyflowId": "5b699e2c-4301-4f9f-bcff-0a8fd3057413", + "name": "72b8ffe3-c8d3-4b4f-8052-38b2a7405b5a", + "card_number": "4315-7650-1359-9681" +} +``` + +- When `returnTokens` is set to `false` + +```json +{ + "skyflowId": "5b699e2c-4301-4f9f-bcff-0a8fd3057413" +} +``` + +## Delete + +To delete records using Skyflow IDs, use the `delete` method. [`DeleteRequest`](../docs/api_reference.md#deleterequest) accepts a table name and list of Skyflow IDs. Returns a [`DeleteResponse`](../docs/api_reference.md#deleteresponse). + +### Construct a delete request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.DeleteRequest; +import com.skyflow.vault.data.DeleteResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to delete records from a Skyflow vault using specified Skyflow IDs, along with corresponding DeleteRequest schema. + * + */ +public class DeleteSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Prepare a list of Skyflow IDs for the records to delete + // The list stores the Skyflow IDs of the records that need to be deleted from the vault + ArrayList ids = new ArrayList<>(); + ids.add(""); // Replace with actual Skyflow ID 1 + ids.add(""); // Replace with actual Skyflow ID 2 + ids.add(""); // Replace with actual Skyflow ID 3 + + // Step 2: Create a DeleteRequest to define the delete operation + // The request specifies the table from which to delete the records and the IDs of the records to delete + DeleteRequest deleteRequest = DeleteRequest.builder() + .ids(ids) // List of Skyflow IDs to delete + .table("") // Replace with the actual table name from which to delete + .build(); + + // Step 3: Send the delete request to the Skyflow vault + DeleteResponse deleteResponse = skyflowClient.vault("").delete(deleteRequest); // Replace with your actual Vault ID + System.out.println(deleteResponse); // Print the response to confirm the delete result + + } catch (SkyflowException e) { + // Step 4: Handle any exceptions that occur during the delete operation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception stack trace for debugging purposes + } + } +} +``` + +### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/DeleteExample.java) of delete call: + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.DeleteRequest; +import com.skyflow.vault.data.DeleteResponse; + +import java.util.ArrayList; + +/** + * This example demonstrates how to delete records from a Skyflow vault using specified Skyflow IDs. + * + * 1. Initializes the Skyflow client with a given Vault ID. + * 2. Constructs a delete request by specifying the IDs of the records to delete. + * 3. Sends the delete request to the Skyflow vault to delete the specified records. + * 4. Prints the response to confirm the success or failure of the delete operation. + */ +public class DeleteExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Prepare a list of Skyflow IDs for the records to delete + // The list stores the Skyflow IDs of the records that need to be deleted from the vault + ArrayList ids = new ArrayList<>(); + ids.add("9cbf66df-6357-48f3-b77b-0f1acbb69280"); // Replace with actual Skyflow ID 1 + ids.add("ea74bef4-f27e-46fe-b6a0-a28e91b4477b"); // Replace with actual Skyflow ID 2 + ids.add("47700796-6d3b-4b54-9153-3973e281cafb"); // Replace with actual Skyflow ID 3 + + // Step 2: Create a DeleteRequest to define the delete operation + // The request specifies the table from which to delete the records and the IDs of the records to delete + DeleteRequest deleteRequest = DeleteRequest.builder() + .ids(ids) // List of Skyflow IDs to delete + .table("table1") // Replace with the actual table name from which to delete + .build(); + + // Step 3: Send the delete request to the Skyflow vault + DeleteResponse deleteResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").delete(deleteRequest); // Replace with your actual Vault ID + System.out.println(deleteResponse); // Print the response to confirm the delete result + + } catch (SkyflowException e) { + // Step 4: Handle any exceptions that occur during the delete operation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception stack trace for debugging purposes + } + } +} +``` + +Sample response: + +```json +{ + "deletedIds": [ + "9cbf66df-6357-48f3-b77b-0f1acbb69280", + "ea74bef4-f27e-46fe-b6a0-a28e91b4477b", + "47700796-6d3b-4b54-9153-3973e281cafb" + ] +} +``` + +## Query + +To retrieve data with SQL queries, use the `query` method. [`QueryRequest`](../docs/api_reference.md#queryrequest) accepts a `query` string. Returns a [`QueryResponse`](../docs/api_reference.md#queryresponse). + +### Construct a query request + +Refer to [Query your data](https://docs.skyflow.com/query-data/) and [Execute Query](https://docs.skyflow.com/record/#QueryService_ExecuteQuery) for guidelines and restrictions on supported SQL statements, operators, and keywords. + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.QueryRequest; +import com.skyflow.vault.data.QueryResponse; + +/** + * This example demonstrates how to execute a custom SQL query on a Skyflow vault, along with QueryRequest schema. + * + */ +public class QuerySchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Define the SQL query to execute on the Skyflow vault + // Replace "" with the actual SQL query you want to run + String query = ""; // Example: "SELECT * FROM table1 WHERE column1 = 'value'" + + // Step 2: Create a QueryRequest with the specified SQL query + QueryRequest queryRequest = QueryRequest.builder() + .query(query) // SQL query to execute + .build(); + + // Step 3: Execute the query request on the specified Skyflow vault + QueryResponse queryResponse = skyflowClient.vault("").query(queryRequest); // Replace with your actual Vault ID + System.out.println(queryResponse); // Print the response containing the query results + + } catch (SkyflowException e) { + // Step 4: Handle any exceptions that occur during the query execution + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception stack trace for debugging + } + } +} +``` + +### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/QueryExample.java) of query call + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.QueryRequest; +import com.skyflow.vault.data.QueryResponse; + +/** + * This example demonstrates how to execute a SQL query on a Skyflow vault to retrieve data. + * + * 1. Initializes the Skyflow client with the Vault ID. + * 2. Constructs a query request with a specified SQL query. + * 3. Executes the query against the Skyflow vault. + * 4. Prints the response from the query execution. + */ +public class QueryExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Define the SQL query + // Example query: Retrieve all records from the "cards" table with a specific skyflow_id + String query = "SELECT * FROM cards WHERE skyflow_id='3ea3861-x107-40w8-la98-106sp08ea83f'"; + + // Step 2: Create a QueryRequest with the SQL query + QueryRequest queryRequest = QueryRequest.builder() + .query(query) // SQL query to execute + .build(); + + // Step 3: Execute the query request on the specified Skyflow vault + QueryResponse queryResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").query(queryRequest); // Vault ID: 9f27764a10f7946fe56b3258e117 + System.out.println(queryResponse); // Print the query response (contains query results) + + } catch (SkyflowException e) { + // Step 4: Handle any exceptions that occur during the query execution + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception stack trace for debugging + } + } +} +``` + +Sample response: + +```json +{ + "fields": [ + { + "card_number": "XXXXXXXXXXXX1112", + "name": "S***ar", + "skyflowId": "3ea3861-x107-40w8-la98-106sp08ea83f", + "tokenizedData": null + } + ] +} +``` + +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + +## Upload File + +To upload files to a Skyflow vault, use the `uploadFile` method. [`FileUploadRequest`](../docs/api_reference.md#fileuploadrequest) accepts the table name, column name, optional skyflow ID, and a file source (`fileObject`, `filePath`, or `base64`). Returns a [`FileUploadResponse`](../docs/api_reference.md#fileuploadresponse). + +### Construct a file upload request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.FileUploadRequest; +import com.skyflow.vault.data.FileUploadResponse; + +/** + * This example demonstrates how to upload a file to a Skyflow vault, along with the UploadFileRequest schema. + * + */ +public class UploadFileSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Specify file Object + File file = new File(""); + + // Step 2: Create an UploadFileRequest with the file details + FileUploadRequest uploadFileRequest = FileUploadRequest.builder() + .fileObject(file) // File object + .table("") // Vault table to upload into + .columnName("") // Column to assign to the uploaded file + .skyflowId("") // Skyflow id of the record + .build(); + + // Step 3: Execute the file upload request on the specified Skyflow vault + FileUploadResponse fileUploadResponse = skyflowClient.vault().uploadFile(uploadFileRequest); + System.out.println("File Upload Response: " + fileUploadResponse); + + } catch (SkyflowException e) { + // Step 4: Handle any exceptions that occur during the upload + System.out.println("Error occurred during file upload:"); + e.printStackTrace(); // Print the exception stack trace for debugging + } + } +} + +``` + +### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/vault/FileUploadExample.java) of file upload call +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.FileUploadRequest; +import com.skyflow.vault.data.FileUploadResponse; + +/** + * This example demonstrates how to upload a file to a Skyflow vault. + * + * 1. Initializes the Skyflow client with the Vault ID. + * 2. Constructs a file upload request with the file path, table name, and file name. + * 3. Executes the upload request against the Skyflow vault. + * 4. Prints the response from the upload. + */ +public class UploadFileExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Specify file Object + File file = new File("test/sample.txt"); + + // Step 2: Create an UploadFileRequest with the file details + FileUploadRequest uploadFileRequest = FileUploadRequest.builder() + .fileObject(file) // File object + .table("cards") // Vault table to upload into + .columnName("file") // Column to assign to the uploaded file + .skyflowId("c9312531-2087-439a-bd26-74c41f24db83") // Skyflow id of the record + .build(); + + // Step 3: Execute the file upload request + FileUploadResponse uploadResponse = skyflowClient.vault("9f27764a10f7946fe56b3258e117").uploadFile(uploadFileRequest); + System.out.println("File Upload Response: " + fileUploadResponse); + + } catch (SkyflowException e) { + // Step 4: Handle any exceptions during the upload + System.out.println("Error occurred during file upload:"); + e.printStackTrace(); // Print exception details for debugging + } + } +} + +``` + +Sample response: + +```json +{ + "skyflowId": "c9312531-2087-439a-bd26-74c41f24db83", + "errors": null +} +``` + +# Detect +Skyflow Detect enables you to deidentify and reidentify sensitive data in text and files, supporting advanced privacy-preserving workflows. + +`DetectController` is the class returned by `skyflowClient.detect()` and `skyflowClient.detect(vaultId)`. + +```java +// Uses the default (first configured) vault +DetectController detect = skyflowClient.detect(); + +// Uses a specific vault by ID +DetectController detect = skyflowClient.detect(""); +``` + +**Methods:** + +| Method | Parameters | Returns | Description | +|--------|-----------|---------|-------------| +| `deidentifyText(DeidentifyTextRequest)` | [`DeidentifyTextRequest`](../docs/api_reference.md#deidentifytextrequest) | [`DeidentifyTextResponse`](../docs/api_reference.md#deidentifytextresponse) | Deidentify sensitive entities in text | +| `reidentifyText(ReidentifyTextRequest)` | [`ReidentifyTextRequest`](../docs/api_reference.md#reidentifytextrequest) | [`ReidentifyTextResponse`](../docs/api_reference.md#reidentifytextresponse) | Restore original values from a deidentified text | +| `deidentifyFile(DeidentifyFileRequest)` | [`DeidentifyFileRequest`](../docs/api_reference.md#deidentifyfilerequest) | [`DeidentifyFileResponse`](../docs/api_reference.md#deidentifyfileresponse) | Deidentify sensitive data in a file | +| `getDetectRun(GetDetectRunRequest)` | [`GetDetectRunRequest`](../docs/api_reference.md#getdetectrunrequest) | [`DeidentifyFileResponse`](../docs/api_reference.md#deidentifyfileresponse) | Poll for the result of an async file deidentification | + +## Deidentify Text +To deidentify text, use the `deidentifyText` method. [`DeidentifyTextRequest`](../docs/api_reference.md#deidentifytextrequest) accepts the text to deidentify along with optional entity types, regex lists, token format, and transformations. Returns a [`DeidentifyTextResponse`](../docs/api_reference.md#deidentifytextresponse). + +### Construct an deidentify text request + +```java +import com.skyflow.enums.DetectEntities; +import com.skyflow.vault.detect.DateTransformation; +import com.skyflow.vault.detect.DeidentifyTextRequest; +import com.skyflow.vault.detect.TokenFormat; +import com.skyflow.vault.detect.Transformations; +import com.skyflow.vault.detect.DeidentifyTextResponse; + +import java.util.ArrayList; +import java.util.List; + +/** + * This example demonstrate to build deidentify text request. + */ +public class DeidentifyTextSchema { + + public static void main(String[] args) { + + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + + // Step 2: Configure the options for deidentify text + + // Replace with the entity you want to detect + List detectEntitiesList = new ArrayList<>(); + detectEntitiesList.add(DetectEntities.SSN); + + // Replace with the entity you want to detect with vault token + List vaultTokenList = new ArrayList<>(); + vaultTokenList.add(DetectEntities.CREDIT_CARD); + + // Replace with the entity you want to detect with entity only + List entityOnlyList = new ArrayList<>(); + entityOnlyList.add(DetectEntities.SSN); + + // Replace with the entity you want to detect with entity unique counter + List entityUniqueCounterList = new ArrayList<>(); + entityUniqueCounterList.add(DetectEntities.SSN); + + // Replace with the regex patterns you want to allow during deidentification + List allowRegexList = new ArrayList<>(); + allowRegexList.add(""); + + // Replace with the regex patterns you want to restrict during deidentification + List restrictRegexList = new ArrayList<>(); + restrictRegexList.add("YOUR_RESTRICT_REGEX_LIST"); + + // Configure Token Format + TokenFormat tokenFormat = TokenFormat.builder() + .vaultToken(vaultTokenList) + .entityOnly(entityOnlyList) + .entityUniqueCounter(entityUniqueCounterList) + .build(); + + // Configure Transformation + List detectEntitiesTransformationList = new ArrayList<>(); + detectEntitiesTransformationList.add(DetectEntities.DOB); // Replace with the entity you want to transform + + DateTransformation dateTransformation = new DateTransformation(20, 5, detectEntitiesTransformationList); + Transformations transformations = new Transformations(dateTransformation); + + // Step 3: Create a deidentify text request for the vault + DeidentifyTextRequest deidentifyTextRequest = DeidentifyTextRequest.builder() + .text("") // Replace with the text you want to deidentify + .entities(detectEntitiesList) + .allowRegexList(allowRegexList) + .restrictRegexList(restrictRegexList) + .tokenFormat(tokenFormat) + .transformations(transformations) + .build(); + + // Step 4: Use the Skyflow client to perform the deidentifyText operation + // Replace with your actual vault ID + DeidentifyTextResponse deidentifyTextResponse = skyflowClient.detect("").deidentifyText(deidentifyTextRequest); + + // Step 5: Print the response + System.out.println("Deidentify text Response: " + deidentifyTextResponse); + } +} + +``` + +## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/DeidentifyTextExample.java) of deidentify text: +```java +import java.util.ArrayList; +import java.util.List; + +import com.skyflow.enums.DetectEntities; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.detect.DateTransformation; +import com.skyflow.vault.detect.DeidentifyTextRequest; +import com.skyflow.vault.detect.DeidentifyTextResponse; +import com.skyflow.vault.detect.TokenFormat; +import com.skyflow.vault.detect.Transformations; + +/** + * Skyflow Deidentify Text Example + *

+ * This example demonstrates how to use the Skyflow SDK to deidentify text data + * across multiple vaults. It includes: + * 1. Setting up credentials and vault configurations. + * 2. Creating a Skyflow client with multiple vaults. + * 3. Performing deidentify of text with various options. + * 4. Handling responses and errors. + */ + +public class DeidentifyTextExample { + public static void main(String[] args) throws SkyflowException { + + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + + // Step 2: Configuring the different options for deidentify + + // Replace with the entity you want to detect + List detectEntitiesList = new ArrayList<>(); + detectEntitiesList.add(DetectEntities.SSN); + detectEntitiesList.add(DetectEntities.CREDIT_CARD); + + // Replace with the entity you want to detect with vault token + List vaultTokenList = new ArrayList<>(); + vaultTokenList.add(DetectEntities.SSN); + vaultTokenList.add(DetectEntities.CREDIT_CARD); + + // Configure Token Format + TokenFormat tokenFormat = TokenFormat.builder() + .vaultToken(vaultTokenList) + .build(); + + // Configure Transformation for deidentified entities + List detectEntitiesTransformationList = new ArrayList<>(); + detectEntitiesTransformationList.add(DetectEntities.DOB); // Replace with the entity you want to transform + + DateTransformation dateTransformation = new DateTransformation(20, 5, detectEntitiesTransformationList); + Transformations transformations = new Transformations(dateTransformation); + + // Step 3: invoking Deidentify text on the vault + try { + // Create a deidentify text request for the vault + DeidentifyTextRequest deidentifyTextRequest = DeidentifyTextRequest.builder() + .text("My SSN is 123-45-6789 and my card is 4111 1111 1111 1111.") // Replace with your deidentify text + .entities(detectEntitiesList) + .tokenFormat(tokenFormat) + .transformations(transformations) + .build(); + // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id + DeidentifyTextResponse deidentifyTextResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").deidentifyText(deidentifyTextRequest); + + System.out.println("Deidentify text Response: " + deidentifyTextResponse); + } catch (SkyflowException e) { + System.err.println("Error occurred during deidentify: "); + e.printStackTrace(); // Print the exception for debugging purposes + } + } +} +``` + +Sample Response: +```json +{ + "processedText": "My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].", + "entities": [ + { + "token": "SSN_IWdexZe", + "value": "123-45-6789", + "textIndex": { + "start": 10, + "end": 21 + }, + "processedIndex": { + "start": 10, + "end": 23 + }, + "entity": "SSN", + "scores": { + "SSN": 0.9384 + } + }, + { + "token": "CREDIT_CARD_rUzMjdQ", + "value": "4111 1111 1111 1111", + "textIndex": { + "start": 37, + "end": 56 + }, + "processedIndex": { + "start": 39, + "end": 60 + }, + "entity": "CREDIT_CARD", + "scores": { + "CREDIT_CARD": 0.9051 + } + } + ], + "wordCount": 9, + "charCount": 57 +} +``` + +## Reidentify Text +To reidentify text, use the `reidentifyText` method. [`ReidentifyTextRequest`](../docs/api_reference.md#reidentifytextrequest) accepts the redacted/deidentified text and optional entity lists controlling which entities to reveal, mask, or keep redacted. Returns a [`ReidentifyTextResponse`](../docs/api_reference.md#reidentifytextresponse). + +### Construct an reidentify text request + +```java +import com.skyflow.enums.DetectEntities; +import com.skyflow.vault.detect.ReidentifyTextRequest; +import com.skyflow.vault.detect.ReidentifyTextResponse; + +import java.util.ArrayList; +import java.util.List; + +/** + * This example demonstrates how to build a reidentify text request. + */ +public class ReidentifyTextSchema { + public static void main(String[] args) { + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + + // Step 2: Configuring the different options for reidentify + List maskedEntity = new ArrayList<>(); + maskedEntity.add(DetectEntities.CREDIT_CARD); // Replace with the entity you want to mask + + List plainTextEntity = new ArrayList<>(); + plainTextEntity.add(DetectEntities.SSN); // Replace with the entity you want to keep in plain text + + // List redactedEntity = new ArrayList<>(); + // redactedEntity.add(DetectEntities.SSN); // Replace with the entity you want to redact + + + // Step 3: Create a reidentify text request with the configured entities + ReidentifyTextRequest reidentifyTextRequest = ReidentifyTextRequest.builder() + .text("My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].") // Replace with your deidentify text + .maskedEntities(maskedEntity) +// .redactedEntities(redactedEntity) + .plainTextEntities(plainTextEntity) + .build(); + + // Step 4: Invoke reidentify text on the vault + ReidentifyTextResponse reidentifyTextResponse = skyflowClient.detect("").reidentifyText(reidentifyTextRequest); + System.out.println("Reidentify text Response: " + reidentifyTextResponse); + } +} +``` + +## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/ReidentifyTextExample.java) of Reidentify text + +```java +import com.skyflow.enums.DetectEntities; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.detect.ReidentifyTextRequest; +import com.skyflow.vault.detect.ReidentifyTextResponse; + +import java.util.ArrayList; +import java.util.List; + +/** + * Skyflow Reidentify Text Example + *

+ * This example demonstrates how to use the Skyflow SDK to reidentify text data + * across multiple vaults. It includes: + * 1. Setting up credentials and vault configurations. + * 2. Creating a Skyflow client with multiple vaults. + * 3. Performing reidentify of text with various options. + * 4. Handling responses and errors. + */ + +public class ReidentifyTextExample { + public static void main(String[] args) throws SkyflowException { + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + + // Step 2: Configuring the different options for reidentify + List maskedEntity = new ArrayList<>(); + maskedEntity.add(DetectEntities.CREDIT_CARD); // Replace with the entity you want to mask + + List plainTextEntity = new ArrayList<>(); + plainTextEntity.add(DetectEntities.SSN); // Replace with the entity you want to keep in plain text + + try { + // Step 3: Create a reidentify text request with the configured options + ReidentifyTextRequest reidentifyTextRequest = ReidentifyTextRequest.builder() + .text("My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].") // Replace with your deidentify text + .maskedEntities(maskedEntity) + .plainTextEntities(plainTextEntity) + .build(); + + // Step 4: Invoke Reidentify text on the vault + // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id + ReidentifyTextResponse reidentifyTextResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").reidentifyText(reidentifyTextRequest); + + // Handle the response from the reidentify text request + System.out.println("Reidentify text Response: " + reidentifyTextResponse); + } catch (SkyflowException e) { + System.err.println("Error occurred during reidentify : "); + e.printStackTrace(); + } + } +} +``` + +Sample Response: + +```json +{ + "processedText":"My SSN is 123-45-6789 and my card is XXXXX1111." +} +``` + +## Deidentify file +To deidentify files, use the `deidentifyFile` method. [`DeidentifyFileRequest`](../docs/api_reference.md#deidentifyfilerequest) accepts a [`FileInput`](../docs/api_reference.md#fileinput) and optional parameters controlling entity detection, masking, output format, and async wait time. Supports images, PDFs, audio, documents, spreadsheets, and presentations. Returns a [`DeidentifyFileResponse`](../docs/api_reference.md#deidentifyfileresponse). + +### AudioBleep + +[`AudioBleep`](../docs/api_reference.md#audiobleep) controls how detected sensitive audio segments are replaced with a bleep tone. Used in `DeidentifyFileRequest.builder().bleep(audioBleep)` for audio files. + +```java +import com.skyflow.vault.detect.AudioBleep; + +AudioBleep audioBleep = AudioBleep.builder() + .frequency(1000D) // bleep tone frequency in Hz + .gain(0.5D) // bleep tone gain (volume level) + .startPadding(0.2D) // silence padding before the bleep (seconds) + .stopPadding(0.2D) // silence padding after the bleep (seconds) + .build(); +``` + +### Construct an deidentify file request + +```java +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.enums.MaskingMethod; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.detect.DeidentifyFileRequest; +import com.skyflow.vault.detect.DeidentifyFileResponse; + +import java.io.File; + +/** + * This example demonstrates how to build a deidentify file request. + */ + +public class DeidentifyFileSchema { + + public static void main(String[] args) { + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + + // Step 2: Create a deidentify file request with all options + + // Create file object + File file = new File(""); // Replace with the path to the file you want to deidentify + + // Create file input using the file object + FileInput fileInput = FileInput.builder() + .file(file) + // .filePath("") // Alternatively, you can use .filePath() + .build(); + + // Output configuration + String outputDirectory = ""; // Replace with the desired output directory to save the deidentified file + + // Entities to detect + // List detectEntities = new ArrayList<>(); + // detectEntities.add(DetectEntities.IP_ADDRESS); // Replace with the entities you want to detect + + // Image-specific options + // Boolean outputProcessedImage = true; // Include processed image in output + // Boolean outputOcrText = true; // Include OCR text in output + MaskingMethod maskingMethod = MaskingMethod.BLACKBOX; // Masking method for images + + // PDF-specific options + // Integer pixelDensity = 15; // Pixel density for PDF processing + // Integer maxResolution = 2000; // Max resolution for PDF + + // Audio-specific options + // Boolean outputProcessedAudio = true; // Include processed audio + // DetectOutputTranscriptions outputTanscription = DetectOutputTranscriptions.PLAINTEXT_TRANSCRIPTION; // Transcription type + + // Audio bleep configuration + // AudioBleep audioBleep = AudioBleep.builder() + // .frequency(5D) // Pitch in Hz + // .startPadding(7D) // Padding at start (seconds) + // .stopPadding(8D) // Padding at end (seconds) + // .build(); + + Integer waitTime = 20; // Max wait time for response (max 64 seconds) + + DeidentifyFileRequest deidentifyFileRequest = DeidentifyFileRequest.builder() + .file(fileInput) + .waitTime(waitTime) + .entities(detectEntities) + .outputDirectory(outputDirectory) + .maskingMethod(maskingMethod) + // .outputProcessedImage(outputProcessedImage) + // .outputOcrText(outputOcrText) + // .pixelDensity(pixelDensity) + // .maxResolution(maxResolution) + // .outputProcessedAudio(outputProcessedAudio) + // .outputTranscription(outputTanscription) + // .bleep(audioBleep) + .build(); + + + DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("").deidentifyFile(deidentifyFileRequest); + System.out.println("Deidentify file response: " + deidentifyFileResponse.toString()); + } +} +``` + +## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/DeidentifyFileExample.java) of Deidentify file + +```java +import java.io.File; + +import com.skyflow.enums.MaskingMethod; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.detect.DeidentifyFileRequest; +import com.skyflow.vault.detect.DeidentifyFileResponse; + +/** + * Skyflow Deidentify File Example + *

+ * This example demonstrates how to use the Skyflow SDK to deidentify file + * It has all available options for deidentifying files. + * Supported file types: images (jpg, png, etc.), pdf, audio (mp3, wav), documents, spreadsheets, presentations, structured text. + * It includes: + * 1. Configure credentials + * 2. Set up vault configuration + * 3. Create a deidentify file request with all options + * 4. Call deidentifyFile to deidentify file. + * 5. Handle response and errors + */ +public class DeidentifyFileExample { + + public static void main(String[] args) throws SkyflowException { + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + try { + // Step 2: Create a deidentify file request with all options + + + // Create file object + File file = new File("sensitive-folder/personal-info.txt"); // Replace with the path to the file you want to deidentify + + // Create file input using the file object + FileInput fileInput = FileInput.builder() + .file(file) + // .filePath("") // Alternatively, you can use .filePath() + .build(); + + // Output configuration + String outputDirectory = "deidentified-file/"; // Replace with the desired output directory to save the deidentified file + + // Entities to detect + // List detectEntities = new ArrayList<>(); + // detectEntities.add(DetectEntities.IP_ADDRESS); // Replace with the entities you want to detect + + // Image-specific options + // Boolean outputProcessedImage = true; // Include processed image in output + // Boolean outputOcrText = true; // Include OCR text in output + MaskingMethod maskingMethod = MaskingMethod.BLACKBOX; // Masking method for images + + Integer waitTime = 20; // Max wait time for response (max 64 seconds) + + DeidentifyFileRequest deidentifyFileRequest = DeidentifyFileRequest.builder() + .file(fileInput) + .waitTime(waitTime) + .outputDirectory(outputDirectory) + .maskingMethod(maskingMethod) + .build(); + + // Step 3: Invoking deidentifyFile + // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id + DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").deidentifyFile(deidentifyFileRequest); + System.out.println("Deidentify file response: " + deidentifyFileResponse.toString()); + } catch (SkyflowException e) { + System.err.println("Error occurred during deidentify file: "); + e.printStackTrace(); + } + } +} + +``` + +Sample response: + +```json +{ + "file": { + "name": "deidentified.txt", + "size": 33, + "type": "", + "lastModified": 1751355183039 + }, + "fileBase64": "bXkgY2FyZCBudW1iZXIgaXMgW0NSRURJVF", + "type": "redacted_file", + "extension": "txt", + "wordCount": 11, + "charCount": 61, + "sizeInKb": 0, + "entities": [ + { + "file": "bmFtZTogW05BTUVfMV0gCm==", + "type": "entities", + "extension": "json" + } + ], + "runId": "undefined", + "status": "success" +} + +``` + +**Supported file types:** +- Documents: `doc`, `docx`, `pdf` +- PDFs: `pdf` +- Images: `bmp`, `jpeg`, `jpg`, `png`, `tif`, `tiff` +- Structured text: `json`, `xml` +- Spreadsheets: `csv`, `xls`, `xlsx` +- Presentations: `ppt`, `pptx` +- Audio: `mp3`, `wav` + +**Note:** +- Transformations cannot be applied to Documents, Images, or PDFs file formats. + +- The `waitTime` option must be ≤ 64 seconds; otherwise, an error is thrown. + +- If the API takes more than 64 seconds to process the file, it will return only the run ID in the response. + +Sample response (when the API takes more than 64 seconds): +```json +{ + "file": null, + "fileBase64": null, + "type": null, + "extension": null, + "wordCount": null, + "charCount": null, + "sizeInKb": null, + "durationInSeconds": null, + "pageCount": null, + "slideCount": null, + "entities": null, + "runId": "1273a8c6-c498-4293-a9d6-389864cd3a44", + "status": "IN_PROGRESS", + "errors": null +} +``` + +## Get run: +To retrieve the results of a previously started file deidentification operation, use the `getDetectRun` method. [`GetDetectRunRequest`](../docs/api_reference.md#getdetectrunrequest) accepts the `runId` returned from a prior `deidentifyFile` call. Returns a [`DeidentifyFileResponse`](../docs/api_reference.md#deidentifyfileresponse). + +### Construct an get run request + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.detect.DeidentifyFileResponse; +import com.skyflow.vault.detect.GetDetectRunRequest; + +/** + * Skyflow Get Detect Run Example + */ + +public class GetDetectRunSchema { + + public static void main(String[] args) { + try { + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + + // Step 2: Create a get detect run request + GetDetectRunRequest getDetectRunRequest = GetDetectRunRequest.builder() + .runId("") // Replace with the runId from deidentifyFile call + .build(); + + // Step 3: Call getDetectRun to poll for file processing results + // Replace with your actual vault ID + DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("").getDetectRun(getDetectRunRequest); + System.out.println("Get Detect Run Response: " + deidentifyFileResponse); + } catch (SkyflowException e) { + System.err.println("Error occurred during get detect run: "); + e.printStackTrace(); + } + } +} + +``` + +## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/GetDetectRunExample.java) of get run +```java +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.detect.DeidentifyFileResponse; +import com.skyflow.vault.detect.GetDetectRunRequest; + +/** + * Skyflow Get Detect Run Example + *

+ * This example demonstrates how to: + * 1. Configure credentials + * 2. Set up vault configuration + * 3. Create a get detect run request + * 4. Call getDetectRun to poll for file processing results + * 5. Handle response and errors + */ +public class GetDetectRunExample { + public static void main(String[] args) throws SkyflowException { + // Step 1: Initialize the Skyflow client by configuring the credentials & vault config. + try { + + // Step 2: Create a get detect run request + GetDetectRunRequest getDetectRunRequest = GetDetectRunRequest.builder() + .runId("e0038196-4a20-422b-bad7-e0477117f9bb") // Replace with the runId from deidentifyFile call + .build(); + + // Step 3: Call getDetectRun to poll for file processing results + // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id + DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").getDetectRun(getDetectRunRequest); + System.out.println("Get Detect Run Response: " + deidentifyFileResponse); + } catch (SkyflowException e) { + System.err.println("Error occurred during get detect run: "); + e.printStackTrace(); + } + } +} +``` + +Sample Response: + +```json +{ + "file": "bmFtZTogW05BTET0JfMV0K", + "type": "redacted_file", + "extension": "txt", + "wordCount": 11, + "charCount": 61, + "sizeInKb": 0.0, + "entities": [ + { + "file": "gW05BTUVfMV0gCmNhcmQ0K", + "type": "entities", + "extension": "json" + } + ], + "runId": "e0038196-4a20-422b-bad7-e0477117f9bb", + "status": "success" +} + +``` + +## Detect response types + +The Detect API returns structured objects for detected entities. See the API Reference for full attribute lists: [`EntityInfo`](../docs/api_reference.md#entityinfo), [`TextIndex`](../docs/api_reference.md#textindex), [`FileEntityInfo`](../docs/api_reference.md#fileentityinfo), [`FileInfo`](../docs/api_reference.md#fileinfo). + +### EntityInfo and TextIndex + +[`EntityInfo`](../docs/api_reference.md#entityinfo) appears in `DeidentifyTextResponse.getEntities()`. Each entry includes the detected entity type, original value, replacement token, character positions ([`TextIndex`](../docs/api_reference.md#textindex)), and confidence scores. + +```java +DeidentifyTextResponse response = skyflowClient.detect("").deidentifyText(request); + +for (EntityInfo entity : response.getEntities()) { + System.out.println("Entity : " + entity.getEntity()); + System.out.println("Value : " + entity.getValue()); + System.out.println("Token : " + entity.getToken()); + System.out.println("Start : " + entity.getTextIndex().getStart()); + System.out.println("End : " + entity.getTextIndex().getEnd()); + System.out.println("Score : " + entity.getScores().get(entity.getEntity())); +} +``` + +### FileEntityInfo and FileInfo + +[`FileEntityInfo`](../docs/api_reference.md#fileentityinfo) appears in `DeidentifyFileResponse.getEntities()`. [`FileInfo`](../docs/api_reference.md#fileinfo) is returned by `DeidentifyFileResponse.getFile()` and contains file metadata. + +## Detect enums + +See the API Reference for full value descriptions: [`TokenType`](../docs/api_reference.md#tokentype), [`DeidentifyFileStatus`](../docs/api_reference.md#deidentifyfilestatus), [`DetectOutputTranscriptions`](../docs/api_reference.md#detectoutputtranscriptions), [`MaskingMethod`](../docs/api_reference.md#maskingmethod), [`DetectEntities`](../docs/api_reference.md#detectentities). + +### TokenType + +[`TokenType`](../docs/api_reference.md#tokentype) controls how detected entities are tokenized. Used in `TokenFormat.builder()`. + +```java +import com.skyflow.enums.TokenType; + +TokenFormat tokenFormat = TokenFormat.builder() + .vaultToken(vaultTokenList) // uses VAULT_TOKEN + .entityOnly(entityOnlyList) // uses ENTITY_ONLY + .entityUniqueCounter(entityUniqueCounterList) // uses ENTITY_UNIQUE_COUNTER + .build(); +``` + +### DeidentifyFileStatus + +[`DeidentifyFileStatus`](../docs/api_reference.md#deidentifyfilestatus) is returned in `DeidentifyFileResponse.getStatus()` to indicate async processing state. + +```java +import com.skyflow.enums.DeidentifyFileStatus; + +DeidentifyFileResponse response = skyflowClient.detect("").getDetectRun(request); +if (DeidentifyFileStatus.SUCCESS.value().equals(response.getStatus())) { + // safe to read response.getFile() +} else if (DeidentifyFileStatus.IN_PROGRESS.value().equals(response.getStatus())) { + // poll again using the runId +} +``` + +### DetectOutputTranscriptions + +[`DetectOutputTranscriptions`](../docs/api_reference.md#detectoutputtranscriptions) controls the transcription format for audio file deidentification. + +```java +import com.skyflow.enums.DetectOutputTranscriptions; + +DeidentifyFileRequest request = DeidentifyFileRequest.builder() + .file(fileInput) + .outputTranscription(DetectOutputTranscriptions.TRANSCRIPTION) + .build(); +``` + +# Connections + +Skyflow Connections is a gateway service that uses tokenization to securely send and receive data between your systems and first- or third-party services. The [connections](https://github.com/skyflowapi/skyflow-java/tree/main/src/main/java/com/skyflow/vault/connection) module invokes both inbound and/or outbound connections. + +- **Inbound connections**: Act as intermediaries between your client and server, tokenizing sensitive data before it reaches your backend, ensuring downstream services handle only tokenized data. +- **Outbound connections**: Enable secure extraction of data from the vault and transfer it to third-party services via your backend server, such as processing checkout or card issuance flows. + +## ConnectionController + +`ConnectionController` is the class returned by `skyflowClient.connection()` and `skyflowClient.connection(connectionId)`. All connection operations are called on this object. + +```java +// Uses the default (first configured) connection +ConnectionController connection = skyflowClient.connection(); + +// Uses a specific connection by ID +ConnectionController connection = skyflowClient.connection(""); +``` + +**Methods:** + +| Method | Parameters | Returns | Description | +|--------|-----------|---------|-------------| +| `invoke(InvokeConnectionRequest)` | [`InvokeConnectionRequest`](../docs/api_reference.md#invokeconnectionrequest) | [`InvokeConnectionResponse`](../docs/api_reference.md#invokeconnectionresponse) | Invoke an inbound or outbound connection | + +## Invoke a connection + +To invoke a connection, use the `invoke` method of the Skyflow client. + +### Construct an invoke connection request + +```java +import com.skyflow.enums.RequestMethod; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.connection.InvokeConnectionRequest; +import com.skyflow.vault.connection.InvokeConnectionResponse; + +import java.util.HashMap; +import java.util.Map; + +/** + * This example demonstrates how to invoke an external connection using the Skyflow SDK, along with corresponding InvokeConnectionRequest schema. + * + */ +public class InvokeConnectionSchema { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Define the request body parameters + // These are the values you want to send in the request body + Map requestBody = new HashMap<>(); + requestBody.put("", ""); + requestBody.put("", ""); + + // Step 2: Define the request headers + // Add any required headers that need to be sent with the request + Map requestHeaders = new HashMap<>(); + requestHeaders.put("", ""); + requestHeaders.put("", ""); + + // Step 3: Define the path parameters + // Path parameters are part of the URL and typically used in RESTful APIs + Map pathParams = new HashMap<>(); + pathParams.put("", ""); + pathParams.put("", ""); + + // Step 4: Define the query parameters + // Query parameters are included in the URL after a '?' and are used to filter or modify the response + Map queryParams = new HashMap<>(); + queryParams.put("", ""); + queryParams.put("", ""); + + // Step 5: Build the InvokeConnectionRequest using the provided parameters + InvokeConnectionRequest invokeConnectionRequest = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) // The HTTP method to use for the request (POST in this case) + .requestBody(requestBody) // The body of the request + .requestHeaders(requestHeaders) // The headers to include in the request + .pathParams(pathParams) // The path parameters for the URL + .queryParams(queryParams) // The query parameters to append to the URL + .build(); + + // Step 6: Invoke the connection using the request + // Replace "" with the actual connection ID you are using + InvokeConnectionResponse invokeConnectionResponse = skyflowClient.connection("").invoke(invokeConnectionRequest); + + // Step 7: Print the response from the invoked connection + // This response contains the result of the request sent to the external system + System.out.println(invokeConnectionResponse); + + } catch (SkyflowException e) { + // Step 8: Handle any exceptions that occur during the connection invocation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception stack trace for debugging + } + } +} +``` + +`method` accepts any [`RequestMethod`](../docs/api_reference.md#requestmethod) value (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). See [`InvokeConnectionRequest`](../docs/api_reference.md#invokeconnectionrequest) in the API Reference for all builder options. + +**pathParams, queryParams, requestHeader, requestBody** are the JSON objects represented as HashMaps, that will be sent through the connection integration url. + +### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/connection/InvokeConnectionExample.java) of invokeConnection + +```java +import com.skyflow.Skyflow; +import com.skyflow.config.ConnectionConfig; +import com.skyflow.config.Credentials; +import com.skyflow.enums.LogLevel; +import com.skyflow.enums.RequestMethod; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.connection.InvokeConnectionRequest; +import com.skyflow.vault.connection.InvokeConnectionResponse; + +import java.util.HashMap; +import java.util.Map; + +/** + * This example demonstrates how to invoke an external connection using the Skyflow SDK. + * It configures a connection, sets up the request, and sends a POST request to the external service. + * + * 1. Initialize Skyflow client with connection details. + * 2. Define the request body, headers, and method. + * 3. Execute the connection request. + * 4. Print the response from the invoked connection. + */ +public class InvokeConnectionExample { + public static void main(String[] args) { + try { + // Initialize Skyflow client + // Step 1: Set up credentials and connection configuration + // Load credentials from a JSON file (you need to provide the correct path) + Credentials credentials = new Credentials(); + credentials.setPath("/path/to/credentials.json"); + + // Define the connection configuration (URL and credentials) + ConnectionConfig connectionConfig = new ConnectionConfig(); + connectionConfig.setConnectionId(""); // Replace with actual connection ID + connectionConfig.setConnectionUrl("https://connection.url.com"); // Replace with actual connection URL + connectionConfig.setCredentials(credentials); // Set credentials for the connection + + // Initialize the Skyflow client with the connection configuration + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) // Set log level to DEBUG for detailed logs + .addConnectionConfig(connectionConfig) // Add connection configuration to client + .build(); // Build the Skyflow client instance + + // Step 2: Define the request body and headers + // Map for request body parameters + Map requestBody = new HashMap<>(); + requestBody.put("card_number", "4337-1696-5866-0865"); // Example card number + requestBody.put("ssn", "524-41-4248"); // Example SSN + + // Map for request headers + Map requestHeaders = new HashMap<>(); + requestHeaders.put("Content-Type", "application/json"); // Set content type for the request + + // Step 3: Build the InvokeConnectionRequest with required parameters + // Set HTTP method to POST, include the request body and headers + InvokeConnectionRequest invokeConnectionRequest = InvokeConnectionRequest.builder() + .method(RequestMethod.POST) // HTTP POST method + .requestBody(requestBody) // Add request body parameters + .requestHeaders(requestHeaders) // Add headers + .build(); // Build the request + + // Step 4: Invoke the connection and capture the response + // Replace "" with the actual connection ID + InvokeConnectionResponse invokeConnectionResponse = skyflowClient.connection("").invoke(invokeConnectionRequest); + + // Step 5: Print the response from the connection invocation + System.out.println(invokeConnectionResponse); // Print the response to the console + + } catch (SkyflowException e) { + // Step 6: Handle any exceptions that occur during the connection invocation + System.out.println("Error occurred: "); + e.printStackTrace(); // Print the exception stack trace for debugging + } + } +} +``` + +Sample response: + +```json +{ + "data": { + "card_number": "4337-1696-5866-0865", + "ssn": "524-41-4248" + }, + "metadata": { + "requestId": "4a3453b5-7aa4-4373-98d7-cf102b1f6f97" + } +} +``` + +# Authenticate with bearer tokens + +This section covers methods for generating and managing tokens to authenticate API calls: + +- **Generate a bearer token**: + Enable the creation of bearer tokens using service account credentials. These tokens, valid for 60 minutes, provide secure access to Vault services and management APIs based on the service account's permissions. Use this for general API calls when you only need basic authentication without additional context or role-based restrictions. +- **Generate a bearer token with context**: + Support embedding context values into bearer tokens, enabling dynamic access control and the ability to track end-user identity. These tokens include context claims and allow flexible authorization for Vault services. Use this when policies depend on specific contextual attributes or when tracking end-user identity is required. +- **Generate a scoped bearer token**: + Facilitate the creation of bearer tokens with role-specific access, ensuring permissions are limited to the operations allowed by the designated role. This is particularly useful for service accounts with multiple roles. Use this to enforce fine-grained role-based access control, ensuring tokens only grant permissions for a specific role. +- **Generate signed data tokens**: + Add an extra layer of security by digitally signing data tokens with the service account's private key. These signed tokens can be securely detokenized, provided the necessary bearer token and permissions are available. Use this to add cryptographic protection to sensitive data, enabling secure detokenization with verified integrity and authenticity. + +## Generate a bearer token + +The [Service Account](https://github.com/skyflowapi/skyflow-java/tree/main/src/main/java/com/skyflow/serviceaccount/util) Java module generates service account tokens using a service account credentials file, which is provided when a service account is created. The tokens generated by this module are valid for 60 minutes and can be used to make API calls to the [Data](https://docs.skyflow.com/record/) and [Management](https://docs.skyflow.com/management/) APIs, depending on the permissions assigned to the service account. + +The `BearerToken` utility class generates bearer tokens using a credentials JSON file. Alternatively, you can pass the credentials as a string. + +[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java): + +```java +/** + * Example program to generate a Bearer Token using Skyflow's BearerToken utility. + * The token can be generated in two ways: + * 1. Using the file path to a credentials.json file. + * 2. Using the JSON content of the credentials file as a string. + */ +public class BearerTokenGenerationExample { + public static void main(String[] args) { + // Variable to store the generated token + String token = null; + + // Example 1: Generate Bearer Token using a credentials.json file + try { + // Specify the full file path to the credentials.json file + String filePath = ""; + + // Check if the token is either not initialized or has expired + if (Token.isExpired(token)) { + // Create a BearerToken object using the credentials file + BearerToken bearerToken = BearerToken.builder() + .setCredentials(new File(filePath)) // Set credentials from the file path + .build(); + + // Generate a new Bearer Token + token = bearerToken.getBearerToken(); + } + + // Print the generated Bearer Token to the console + System.out.println("Generated Bearer Token (from file): " + token); + } catch (SkyflowException e) { + // Handle any exceptions encountered during the token generation process + e.printStackTrace(); + } + + // Example 2: Generate Bearer Token using the credentials JSON as a string + try { + // Provide the credentials JSON content as a string + String fileContents = ""; + + // Check if the token is either not initialized or has expired + if (Token.isExpired(token)) { + // Create a BearerToken object using the credentials string + BearerToken bearerToken = BearerToken.builder() + .setCredentials(fileContents) // Set credentials from the string + .build(); + + // Generate a new Bearer Token + token = bearerToken.getBearerToken(); + } + + // Print the generated Bearer Token to the console + System.out.println("Generated Bearer Token (from string): " + token); + } catch (SkyflowException e) { + // Handle any exceptions encountered during the token generation process + e.printStackTrace(); + } + } +} +``` + +## Generate bearer tokens with context + +**Context-aware authorization** embeds context values into a bearer token during its generation so you can reference those values in your policies. This enables more flexible access controls, such as helping you track end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization. + +A service account with the `context_id` identifier generates bearer tokens containing context information, represented as a JWT claim in a Skyflow-generated bearer token. Tokens generated from such service accounts include a `context_identifier` claim, are valid for 60 minutes, and can be used to make API calls to the Data and Management APIs, depending on the service account's permissions. + +The `setCtx()` method accepts either a **String** or a **`Map`**: + +**String context** — use when your policy references a single context value: + +```java +BearerToken token = BearerToken.builder() + .setCredentials(new File(filePath)) + .setCtx("user_12345") + .build(); +``` + +**JSON object context** — use when your policy needs multiple context values for conditional data access. Each key in the `Map` maps to a Skyflow CEL policy variable under `request.context.*`: + +```java +Map ctx = new HashMap<>(); +ctx.put("role", "admin"); +ctx.put("department", "finance"); +ctx.put("user_id", "user_12345"); + +BearerToken token = BearerToken.builder() + .setCredentials(new File(filePath)) + .setCtx(ctx) + .build(); +``` + +With the map above, your Skyflow policies can reference `request.context.role`, `request.context.department`, and `request.context.user_id` to make conditional access decisions. + +You can also set context on `Credentials` for automatic token generation: + +```java +// String context +Credentials credentials = new Credentials(); +credentials.setPath("path/to/credentials.json"); +credentials.setContext("user_12345"); + +// Map context +Map ctx = new HashMap<>(); +ctx.put("role", "admin"); +ctx.put("department", "finance"); +credentials.setContext(ctx); +``` + +> **Note:** `getContext()` returns `Object` — callers should use `instanceof` if they need to inspect the type. + +Context map keys must contain only alphanumeric characters and underscores (`[a-zA-Z0-9_]`). Invalid keys will throw a `SkyflowException`. + +[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java) + +See Skyflow's [context-aware authorization](https://docs.skyflow.com) and [conditional data access](https://docs.skyflow.com) docs for policy variable syntax like `request.context.*`. + +## Generate scoped bearer tokens + +A service account with multiple roles can generate bearer tokens with access limited to a specific role by specifying the appropriate `roleID`. This can be used to limit access to specific roles for services with multiple responsibilities, such as segregating access for billing and analytics. The generated bearer tokens are valid for 60 minutes and can only execute operations permitted by the permissions associated with the designated role. + +[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java): + +```java +import com.skyflow.errors.SkyflowException; +import com.skyflow.serviceaccount.util.BearerToken; + +import java.io.File; +import java.util.ArrayList; + +/** + * Example program to generate a Scoped Token using Skyflow's BearerToken utility. + * The token is generated by providing the file path to the credentials.json file + * and specifying roles associated with the token. + */ +public class ScopedTokenGenerationExample { + public static void main(String[] args) { + // Variable to store the generated scoped token + String scopedToken = null; + + // Example: Generate Scoped Token by specifying the credentials.json file path + try { + // Create a list of roles that the generated token will be scoped to + ArrayList roles = new ArrayList<>(); + roles.add("ROLE_ID"); // Add a specific role to the list (e.g., "ROLE_ID") + + // Specify the full file path to the service account's credentials.json file + String filePath = ""; + + // Create a BearerToken object using the credentials file and associated roles + BearerToken bearerToken = BearerToken.builder() + .setCredentials(new File(filePath)) // Set credentials using the credentials.json file + .setRoles(roles) // Set the roles that the token should be scoped to + .build(); // Build the BearerToken object + + // Retrieve the generated scoped token + scopedToken = bearerToken.getBearerToken(); + + // Print the generated scoped token to the console + System.out.println(scopedToken); + } catch (SkyflowException e) { + // Handle exceptions that may occur during token generation + e.printStackTrace(); + } + } +} +``` + +Notes: + +- You can pass either the file path of a service account key credentials file or the service account key credentials as a string to the `setCredentials` method of the `BearerTokenBuilder` class. +- If both a file path and a string are provided, the last method used takes precedence. +- To generate multiple bearer tokens concurrently using threads, refer to the following [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java). + +## Generate Signed Data Tokens + +Skyflow generates data tokens when sensitive data is inserted into the vault. These data tokens can be digitally signed +with the private key of the service account credentials, which adds an additional layer of protection. Signed tokens can +be detokenized by passing the signed data token and a bearer token generated from service account credentials. The +service account must have appropriate permissions and context to detokenize the signed data tokens. + +The `setCtx()` method on `SignedDataTokensBuilder` also accepts either a **String** or a **`Map`**, using the same format as bearer tokens: + +```java +// String context +SignedDataTokens signedToken = SignedDataTokens.builder() + .setCredentials(new File(filePath)) + .setCtx("user_12345") + .setTimeToLive(30) + .setDataTokens(dataTokens) + .build(); + +// JSON object context +Map ctx = new HashMap<>(); +ctx.put("role", "analyst"); +ctx.put("department", "research"); + +SignedDataTokens signedToken = SignedDataTokens.builder() + .setCredentials(new File(filePath)) + .setCtx(ctx) + .setTimeToLive(30) + .setDataTokens(dataTokens) + .build(); +``` + +[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java) + +Response: + +```json +[ + { + "dataToken": "5530-4316-0674-5748", + "signedDataToken": "signed_token_eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzLCpZjA" + } +] +``` + +Notes: + +- You can provide either the file path to a service account key credentials file or the service account key credentials as a string to the `setCredentials` method of the `SignedDataTokensBuilder` class. +- If both a file path and a string are passed to the `setCredentials` method, the most recently specified input takes precedence. +- The `time-to-live` (TTL) value should be specified in seconds. +- By default, the TTL value is set to 60 seconds. + +## Bearer token expiry edge case +When you use bearer tokens for authentication and API requests in SDKs, there's the potential for a token to expire after the token is verified as valid but before the actual API call is made, causing the request to fail unexpectedly due to the token's expiration. An error from this edge case would look something like this: + +```txt +message: Authentication failed. Bearer token is expired. Use a valid bearer token. See https://docs.skyflow.com/api-authentication/ +``` + +If you encounter this kind of error, retry the request. During the retry, the SDK detects that the previous bearer token has expired and generates a new one for the current and subsequent requests. + +#### [Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java): + +```java +package com.example.serviceaccount; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.tokens.DetokenizeRequest; +import com.skyflow.vault.tokens.DetokenizeResponse; +import io.github.cdimascio.dotenv.Dotenv; +import java.util.ArrayList; + +/** + * This example demonstrates how to configure and use the Skyflow SDK + * to detokenize sensitive data stored in a Skyflow vault. + * It includes setting up credentials, configuring the vault, and + * making a detokenization request. The code also implements a retry + * mechanism to handle unauthorized access errors (HTTP 401). + */ +public class DetokenizeExample { + public static void main(String[] args) { + try { + // Setting up credentials for accessing the Skyflow vault + Credentials vaultCredentials = new Credentials(); + vaultCredentials.setCredentialsString(""); + + // Configuring the Skyflow vault with necessary details + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Vault ID + vaultConfig.setClusterId(""); // Cluster ID + vaultConfig.setEnv(Env.PROD); // Environment (e.g., DEV, PROD) + vaultConfig.setCredentials(vaultCredentials); // Setting credentials + + // Creating a Skyflow client instance with the configured vault + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) // Setting log level to ERROR + .addVaultConfig(vaultConfig) // Adding vault configuration + .build(); + + // Attempting to detokenize data using the Skyflow client + try { + detokenizeData(skyflowClient); + } catch (SkyflowException e) { + // Retry detokenization if the error is due to unauthorized access (HTTP 401) + if (e.getHttpCode() == 401) { + detokenizeData(skyflowClient); + } else { + // Rethrow the exception for other error codes + throw e; + } + } + } catch (SkyflowException e) { + // Handling any exceptions that occur during the process + System.out.println("An error occurred: " + e.getMessage()); + } + } + + /** + * Method to detokenize data using the Skyflow client. + * It sends a detokenization request with a list of tokens and prints the response. + * + * @param skyflowClient The Skyflow client instance used for detokenization. + * @throws SkyflowException If an error occurs during the detokenization process. + */ + public static void detokenizeData(Skyflow skyflowClient) throws SkyflowException { + // Creating a list of tokens to be detokenized + ArrayList tokenList = new ArrayList<>(); + tokenList.add(""); // First token + tokenList.add(""); // Second token + + // Building a detokenization request with the token list and configuration + DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() + .tokens(tokenList) // Adding tokens to the request + .continueOnError(false) // Stop on error + .redactionType(RedactionType.PLAIN_TEXT) // Redaction type (e.g., PLAIN_TEXT) + .build(); + + // Sending the detokenization request and receiving the response + DetokenizeResponse detokenizeResponse = skyflowClient.vault().detokenize(detokenizeRequest); + + // Printing the detokenized response + System.out.println(detokenizeResponse); + } +} +``` + +# Client Management + +After the `Skyflow` client is built you can add, retrieve, update, or remove vault and connection configurations at runtime — without rebuilding the client. + +## Vault configuration management + +```java +import com.skyflow.config.VaultConfig; + +// Add a new vault at runtime +skyflowClient.addVaultConfig(newVaultConfig); + +// Retrieve the config for a specific vault +VaultConfig config = skyflowClient.getVaultConfig(""); + +// Update an existing vault config (match by vaultId) +skyflowClient.updateVaultConfig(updatedVaultConfig); + +// Remove a vault from the client +skyflowClient.removeVaultConfig(""); +``` + +## Connection configuration management + +```java +import com.skyflow.config.ConnectionConfig; + +// Add a new connection at runtime +skyflowClient.addConnectionConfig(newConnectionConfig); + +// Retrieve the config for a specific connection +ConnectionConfig config = skyflowClient.getConnectionConfig(""); + +// Update an existing connection config (match by connectionId) +skyflowClient.updateConnectionConfig(updatedConnectionConfig); + +// Remove a connection from the client +skyflowClient.removeConnectionConfig(""); +``` + +## Credentials and log level management + +```java +// Replace the Skyflow-level credentials used when vault/connection configs +// do not specify their own credentials +skyflowClient.updateSkyflowCredentials(newCredentials); + +// Update the log level after the client has been built +skyflowClient.updateLogLevel(LogLevel.DEBUG); + +// Read the current log level +LogLevel currentLevel = skyflowClient.getLogLevel(); +``` + +**Client management method reference:** + +| Method | Returns | Description | +|--------|---------|-------------| +| `addVaultConfig(VaultConfig)` | `Skyflow` | Add a vault configuration | +| `getVaultConfig(String vaultId)` | `VaultConfig` | Retrieve a vault configuration by ID | +| `updateVaultConfig(VaultConfig)` | `Skyflow` | Replace a vault configuration (matched by `vaultId`) | +| `removeVaultConfig(String vaultId)` | `Skyflow` | Remove a vault configuration | +| `addConnectionConfig(ConnectionConfig)` | `Skyflow` | Add a connection configuration | +| `getConnectionConfig(String connectionId)` | `ConnectionConfig` | Retrieve a connection configuration by ID | +| `updateConnectionConfig(ConnectionConfig)` | `Skyflow` | Replace a connection configuration | +| `removeConnectionConfig(String connectionId)` | `Skyflow` | Remove a connection configuration | +| `updateSkyflowCredentials(Credentials)` | `Skyflow` | Replace the client-level credentials | +| `updateLogLevel(LogLevel)` | `Skyflow` | Change the log level after initialization | +| `getLogLevel()` | `LogLevel` | Return the current log level | + +All mutating methods return the `Skyflow` instance for chaining and throw `SkyflowException` on validation errors. + +# Error Handling + +The SDK uses `SkyflowException` for all errors — both client-side validation errors and server-side API errors. + +## Catching SkyflowException + +Wrap SDK calls in a `try/catch` block and catch `SkyflowException` to handle Skyflow-specific errors separately from unexpected exceptions: + +```java +import com.skyflow.errors.SkyflowException; + +try { + InsertResponse response = skyflowClient.vault().insert(insertRequest); +} catch (SkyflowException e) { + System.err.println("Skyflow error:"); + System.err.println(" HTTP code : " + e.getHttpCode()); + System.err.println(" Message : " + e.getMessage()); + System.err.println(" Request ID: " + e.getRequestId()); + System.err.println(" Details : " + e.getDetails()); +} catch (Exception e) { + System.err.println("Unexpected error: " + e.getMessage()); +} +``` + +## SkyflowException properties + +| Property | Method | Description | +|---|---|---| +| HTTP status code | `getHttpCode()` | Integer status code (e.g. `400`, `404`, `500`). | +| Message | `getMessage()` | Human-readable description of the error. | +| HTTP status string | `getHttpStatus()` | Status string from the server (e.g. `"BAD_REQUEST"`). | +| gRPC code | `getGrpcCode()` | gRPC status code from the server. | +| Request ID | `getRequestId()` | The `x-request-id` header — useful for support escalations. | +| Details | `getDetails()` | `JsonArray` of additional error context from the server. Empty array for validation errors, `null` if the server response omitted the field. | + +**Validation errors** (missing table name, empty token list, etc.) are thrown before any network call: +- `httpCode` is always `400` +- `requestId` and `grpcCode` are `null` +- `details` is an empty array + +**API errors** are returned by the Skyflow server and have all fields populated from the response body and headers. + +# Logging + +The SDK provides logging with Java's built-in logging library. By default, the SDK's logging level is set to `LogLevel.ERROR`. This can be changed using the `setLogLevel(logLevel)` method, as shown below: + +Currently, the following five log levels are supported: + +- `DEBUG`**:** + When `LogLevel.DEBUG` is passed, logs at all levels will be printed (DEBUG, INFO, WARN, ERROR). +- `INFO`**:** + When `LogLevel.INFO` is passed, INFO logs for every event that occurs during SDK flow execution will be printed, along with WARN and ERROR logs. +- `WARN`**:** + When `LogLevel.WARN` is passed, only WARN and ERROR logs will be printed. +- `ERROR`**:** + When `LogLevel.ERROR` is passed, only ERROR logs will be printed. +- `OFF`**:** + `LogLevel.OFF` can be used to turn off all logging from the Skyflow Java SDK. + +**Note:** The ranking of logging levels is as follows: `DEBUG` \< `INFO` \< `WARN` \< `ERROR` \< `OFF`. + +```java +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +/** + * This example demonstrates how to configure the Skyflow client with custom log levels + * and authentication credentials (either token, credentials string, or other methods). + * It also shows how to configure a vault connection using specific parameters. + * + * 1. Set up credentials with a Bearer token or credentials string. + * 2. Define the Vault configuration. + * 3. Build the Skyflow client with the chosen configuration and set log level. + * 4. Example of changing the log level from ERROR (default) to INFO. + */ +public class ChangeLogLevel { + public static void main(String[] args) throws SkyflowException { + // Step 1: Set up credentials - either pass token or use credentials string + // In this case, we are using a Bearer token for authentication + Credentials credentials = new Credentials(); + credentials.setToken(""); // Replace with actual Bearer token + + // Step 2: Define the Vault configuration + // Configure the vault with necessary details like vault ID, cluster ID, and environment + VaultConfig config = new VaultConfig(); + config.setVaultId(""); // Replace with actual Vault ID (primary vault) + config.setClusterId(""); // Replace with actual Cluster ID (from vault URL) + config.setEnv(Env.PROD); // Set the environment (default is PROD) + config.setCredentials(credentials); // Set credentials for the vault (either token or credentials) + + // Step 3: Define additional Skyflow credentials (optional, if needed for credentials string) + // Create a JSON object to hold your Skyflow credentials + JsonObject credentialsObject = new JsonObject(); + credentialsObject.addProperty("clientId", ""); // Replace with your client ID + credentialsObject.addProperty("clientName", ""); // Replace with your client name + credentialsObject.addProperty("tokenUri", ""); // Replace with your token URI + credentialsObject.addProperty("keyId", ""); // Replace with your key ID + credentialsObject.addProperty("privateKey", ""); // Replace with your private key + + // Convert the credentials object to a string format to be used for generating a Bearer Token + Credentials skyflowCredentials = new Credentials(); + skyflowCredentials.setCredentialsString(credentialsObject.toString()); // Set credentials string + + // Step 4: Build the Skyflow client with the chosen configuration and log level + Skyflow skyflowClient = Skyflow.builder() + .addVaultConfig(config) // Add the Vault configuration + .addSkyflowCredentials(skyflowCredentials) // Use Skyflow credentials if no token is passed + .setLogLevel(LogLevel.INFO) // Set log level to INFO (default is ERROR) + .build(); // Build the Skyflow client + + // Now, the Skyflow client is ready to use with the specified log level and credentials + System.out.println("Skyflow client has been successfully configured with log level: INFO."); + } +} +``` + +# Reporting a Vulnerability + +If you discover a potential security issue in this project, please reach out to us at **security@skyflow.com**. Please do not create public GitHub issues or Pull Requests, as malicious actors could potentially view them. diff --git a/skyvault/api-report/skyflow-java.baseline.jar b/skyvault/api-report/skyflow-java.baseline.jar index 6911c011..81085660 100644 Binary files a/skyvault/api-report/skyflow-java.baseline.jar and b/skyvault/api-report/skyflow-java.baseline.jar differ diff --git a/skyvault/pom.xml b/skyvault/pom.xml index c7474199..618c7e9d 100644 --- a/skyvault/pom.xml +++ b/skyvault/pom.xml @@ -119,10 +119,35 @@ false true true - - com.skyflow.generated.* - com.skyflow.utils.* - + + + com.skyflow.Skyflow + com.skyflow.config + com.skyflow.enums + com.skyflow.errors + com.skyflow.serviceaccount.util + com.skyflow.vault.audit + com.skyflow.vault.bin + com.skyflow.vault.connection + com.skyflow.vault.controller + com.skyflow.vault.data + com.skyflow.vault.detect + com.skyflow.vault.tokens + false false @@ -177,7 +202,11 @@ central true - true + + false diff --git a/skyvault/src/main/java/com/skyflow/Skyflow.java b/skyvault/src/main/java/com/skyflow/Skyflow.java index 66640f0e..940f5008 100644 --- a/skyvault/src/main/java/com/skyflow/Skyflow.java +++ b/skyvault/src/main/java/com/skyflow/Skyflow.java @@ -35,6 +35,43 @@ public static SkyflowClientBuilder builder() { return new SkyflowClientBuilder(); } + // ── Covariant overrides ─────────────────────────────────────────────────── + // BaseSkyflow declares these as `Self` / `V`, which erase to BaseSkyflow and BaseVaultConfig. + // Source callers are unaffected because javac resolves the type parameters, but a consumer + // JAR compiled against skyflow-java 2.1.1 references the concrete descriptors and would fail + // with NoSuchMethodError against an erased-only surface. Re-declaring them keeps the published + // 2.x binary contract intact; each one just delegates. + + @Override + public Skyflow addVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + return super.addVaultConfig(vaultConfig); + } + + @Override + public VaultConfig getVaultConfig(String vaultId) { + return super.getVaultConfig(vaultId); + } + + @Override + public Skyflow updateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + return super.updateVaultConfig(vaultConfig); + } + + @Override + public Skyflow removeVaultConfig(String vaultId) throws SkyflowException { + return super.removeVaultConfig(vaultId); + } + + @Override + public Skyflow updateSkyflowCredentials(Credentials credentials) throws SkyflowException { + return super.updateSkyflowCredentials(credentials); + } + + @Override + public Skyflow setLogLevel(LogLevel logLevel) { + return super.setLogLevel(logLevel); + } + public Skyflow addConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { this.builder.addConnectionConfig(connectionConfig); return this; diff --git a/skyvault/src/main/java/com/skyflow/VaultClient.java b/skyvault/src/main/java/com/skyflow/VaultClient.java index 84289e8f..6a0a3134 100644 --- a/skyvault/src/main/java/com/skyflow/VaultClient.java +++ b/skyvault/src/main/java/com/skyflow/VaultClient.java @@ -45,6 +45,16 @@ public class VaultClient extends BaseVaultClient { + + /** + * Restores the concrete descriptor from skyflow-java 2.1.1. BaseVaultClient declares this as + * {@code V getVaultConfig()}, which erases to BaseVaultConfig; VaultController and + * DetectController are compiled against the concrete form. + */ + @Override + protected VaultConfig getVaultConfig() { + return super.getVaultConfig(); + } private final ApiClientBuilder apiClientBuilder; private ApiClient apiClient; diff --git a/skyvault/src/main/java/com/skyflow/errors/SkyflowException.java b/skyvault/src/main/java/com/skyflow/errors/SkyflowException.java deleted file mode 100644 index 6fedf9c3..00000000 --- a/skyvault/src/main/java/com/skyflow/errors/SkyflowException.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.skyflow.errors; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.skyflow.utils.Constants; - -import java.util.List; -import java.util.Map; - -/** - * Exception thrown by all Skyflow SDK operations. - * - *

There are two broad categories of errors: - * - *

    - *
  • Validation errors — caught before any network call is made (e.g. missing table, - * empty token list). These always have {@code httpCode = 400} and an empty - * {@link #getDetails()} array. {@link #getRequestId()} and {@link #getGrpcCode()} are - * {@code null}. - *
  • API errors — returned by the Skyflow server. The HTTP status code, gRPC code, - * human-readable status string, error message, and request ID are all parsed from the - * response and available via the corresponding getters. - *
- * - *

Typical error-handling pattern: - *

{@code
- * try {
- *     InsertResponse response = vault.insert(request);
- * } catch (SkyflowException e) {
- *     System.err.println("HTTP " + e.getHttpCode() + " — " + e.getMessage());
- *     if (e.getRequestId() != null) {
- *         System.err.println("Request ID: " + e.getRequestId());
- *     }
- * }
- * }
- */ -public class SkyflowException extends Exception { - private String requestId; - private Integer grpcCode; - private Integer httpCode; - private String message; - private String httpStatus; - private JsonArray details; - private JsonObject responseBody; - - public SkyflowException(String message) { - super(message); - this.message = message; - } - - public SkyflowException(Throwable cause) { - super(cause); - this.message = cause.getMessage(); - } - - public SkyflowException(String message, Throwable cause) { - super(message, cause); - this.message = message; - } - - /** - * Constructs a validation error with a fixed HTTP 400 status. - * {@link #getDetails()} returns an empty array; {@link #getRequestId()} and - * {@link #getGrpcCode()} return {@code null}. - */ - public SkyflowException(int code, String message) { - super(message); - this.httpCode = code; - this.message = message; - this.httpStatus = HttpStatus.BAD_REQUEST.getHttpStatus(); - this.details = new JsonArray(); - } - - /** - * Constructs an API error from an HTTP response. - * Parses the JSON error body to populate {@link #getMessage()}, {@link #getGrpcCode()}, - * {@link #getHttpStatus()}, and {@link #getDetails()}. The request ID is read from the - * {@code x-request-id} response header. If the body cannot be parsed, falls back to the - * raw body string as the message. - */ - public SkyflowException(int httpCode, Throwable cause, Map> responseHeaders, String responseBody) { - super(cause); - this.httpCode = httpCode > 0 ? httpCode : 400; - try { - setRequestId(responseHeaders); - setResponseBody(responseBody, responseHeaders); - } catch (Exception e) { - this.httpStatus = HttpStatus.BAD_REQUEST.getHttpStatus(); - String fullMessage = responseBody != null ? responseBody : - (cause.getLocalizedMessage() != null ? cause.getMessage() : ErrorMessage.ErrorOccurred.getMessage()); - this.message = fullMessage.split("HTTP response code:")[0].trim(); - } - } - - private void setResponseBody(String responseBody, Map> responseHeaders) { - this.responseBody = JsonParser.parseString(responseBody).getAsJsonObject(); - if (this.responseBody.get("error") != null) { - setGrpcCode(); - setHttpStatus(); - setMessage(); - setDetails(responseHeaders); - } - } - - /** - * Returns the {@code x-request-id} from the server response, useful for support escalations. - * {@code null} for validation errors that never reached the server. - */ - public String getRequestId() { - return requestId; - } - - private void setRequestId(Map> responseHeaders) { - List ids = responseHeaders.get(Constants.REQUEST_ID_HEADER_KEY); - this.requestId = ids == null ? null : ids.get(0); - } - - private void setMessage() { - JsonElement messageElement = ((JsonObject) responseBody.get("error")).get("message"); - this.message = messageElement == null ? null : messageElement.getAsString(); - } - - private void setGrpcCode() { - JsonElement grpcElement = ((JsonObject) responseBody.get("error")).get("grpc_code"); - this.grpcCode = grpcElement == null ? null : grpcElement.getAsInt(); - } - - private void setHttpStatus() { - JsonElement statusElement = ((JsonObject) responseBody.get("error")).get("http_status"); - this.httpStatus = statusElement == null ? null : statusElement.getAsString(); - } - - /** - * Returns the HTTP status code (e.g. 400, 404, 500). - * Defaults to 400 when the server returned a non-positive code. - */ - public int getHttpCode() { - return httpCode; - } - - /** - * Returns additional error details from the server response, or an empty array for - * validation errors. Never {@code null} for validation errors; may be {@code null} for - * API errors whose response body contained no {@code details} field. - */ - public JsonArray getDetails() { - return details; - } - - private void setDetails(Map> responseHeaders) { - JsonElement detailsElement = ((JsonObject) responseBody.get("error")).get("details"); - List errorFromClientHeader = responseHeaders.get(Constants.ERROR_FROM_CLIENT_HEADER_KEY); - if (detailsElement != null) { - this.details = detailsElement.getAsJsonArray(); - } - if (errorFromClientHeader != null) { - this.details = this.details == null ? new JsonArray() : this.details; - String errorFromClient = errorFromClientHeader.get(0); - JsonObject detailObject = new JsonObject(); - detailObject.addProperty("errorFromClient", errorFromClient); - this.details.add(detailObject); - } - } - - /** - * Returns the gRPC status code from the server response. - * {@code null} for validation errors and API responses that omit this field. - */ - public Integer getGrpcCode() { - return grpcCode; - } - - /** - * Returns the human-readable HTTP status string from the server response (e.g. - * {@code "Bad Request"}, {@code "Not Found"}). - */ - public String getHttpStatus() { - return httpStatus; - } - - @Override - public String getMessage() { - return message; - } - - @Override - public String toString() { - return String.format( - "%n requestId: %s%n grpcCode: %s%n httpCode: %s%n httpStatus: %s%n message: %s%n details: %s", - this.requestId, this.grpcCode, this.httpCode, this.httpStatus, this.message, this.details - ); - } -}