chore: import sap document ai#96
Conversation
Initialize CAP bookshop sample application
Setup Repo for the SAP Document AI Plugin
remove extra files rename handler tiny fixes
remove extra files rename handler tiny fixes
#2750 - Integrate Attachment Plugin into Doc AI Plugin
…-setup # Conflicts: # sap-document-ai/pom.xml # sap-document-ai/src/main/java/com/sap/cds/handlers/AttachmentEventHandler.java # sap-document-ai/src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration # sap-document-ai/src/test/java/com/sap/cds/AttachmentEventHandlerTest.java
#2753 - Implement attachment-triggered Document AI orchestration
remove extra files rename handler tiny fixes setup orchestrator initial Extraction Orchestrator setup async process :) add tests bounded thread pool # Conflicts: # sap-document-ai/pom.xml # sap-document-ai/src/main/java/com/sap/cds/handlers/AttachmentEventHandler.java # sap-document-ai/src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration # sap-document-ai/src/test/java/com/sap/cds/AttachmentEventHandlerTest.java
clean up
#2761 - Add Document AI processing service
move the file one level lower attempt to fix failing test attempt 2 to fix failing test attempt 3 to fix failing test attempt 4 to fix failing test attempt 5 to fix failing workflow test clean up configure pmd, jacoco and mvn spotless fix version tests
#2782 - Introduce a CI Pipeline to automatically validate pull requests
…er to decouple consumer from plugin service name
…uler #2773 - feature/outbox scheduler
#2752 - Docs: add javadcos to classes and methods
…gin-documentation #2775 - Docs: Write SAP Document AI documentation
…ements-cleanups #2752 - tiny enhancements & cleanups
refactor: rename integration test files refactor: turn off logs
#2840 - Add integration tests
- Hoist the plugin Maven module up: sap-document-ai/{pom.xml,src} -> cds-feature-sap-document-ai/{pom.xml,src}
- Move imported bookshop sample to samples/document-ai-bookshop/
- Move imported integration test Java sources into integration-tests/spring/
under com.sap.cds.feature.documentai.integrationtest (Application, config
and remaining files are handled in later commits).
Structural-only change; no pom or Java content is modified in this commit.
- Delete the imported LICENSE (Apache 2.0 already present at repo root, only whitespace-different). - Delete the imported .github/ workflows and .gitignore; both are superseded by the repo-level equivalents in cds-ai. - Delete .hyperspace/pull_request_bot.json (byte-identical to the copy at repo root). - Delete the leftover cds-feature-sap-document-ai/integration-tests/ directory: its Java test sources were moved into integration-tests/spring in the previous commit; the remaining Application.java, application.yaml, logback-test.xml, pom.xml, package.json will be reintegrated into the spring integration-tests module in commit 5.
- Set parent to cds-ai-root, drop local version, groupId, dependencyManagement and spotless/pmd/spotbugs/compiler configuration (inherit from root). - Depend on cds-services-api, cds-services-utils, cds4j-core, cds-services-impl and connectivity-apache-httpclient5 (versions managed by root parent or the SAP Cloud SDK BOM). - Add hermetic CDS build: cds.install-node and cds.npm-ci executions plus a local package.json pinning @sap/cds-dk 9.9.1 (matches cds-feature-ai-core). - Keep the plugin's existing cds.build layout (src/main/resources/cds/ com.sap.cds/sap-document-ai) via a workingDirectory-based invocation of the cds-maven-plugin cds goal, mirroring cds-feature-ai-core. - Add cds.generate for basePackage com.sap.cds.feature.documentai.generated.cds4j. - Add a module spotbugs-exclusion-filter.xml so the spotbugs check inherited from the root pluginManagement can resolve its excludeFilterFile. - Wire module-level jacoco (prepare-agent, report) matching sibling modules.
…rences
- Rewrite the copyright header in every imported Java file (plugin sources,
moved bookshop handlers and moved integration tests):
* update contributor line from "cds-feature-sap-document-ai contributors"
to "cds-ai contributors" so files match the license header enforced by
the root spotless configuration;
* fix indentation from "* " / "*/" to " * " / " */" to match the
canonical style used by the rest of the repository.
- Update the DocumentExtractionHandler error message in the bookshop sample
to reference the new artifactId cds-feature-sap-document-ai.
…actor
Root pom:
- Register cds-feature-sap-document-ai as a Maven module.
- Add a dependencyManagement entry for cds-feature-sap-document-ai using
${revision} so downstream modules pick up the CI-friendly version.
Integration tests (integration-tests/spring):
- Add cds-feature-sap-document-ai (managed) as a test-scope reachable
dependency so tests can reach the plugin's generated cds4j classes and
runtime handlers.
- Add org.awaitility:awaitility 4.2.2 (test) used by the moved tests.
- Add "using from 'com.sap.cds/sap-document-ai';" to test-service.cds so
the CDS build for the integration-tests reactor includes the plugin
model and the H2 deploy provisions the ExtractionJob table.
- Rename moved *ITest.java files to *Test.java so surefire picks them up
(the spring integration-tests module runs everything through surefire
and does not have failsafe wired), and update internal class references.
Samples (samples/document-ai-bookshop):
- Swap plugin coordinates from sap-document-ai:1.0-SNAPSHOT to
com.sap.cds:cds-feature-sap-document-ai using a version property.
- Pin cds-feature-sap-document-ai.version to 0.0.1-alpha (matches root
${revision}) and align cds.services.version to the repo default 4.9.0.
…build blockers
- Set explicit version on cds4j-core dependency (com.sap.cds:cds4j-core is
not managed by cds-services-bom); use ${cds.services.version} to keep it
aligned with the rest of the CDS stack.
- Commit package-lock.json so the cds-maven-plugin cds.npm-ci execution can
run non-interactively (mirrors cds-feature-ai-core and cds-feature-
recommendations).
- Extend the module-local spotbugs-exclusion-filter.xml to also skip the
DM_DEFAULT_ENCODING pattern for classes matching *Test / *TestBase. The
imported unit tests use byte fixtures that intentionally rely on the
platform default encoding; production code is not affected.
There was a problem hiding this comment.
This PR introduces a well-structured CAP Java plugin for SAP Document AI with good test coverage, but there are several concrete bugs to address: a NullPointerException risk in mapDieStatus, a documentAiJobId field that is never populated on the outbound DocumentExtractionResult event (causing consumers to always receive null for that field), a URI.resolve misuse that can silently produce wrong URLs when the destination has a non-root base path, and a redundant variable assignment in DefaultDocumentAiProcessingService.
PR Bot Information
Version: 1.26.14
- Event Trigger:
pull_request.opened - Correlation ID:
5d40f3a0-76d7-11f1-86df-2056d2e8c59b - File Content Strategy: Full file content
- LLM:
anthropic--claude-4.6-sonnet
| public String processDocument(String jobId, DocumentInput documentInput) { | ||
| try { | ||
| String documentAiJobId = documentAiClient.submitDocument(documentInput); | ||
| return documentAiJobId; |
There was a problem hiding this comment.
Bug: Unnecessary variable for documentAiJobId return value — redundant assignment before immediate return.
| return documentAiJobId; | |
| return documentAiClient.submitDocument(documentInput); | |
Double-check suggestion before committing. Edit this comment for amendments.
Please provide feedback on the review comment by checking the appropriate box:
- 🌟 Awesome comment, a human might have missed that.
- ✅ Helpful comment
- 🤷 Neutral
- ❌ This comment is not helpful
| String base = destination.getUri().toString(); | ||
| String prefix = base.endsWith("/") ? base : base + "/"; | ||
| return URI.create(prefix).resolve(path); | ||
| } |
There was a problem hiding this comment.
Bug: buildUri uses URI.resolve(path) with a relative path like "document-information-extraction/v1/document/jobs". When the base URI already has a path (e.g. https://host/base/), URI.resolve resolves relative to the last segment — if the base ends without a slash, the last path component is replaced. The current code appends a trailing slash to base before calling URI.create(prefix).resolve(path), but URI.create(prefix) on a string ending with / still needs the path to be truly relative. This works only when the base is just the host root. If the base has a path like https://host/service/, calling .resolve("document-information-extraction/v1/document/jobs") would replace the last segment with the API path rather than appending under it. A safer approach is direct string concatenation.
| } | |
| private URI buildUri(String path) { | |
| String base = destination.getUri().toString(); | |
| String prefix = base.endsWith("/") ? base : base + "/"; | |
| return URI.create(prefix + path); | |
| } | |
Double-check suggestion before committing. Edit this comment for amendments.
Please provide feedback on the review comment by checking the appropriate box:
- 🌟 Awesome comment, a human might have missed that.
- ✅ Helpful comment
- 🤷 Neutral
- ❌ This comment is not helpful
| } | ||
|
|
||
| private ExtractionStatus mapDieStatus(String dieStatus) { | ||
| return switch (dieStatus.toUpperCase()) { |
There was a problem hiding this comment.
Bug: dieStatus is passed to .toUpperCase() without a null check; ExtractionData.dieStatus() could be null (the record has no non-null constraint), causing a NullPointerException here.
| return switch (dieStatus.toUpperCase()) { | |
| private ExtractionStatus mapDieStatus(String dieStatus) { | |
| if (dieStatus == null) return null; | |
| return switch (dieStatus.toUpperCase()) { | |
| case "RUNNING" -> ExtractionStatus.RUNNING; | |
| case "DONE" -> ExtractionStatus.DONE; | |
| case "FAILED" -> ExtractionStatus.FAILED; | |
| default -> null; // PENDING or unknown — no transition | |
| }; | |
| } | |
Double-check suggestion before committing. Edit this comment for amendments.
Please provide feedback on the review comment by checking the appropriate box:
- 🌟 Awesome comment, a human might have missed that.
- ✅ Helpful comment
- 🤷 Neutral
- ❌ This comment is not helpful
| eventData.setJobId(jobId); | ||
| eventData.setExtractionResult(extractionResult); | ||
| DocumentExtractionResultContext eventContext = DocumentExtractionResultContext.create(); | ||
| eventContext.setData(eventData); |
There was a problem hiding this comment.
Bug: emitExtractionCompleted is called with extractionResult but never sets documentAiJobId on the emitted DocumentExtractionResult event, so consumers always receive a null documentAiJobId — even though the field is documented and the ExtractionPollingHandler has dieJobId available at the call site.
| eventContext.setData(eventData); | |
| DocumentExtractionResult eventData = DocumentExtractionResult.create(); | |
| eventData.setJobId(jobId); | |
| eventData.setDocumentAiJobId(dieJobId); | |
| eventData.setExtractionResult(extractionResult); | |
Double-check suggestion before committing. Edit this comment for amendments.
Please provide feedback on the review comment by checking the appropriate box:
- 🌟 Awesome comment, a human might have missed that.
- ✅ Helpful comment
- 🤷 Neutral
- ❌ This comment is not helpful
| } | ||
| } | ||
|
|
||
| private void emitExtractionCompleted(String jobId, String extractionResult) { |
There was a problem hiding this comment.
Bug: emitExtractionCompleted signature only accepts jobId and extractionResult, but dieJobId is also needed to populate documentAiJobId on the event (see prior comment). The method signature must be updated to pass dieJobId.
| private void emitExtractionCompleted(String jobId, String extractionResult) { | |
| private void emitExtractionCompleted(String jobId, String dieJobId, String extractionResult) { | |
Double-check suggestion before committing. Edit this comment for amendments.
Please provide feedback on the review comment by checking the appropriate box:
- 🌟 Awesome comment, a human might have missed that.
- ✅ Helpful comment
- 🤷 Neutral
- ❌ This comment is not helpful
Ran `mvn spotless:apply` at the repo root. Reformatting affects:
- The moved doc-ai integration tests under integration-tests/spring
(five files): minor line-break normalization introduced during the
cds-feature-sap-document-ai import.
- One pre-existing formatting nit in
cds-feature-ai-core/.../MockAICoreApiHandler.java that the previous
runs of spotless never caught because the spotless check phase is
gated by `${spotless.check.skip}` (default true) in the root pom.
No functional changes; unit and integration tests still pass.
The integration-tests/spring module now compile-depends on
com.sap.cds:cds-feature-sap-document-ai:${revision}. The composite
action for the Integration Tests job first installs a small subset of
plugins to ~/.m2 with a -pl list and then runs `mvn clean verify`
against integration-tests/pom.xml. The new plugin was missing from
that list, so dependency resolution failed with:
Could not find artifact com.sap.cds:cds-feature-sap-document-ai:
jar:0.0.1-alpha in central
Add cds-feature-sap-document-ai to the -pl list so the plugin gets
built and installed locally before the integration-tests reactor
runs. -am still resolves the transitive dependency graph correctly.
…e cds install
Reproducing the failing CI 'Local MTX Tests' job locally showed that a
fresh 'npm install' in integration-tests/mtx-local/ ends up with three
copies of @sap/cds:
- mtx-local/node_modules/@sap/cds @ 10.0.3 (hoisted)
- mtx-local/node_modules/@sap/cds-dk/node_modules/@sap/cds @ 9.9.1
- mtx-local/mtx/sidecar/node_modules/@sap/cds @ 9.9.2
The sidecar boots with 'cds-serve --profile development' and refuses to
start with:
ERROR: @sap/cds was loaded from different locations
Root cause:
* @sap/cds 10.0.3 was published to npm (major bump from 9.x).
* @sap/cds-mtxs 3.9.5 (latest 3.x) declares peer '@sap/cds: >=9' -
the loose lower bound. With no top-level pin, npm satisfies that
peer with the newest matching version and hoists 10.0.3.
* The sidecar itself declares '@sap/cds: ^9' (strict 9.x) so it
keeps a nested 9.x copy. Same for @sap/cds-dk, whose own dep
range '^8.3 || ^9' can not accept 10.x.
Result: three copies of @sap/cds, cds-serve aborts.
Fix: declare '@sap/cds: ^9' at the workspace root as a devDependency.
This gives npm a top-level constraint that pins the hoisted @sap/cds
to 9.x, which simultaneously satisfies cds-mtxs's '>=9', cds-dk's
'^8.3 || ^9' and the sidecar's '^9' - all resolve to the same hoisted
9.x copy. Verified locally with a full 'mvn clean verify -pl
integration-tests/mtx-local/srv -am -P mtx-integration-tests' run:
8 MTX tests pass, sidecar starts cleanly.
The 'CI - PR' job for main was not affected earlier only because @sap/
cds 10 was published between PR 95's run (2026-07-02) and PR 96's run
(2026-07-03).
Import SAP Document AI CAP Java Plugin (
cds-feature-sap-document-ai)New Features
✨ Introduces the
cds-feature-sap-document-aimodule — a new CAP Java plugin (Alpha) that integrates SAP Document AI (Document Information Extraction / DIE) into CDS applications.The plugin exposes a CDS event-based API for submitting documents, manages asynchronous polling against the DIE service via the CDS persistent outbox, and delivers extraction results back to the application through a
DocumentExtractionResultoutbound event. It supports graceful degraded operation when no DIE service binding is present.Key capabilities:
DocumentExtractioninbound event triggers document submission to DIEDocumentExtractionResultoutbound event delivers extraction results to consumer handlersPENDING → SUBMITTED → RUNNING → DONE/FAILEDstate machine)cds.document-ai.polling.interval-secondsConnectivity,Request,ProcessingChanges
cds-feature-sap-document-ai/pom.xml: New Maven module definition with CDS build, code generation (POJOs), JaCoCo coverage enforcement (85%), Spotless, and PMD tooling.cds-feature-sap-document-ai/src/main/resources/cds/...: CDS model files definingDocumentAiService(events),ExtractionJob(persistence entity), andindex.cdsentry point.DocumentAiServiceConfiguration.java: Plugin entry point viaCdsRuntimeConfiguration/ServiceLoader; resolves DIE binding, builds OAuth2 destination via SAP Cloud SDK, wires all handlers.DocumentSubmissionHandler.java: HandlesDocumentExtractionevents on anyApplicationService; delegates toExtractionService.ExtractionPollingHandler.java: Outbox-registered handler polling DIE for active jobs; self-reschedules and emitsDocumentExtractionResulton completion.ExtractionServiceImpl.java: Core orchestrator — creates jobs, submits via processing service, enforces state machine, schedules polling.DefaultDocumentAiClient.java: HTTP client using Apache HttpClient 5 + SAP Cloud SDK for DIE REST API (submitDocument,getJobResult).StatusTransitionValidator.java,ExtractionStatus.java: Stateless state machine enforcement.service/exceptions/: Typed exceptions (DocumentAiException,ConcurrentJobUpdateException,IllegalStatusTransitionException).src/test/java/.../: Comprehensive unit tests for all production classes using Mockito and AssertJ.integration-tests/spring/: New Spring Boot integration tests covering the full extraction lifecycle, parallel processing, error resilience, event emission, and invalid transition rejection.samples/document-ai-bookshop/: Complete runnable Bookshop sample application demonstrating plugin integration with the CAP Attachments plugin and a Fiori UI.pom.xml(root): Addedcds-feature-sap-document-aias a module and to the dependency management BOM.cds-feature-sap-document-ai/README.md,docs/architecture.md: Full user-facing documentation covering quick start, integration guide, configuration, architecture, and quality tooling.PR Bot Information
Version:
1.26.14pull_request.openedanthropic--claude-4.6-sonnet5d40f3a0-76d7-11f1-86df-2056d2e8c59b