Skip to content

Document AI: Multitenancy#122

Open
samyuktaprabhu wants to merge 4 commits into
mainfrom
doc-ai-feat/multitenancy
Open

Document AI: Multitenancy#122
samyuktaprabhu wants to merge 4 commits into
mainfrom
doc-ai-feat/multitenancy

Conversation

@samyuktaprabhu

@samyuktaprabhu samyuktaprabhu commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Document AI: Multi-Tenancy Support for SAP CAP Java Plugin

New Features

✨ Implements full multi-tenancy support for the SAP Document AI CAP Java plugin. Each subscriber tenant's documents are now fully isolated within the shared Document AI service instance. Multi-tenancy is detected automatically at startup — no configuration flag is required.

Key additions:

  • DocumentAiSetupHandler: New handler registered only in multi-tenant mode. Hooks into the CAP MTX DeploymentService lifecycle — logs tenant onboarding on subscribe, and marks all active jobs (PENDING, SUBMITTED, RUNNING) as FAILED on unsubscribe to prevent poll errors after credentials are revoked.
  • Tenant-aware polling: ExtractionPollingHandler now groups active jobs by tenantId and switches the CDS request context per group via runtime.requestContext().systemUser(tenantId).run(...).
  • clientId query parameter: All Document AI API calls now append ?clientId=<tenantId> (submit) and &clientId=<tenantId> (poll) when a tenant ID is present, enabling job isolation within the shared service instance.
  • Submission failure result event: DocumentSubmissionHandler now emits a DocumentExtractionResult event immediately when submission fails.
  • isTerminal() helper: Added to ExtractionStatus enum to simplify terminal state checks.

Changes

  • DocumentAiSetupHandler.java: New file. Hooks into MTX subscribe/unsubscribe lifecycle. Marks active tenant jobs as FAILED on unsubscribe.
  • DocumentAiServiceConfiguration.java: Added detectMultiTenancy() method; conditionally registers DocumentAiSetupHandler; passes multiTenancyEnabled flag to ExtractionPollingHandler.
  • ExtractionPollingHandler.java: Added multiTenancyEnabled flag and processJobsForTenant() method; tenant ID is now passed through to the HTTP client on each poll.
  • DefaultDocumentAiClient.java / DocumentAiClient.java: submitDocument and getJobResult now accept a tenantId parameter; clientId query param appended when non-null.
  • DefaultDocumentAiProcessingService.java / DocumentAiProcessingService.java: processDocument interface and implementation updated to propagate tenantId.
  • DocumentSubmissionHandler.java: Emits DocumentExtractionResult event on submission failure.
  • ExtractionStatus.java: Added isTerminal() utility method.
  • DocumentAiSetupHandlerTest.java (unit + integration): New test files covering subscribe logging, unsubscribe job marking, and tenant isolation.
  • ExtractionPollingHandlerTest.java: Added tests for per-tenant context switching and single-tenant bypass.
  • DefaultDocumentAiClientTest.java: Added tests verifying clientId query param presence/absence.
  • README.md, architecture.md, handover.md: Documentation updated to reflect multi-tenancy as implemented; removed "future work" language; updated capability tables and handler descriptions.
  • samples/bookshop/app/index.html: Updated UI5/Fiori sandbox script URLs to pinned version 1.148.2.
  • AbstractDocumentAiTest.java: Updated test polling client signatures to match new tenantId parameters.
  • 🔄 Regenerate and Update Summary
PR Bot Information

Version: 1.28.2

  • Summary Prompt: Default Prompt
  • Output Template: Default Template
  • LLM: anthropic--claude-4.6-sonnet
  • Correlation ID: 60edba20-84dc-11f1-8c27-ce5a0ab59a0b
  • File Content Strategy: Full file content
  • Event Trigger: pull_request.edited

@samyuktaprabhu
samyuktaprabhu requested a review from a team as a code owner July 21, 2026 07:08

@hyperspace-pr-bot hyperspace-pr-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR successfully implements multi-tenancy for the Document AI plugin with solid test coverage across unit and integration layers. There are a few issues to address: the unsubscribe filter uses a denylist instead of an allowlist for active job statuses (fragile against new statuses), tenant IDs are appended to URLs without URI encoding (potential malformed requests), a test verification pattern incorrectly invokes the mock under verify() (unreliable assertion), and the failure result event emission doesn't guard against a null jobId.

PR Bot Information

Version: 1.28.2

  • Event Trigger: pull_request.opened
  • LLM: anthropic--claude-4.6-sonnet
  • Correlation ID: efd3a6a0-84d2-11f1-8c4d-0b4c74e387a8
  • File Content Strategy: Full file content

Comment thread cds-feature-sap-document-ai/README.md
}
}

static boolean detectMultiTenancy(CdsRuntime runtime) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exact same function exists in AICoreServiceConfiguration.java - since this is only one function I'd leave this as it is, but in case there is more shared code at some point, this might be extracted into a shared internal utility module (e.g. cds-ai-common).

if (result.status() == ExtractionResult.Status.FAILED) {
logger.error("[sap-document-ai] Extraction failed for fileName={}", event.getFileName());
if (result.internalJobId() != null) {
emitExtractionResult(context, result.internalJobId(), null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We emit a DocumentExtractionResult when the extraction failed?
How does the consumer know this failed?
Can we set a status field in DocumentExtractionResult?


@Before
@HandlerOrder(HandlerOrder.EARLY)
public void beforeUnsubscribe(UnsubscribeEventContext context) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So: a tenant unsubscribes, before that actually is executed we mark all his jobs as failed, right?
Why do we not do this in afterUnsubscribe? That way, in case unsubscription fails for some reason, we have not marked the jobs as failed before.

String path = DOCUMENT_AI_API_PATH + DOCUMENT_JOBS + "/" + dieJobId + EXTRACTED_VALUES_TRUE;
if (tenantId != null) {
path =
path + "&" + CLIENT_ID_PARAM + "=" + URLEncoder.encode(tenantId, StandardCharsets.UTF_8);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also use buildUriWithClientId here?


private void processJobsForTenant(String tenantId, List<ExtractionJob> jobs) {
if (tenantId == null) {
jobs.forEach(job -> processJob(job, null));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A job without a tenant id is processed in a MT-scenario: How can this actually happen?
In case there is a good reason for this: Add a warning here: multitenancy enabled but processing jobs without tenant ID.

@lisajulia

Copy link
Copy Markdown
Contributor

Also: as of now there is no real integration test connecting to a real SAP Document Information Extraction instance - the tests in cds-feature-ai-core use a real AI Core instance. Possibly we can add a document ai instance to the same cf space and test the document extraction end-to-end?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants