Skip to content

Release/7.0.2 - #470

Open
machacjozef wants to merge 39 commits into
release/7.0.0from
release/7.0.2
Open

Release/7.0.2#470
machacjozef wants to merge 39 commits into
release/7.0.0from
release/7.0.2

Conversation

@machacjozef

@machacjozef machacjozef commented Aug 3, 2026

Copy link
Copy Markdown
Member

Release

Summary by CodeRabbit

  • New Features

    • Improved full-text case search with multi-term matching, boosts, case-insensitive queries, and safer wildcard handling.
    • Added expanded monitoring visibility, including startup, node, Elasticsearch, and tracing information.
    • Improved asynchronous workflow actions and lifecycle handling.
  • Bug Fixes

    • Improved migration and identifier handling for cases, tasks, roles, and processes.
    • Ensured newly created tasks retain their process association.
  • Security

    • Restricted unauthenticated management access to health checks and disabled shutdown access.

renczesstefan and others added 30 commits July 10, 2026 10:15
Update Jackson dependencies to version 3.1.5 in `pom.xml`
…elpers to use a dedicated MongoTemplate bean (`migrationMongoTemplate`) with fallback logic in multi-database setups.
…rocessResourceId` and update related classes.
…om queries and setting it to null during migration.
…igrate role-related fields in `Case` documents
…and centralize ID translation logic with `getNewIdFromOldId`.
…and centralize ID translation logic with `getNewIdFromOldId`.
…for task and Petri net migration logic, adding `@ConditionalOnProperty` for configurability, and improving clarity in variable naming.
…Id` and deprecate `findByNetworkIdAndObjectId`.
…te management and enhanced executor configuration.

- Added methods to `ActionDelegate` to manage asynchronous execution lifecycle (`retainForAsyncExecution`, `releaseAfterAsyncExecution`, `clearAfterExecution`).
- Enhanced `AsyncRunner` with delegate state tracking and custom `actionsExecutor`.
- Added `async_run.xml` test Petri net and corresponding test cases to validate asynchronous action handling.
…te management and enhanced executor configuration.

- Added methods to `ActionDelegate` to manage asynchronous execution lifecycle (`retainForAsyncExecution`, `releaseAfterAsyncExecution`, `clearAfterExecution`).
- Enhanced `AsyncRunner` with delegate state tracking and custom `actionsExecutor`.
- Added `async_run.xml` test Petri net and corresponding test cases to validate asynchronous action handling.
- Update `server-patterns` to replace `/manage/**` with `/manage/health`
- Configure `management.endpoint.shutdown.enabled` as `false` across properties
- Expand `management.endpoints.web.exposure.include` for additional actuator endpoints
…logic; consolidate MongoTemplate usage to default bean.
# Conflicts:
#	application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy
#	application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy
… and `TestHelper` for consistency and clarity.
…d` and deprecate `findByNetworkIdAndObjectId`; update tests and enums accordingly.
…tringQuery` with `BoolQuery`, introduce `FullTextField` model, and enhance wildcard handling.
…ce static imports with dynamic discovery, introduce `ACTION_IMPORT_PACKAGES`, and optimize class loading.
[NAE-2464] Release 1.0.1 Bugfixes
…ce static imports with dynamic discovery, introduce `ACTION_IMPORT_PACKAGES`, and optimize class loading.
renczesstefan and others added 9 commits July 30, 2026 12:02
- Update version to 7.0.2 in all affected `pom.xml` files
- Modify `Dockerfile` and `Dockerfile.multi-stage` to use version 7.0.2
- Adjust `docker-compose.yml` to reflect the new image version
Remove the dedicated actions executor bean
Log ambiguous automatic Groovy action imports instead of silently skipping them
Prevent dev profile from dropping MongoDB and Elasticsearch data by default
Update deprecated case repository methods to version 7.0.2
Remove obsolete migration MongoTemplate bean configuration
Validate Elasticsearch full-text field boost values before applying them
…r regex handling consistency in full-text search normalization methods.
…r string normalization in full-text search
…e string replacement in full-text search normalization
[NAE-2464] Post release fixes
@machacjozef machacjozef self-assigned this Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The release updates workflow resource identifiers, asynchronous action execution, Elasticsearch full-text search, Groovy action imports, actuator exposure, migration configuration, tests, and project versions.

Changes

Workflow platform updates

Layer / File(s) Summary
Resource identifier model and propagation
nae-object-library/..., application-engine/src/main/.../repositories/*, application-engine/src/main/.../migration/helpers/*
Process resources now use process identifiers with generated object IDs. Repository, task, migration, and deletion paths use the renamed identifier field.
Asynchronous action lifecycle
application-engine/src/main/groovy/.../AsyncRunner.groovy, .../ActionDelegate.groovy, application-engine/src/test/.../ActionDelegateTest.groovy, application-engine/src/test/resources/...
Async actions use an injected TaskExecutor. Delegate retention and release are guarded across submission and execution.
Full-text search parsing and validation
application-engine/src/main/java/.../ElasticCaseService.java, .../FullTextField.java, application-engine/src/test/java/.../elastic/service/*
Search now normalizes terms and builds required, case-insensitive wildcard queries with field boosts. Unit and integration tests cover parsing, escaping, matching, counts, and queue behavior.
Groovy action import discovery
application-engine/src/main/java/.../GroovyShellFactory.java, .../GroovyShellConfiguration.java
Action classes are discovered from configured recursive packages. Duplicate names, explicit imports, test classes, and scan failures receive defined handling.
Runtime configuration and release alignment
Dockerfile*, docker-compose.yml, application-engine/src/main/resources/*, pom.xml, */pom.xml
Management endpoints use narrower or explicit exposure rules. Migration configuration and project version properties are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GroovyAction
  participant AsyncRunner
  participant ActionDelegate
  participant TaskExecutor
  GroovyAction->>AsyncRunner: submit closure
  AsyncRunner->>ActionDelegate: retainForAsyncExecution
  AsyncRunner->>TaskExecutor: execute Runnable
  TaskExecutor->>ActionDelegate: releaseAfterAsyncExecution
  ActionDelegate->>ActionDelegate: clear after execution completes
Loading

Possibly related PRs

Suggested labels: Large, improvement, breaking change

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the 7.0.2 release, which is the primary purpose of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added breaking change Fix or feature that would cause existing functionality doesn't work as expected improvement A change that improves on an existing feature Large labels Aug 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/roles/ProcessRole.java (1)

62-68: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix the field mismatch between the guard and the constructed ProcessResourceId.

The guard checks this.getProcessId() == null, but the else branch constructs _id from processIdentifier, not processId. Since fields cannot be set before a no-arg constructor runs, getProcessId() is always null here, so the else branch is unreachable in practice. This mismatch indicates the guard was not updated when the identifier field changed from processId to processIdentifier.

Check processIdentifier instead of processId, to align the guard with the value actually used to build _id.

🐛 Proposed fix
     public ProcessRole() {
-        if (this.getProcessId() == null) {
+        if (this.getProcessIdentifier() == null) {
             _id = new ProcessResourceId();
         } else {
             _id = new ProcessResourceId(processIdentifier, new ObjectId());
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/roles/ProcessRole.java`
around lines 62 - 68, Update the no-argument ProcessRole constructor’s guard to
check processIdentifier instead of getProcessId(), keeping the ProcessResourceId
construction aligned with the identifier used in the else branch.
nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java (1)

95-129: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fix the short process ID round-trip for non-ASCII identifiers.

generateShortProcessIdentifier() encodes processIdentifier.getBytes() via new BigInteger(1, ...), and decodeShortProcessIdentifier() uses number.toByteArray() to decode. BigInteger.toByteArray() returns a minimal two's-complement result including a sign byte, so any encoded process identifier whose encoding produces a leading byte with bit 7 set is decoded with a spurious leading NUL byte. Use a fixed positive sentinel byte before encoding so the magnitude has an explicit zero sign, then strip that byte on decode; reuse one decoded BigInteger for both decode methods.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java`
around lines 95 - 129, Update generateShortProcessIdentifier to prepend a fixed
zero sentinel byte before constructing the positive BigInteger, ensuring the
encoded magnitude preserves the original byte sequence. In
decodeShortProcessIdentifier, convert the decoded BigInteger to bytes once and
remove that sentinel before creating the identifier, reusing the same decoded
value across the decode paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@application-engine/src/main/groovy/com/netgrif/application/engine/AsyncRunner.groovy`:
- Around line 42-44: Update AsyncRunner.execute and its actionsExecutor
configuration to use a dedicated, bounded executor for action-triggered
asynchronous work instead of the shared qualified taskExecutor bean. Keep the
executor isolated from unrelated `@Async` workloads while preserving execute’s
Runnable submission behavior.
- Around line 52-72: Add a focused unit test for AsyncRunner.findActionDelegate
using an outer wrapper closure delegated to an ActionDelegate and a nested
closure passed to async.run, verifying the delegate is found through the nested
closure traversal. Ensure the test reflects real action-closure wiring and fails
if the delegate/owner traversal no longer reaches the ActionDelegate.
- Around line 21-40: Update the Runnable in AsyncRunner.run so exceptions from
closure() are caught, logged with log.error(...) including the throwable, and
then rethrown or otherwise handled consistently with the existing async failure
behavior; preserve the finally block’s release(actionDelegate, released)
cleanup.

In
`@application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy`:
- Around line 465-467: Update CaseMigrationHelper.migratePetriNet at
application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy:465-467
to preserve the case’s old resource ID and migrate linked task references when
setting the new _id. In TaskMigrationHelper at
application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/TaskMigrationHelper.groovy:271-276,
set oldTask.caseId to the migrated case string ID before saving.

In
`@application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java`:
- Around line 433-446: Update the full-text query construction around the
fullTextTerms/fullTextFields loop to avoid generating leading-wildcard wildcard
clauses for every term and field. Preserve substring matching by querying an
appropriately configured n-gram subfield with match queries, or otherwise retain
prefix-searchable behavior without prepending “*”; ensure the resulting per-term
field matching and boost semantics remain intact.
- Around line 559-573: The normalizeFullTextSearch method should not call
Matcher.quoteReplacement, since it escapes dollar signs and corrupts terms such
as $100 before wildcard escaping. Replace that normalization step with direct
backslash handling, preserve the existing whitespace and dangling-escape
cleanup, and remove the now-unused Matcher import.

In
`@application-engine/src/main/java/com/netgrif/application/engine/event/GroovyShellFactory.java`:
- Around line 27-31: Update GroovyShellFactory’s ACTION_IMPORT_PACKAGES
initialization to source scanned packages from the existing ActionsProperties
configuration instead of a hardcoded list. Reuse the configured imports,
starImports, or staticStarImports values as appropriate, preserving the current
defaults when no values are configured and allowing consumers to add packages
without code changes.
- Around line 63-81: Update findAllActionImportClasses and the class-discovery
flow so an individual class load or scan failure is logged and skipped rather
than propagated. Ensure loadClass handles both ClassNotFoundException and
linkage-related failures, while findAllClassesUsingClassLoader tolerates
per-class discovery failures; preserve discovery of all remaining valid classes
so getGroovyShell can initialize successfully.
- Around line 144-147: Update the resource filtering loop in GroovyShellFactory
to identify and exclude test classes using build-agnostic classpath or source
metadata rather than matching resource.getDescription() against “test-classes”.
Preserve inclusion of production resources and use an explicit configuration,
marker type, or equivalent reliable test-class distinction.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/repositories/CaseRepository.java`:
- Around line 41-65: Add legacy-field fallback handling for composite ID
lookups: in CaseRepository, make findById() try the deprecated
_id.shortProcessId query when findByNetworkIdentifierAndObjectId() finds
nothing; apply the equivalent migration/fallback in ProcessRoleRepository; and
update CaseEventHandler delete-ID construction to support legacy shortProcessId
values. Affected sites:
application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/repositories/CaseRepository.java:41-65,
application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java:129-156,
and
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseEventHandler.java:60.

In `@application-engine/src/main/resources/application.yaml`:
- Around line 124-129: Remove heapdump from the
management.endpoints.web.exposure.include allowlist and delete the
management.endpoint.heapdump access: unrestricted configuration. Keep the
remaining actuator endpoint exposure and shutdown settings unchanged.

In
`@application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy`:
- Around line 183-197: Strengthen testAsyncRunAction by asserting the
asynchronous action’s observable result, not only successful net import and case
creation. Exercise the retain/release lifecycle through the async action in
async_run.xml—preferably by writing to a case field and polling until the
expected value appears with a bounded timeout, or by directly testing
AsyncRunner/ActionDelegate with a controllable TaskExecutor—so delegate lookup
or cleared fields cause the test to fail.
- Around line 191-195: Update the CreateCaseParams construction in
ActionDelegateTest to use the processIdentifier builder with the net’s process
identifier instead of processId with net.getNet().getStringId(), ensuring
createCase resolves the intended process identity.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java`:
- Line 4: Remove the class-level `@Setter` from ProcessResourceId and eliminate
setters for the identity fields objectId, shortProcessIdentifier, and
shortProcessId; preserve constructor initialization and retain or scope setters
only for fields that genuinely require external mutation.
- Around line 28-59: Backfill the non-deprecated shortProcessIdentifier from
legacy shortProcessId when ProcessResourceId data is loaded, including persisted
process resources and Cases, before getFullId() is used. Update
CaseEventHandler.onAfterDelete and TaskEventHandler to use the normalized
shortProcessIdentifier value while retaining compatibility with legacy
documents.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java`:
- Around line 177-181: Update the lazy initialization guard in get_id() to check
processIdentifier, matching the field passed to ProcessResourceId and preventing
null-based identifiers. Apply the same field alignment to the Task() constructor
guard if retained, without changing the existing setter-based initialization
behavior.

---

Outside diff comments:
In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/roles/ProcessRole.java`:
- Around line 62-68: Update the no-argument ProcessRole constructor’s guard to
check processIdentifier instead of getProcessId(), keeping the ProcessResourceId
construction aligned with the identifier used in the else branch.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java`:
- Around line 95-129: Update generateShortProcessIdentifier to prepend a fixed
zero sentinel byte before constructing the positive BigInteger, ensuring the
encoded magnitude preserves the original byte sequence. In
decodeShortProcessIdentifier, convert the decoded BigInteger to bytes once and
remove that sentinel before creating the identifier, reusing the same decoded
value across the decode paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2bfd2081-7281-461d-8634-52f525b81fca

📥 Commits

Reviewing files that changed from the base of the PR and between 3d8cccd and 619465d.

📒 Files selected for processing (40)
  • Dockerfile
  • Dockerfile.multi-stage
  • application-engine/pom.xml
  • application-engine/src/main/groovy/com/netgrif/application/engine/AsyncRunner.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/PetriNetMigrationHelper.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/TaskMigrationHelper.groovy
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/MongoClientConfiguration.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/MigrationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java
  • application-engine/src/main/java/com/netgrif/application/engine/elastic/service/model/FullTextField.java
  • application-engine/src/main/java/com/netgrif/application/engine/event/GroovyShellFactory.java
  • application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/roles/ProcessRoleRepository.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/repositories/CaseRepository.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseEventHandler.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java
  • application-engine/src/main/resources/application-old-dev.properties
  • application-engine/src/main/resources/application-old.properties
  • application-engine/src/main/resources/application.yaml
  • application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignRemoveTest.groovy
  • application-engine/src/test/java/com/netgrif/application/engine/elastic/service/ElasticCaseServiceIntegrationTest.java
  • application-engine/src/test/java/com/netgrif/application/engine/elastic/service/ElasticCaseServiceTest.java
  • application-engine/src/test/resources/application-test.yaml
  • application-engine/src/test/resources/petriNets/async_run.xml
  • docker-compose.yml
  • nae-object-library/pom.xml
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/roles/ProcessRole.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Case.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java
  • nae-spring-core-adapter/pom.xml
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/workflow/domain/Task.java
  • nae-user-ce/pom.xml
  • nae-user-common/pom.xml
  • pom.xml

Comment on lines +465 to 467
ProcessResourceId newCaseId = new ProcessResourceId(newNet.getIdentifier(), useCase.get_id().getObjectId())
useCase.set_id(newCaseId)
useCase.setPetriNetObjectId(newNet.objectId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Migrate Task.caseId with the case resource ID.

migratePetriNet changes Case._id, so the case string ID changes. TaskService.createFromTransition stores that value in Task.caseId. updateTaskPermissions updates process fields only. Existing tasks can retain the old case ID and then disappear from case task queries, reloads, and deletion.

  • application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy#L465-L467: Preserve the old case ID and migrate linked task references when the case ID changes.
  • application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/TaskMigrationHelper.groovy#L271-L276: Set oldTask.caseId to the migrated case string ID before saving the task.
📍 Affects 2 files
  • application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy#L465-L467 (this comment)
  • application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/TaskMigrationHelper.groovy#L271-L276
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy`
around lines 465 - 467, Update CaseMigrationHelper.migratePetriNet at
application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy:465-467
to preserve the case’s old resource ID and migrate linked task references when
setting the new _id. In TaskMigrationHelper at
application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/TaskMigrationHelper.groovy:271-276,
set oldTask.caseId to the migrated case string ID before saving.

Comment on lines +433 to +446
fullTextTerms.forEach(term -> {
BoolQuery.Builder termQuery = new BoolQuery.Builder();
String wildcardValue = "*" + escapeWildcardValue(term) + "*";

fullTextFields.forEach(fullTextField -> termQuery.should(QueryBuilders.wildcard(builder -> builder
.field(fullTextField.field())
.value(wildcardValue)
.caseInsensitive(true)
.boost(fullTextField.boost())
)));

termQuery.minimumShouldMatch("1");
fullTextQuery.must(termQuery.build()._toQuery());
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Leading wildcards on every term and field can degrade search latency.

Each normalized term produces one wildcard clause per configured field, and each value starts with *. A leading wildcard cannot use the term prefix of the inverted index, so Elasticsearch scans all terms of the field. The cost scales with terms x configured fields x index cardinality. Configured patterns such as dataSet.*.fulltextValue expand this further. The previous QueryStringQuery did not force a leading wildcard on every term.

Measure the latency on a production-sized index. If substring matching is required, index an n-gram or edge n-gram subfield and query it with a match query instead of a wildcard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java`
around lines 433 - 446, Update the full-text query construction around the
fullTextTerms/fullTextFields loop to avoid generating leading-wildcard wildcard
clauses for every term and field. Preserve substring matching by querying an
appropriately configured n-gram subfield with match queries, or otherwise retain
prefix-searchable behavior without prepending “*”; ensure the resulting per-term
field matching and boost semantics remain intact.

Comment on lines +183 to +197
@Test
void testAsyncRunAction() {
ImportPetriNetEventOutcome net = petriNetService.importPetriNet(ImportPetriNetParams.with()
.xmlFile(new FileInputStream("src/test/resources/petriNets/async_run.xml"))
.releaseType(VersionType.MAJOR)
.author(superCreator.getLoggedSuper())
.build())
assert net.getNet() != null
CreateCaseEventOutcome outcome = workflowService.createCase(CreateCaseParams.with()
.processId(net.getNet().getStringId())
.title("Test title")
.author(userService.getLoggedOrSystem())
.build())
assert outcome.getCase() != null
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Strengthen testAsyncRunAction to validate the async safety mechanism, not just import/case creation.

This test only asserts net.getNet() != null and outcome.getCase() != null. The async action in async_run.xml only calls println, so this test passes even if findActionDelegate fails to locate the ActionDelegate, or if the delegate's fields are nulled before the async closure runs — both of which would only surface as an uncaught exception on a background thread that does not fail the JUnit test.

Consider adding an assertion path that actually exercises the retain/release lifecycle, for example by having the async action write to a case field so the test can poll for the write with a timeout, or by unit-testing AsyncRunner/ActionDelegate directly against a controllable TaskExecutor.

Do you want help drafting such a test?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy`
around lines 183 - 197, Strengthen testAsyncRunAction by asserting the
asynchronous action’s observable result, not only successful net import and case
creation. Exercise the retain/release lifecycle through the async action in
async_run.xml—preferably by writing to a case field and polling until the
expected value appears with a bounded timeout, or by directly testing
AsyncRunner/ActionDelegate with a controllable TaskExecutor—so delegate lookup
or cleared fields cause the test to fail.

Comment on lines +191 to +195
CreateCaseEventOutcome outcome = workflowService.createCase(CreateCaseParams.with()
.processId(net.getNet().getStringId())
.title("Test title")
.author(userService.getLoggedOrSystem())
.build())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect CreateCaseParams for processId/processIdentifier builder methods and their semantics.
fd -a CreateCaseParams.java
fd -a CreateCaseParams.java --exec cat {}
rg -n '\.processId\(|\.processIdentifier\(' --type=groovy --type=java

Repository: netgrif/application-engine

Length of output: 2653


🏁 Script executed:

#!/bin/bash
# Inspect all createCase usage and CreateCaseParams field resolution.
set -e
rg -n 'createCase\(|processId|processIdentifier' --type=groovy --type=java -C 2
python3 - <<'PY'
from pathlib import Path
p = Path('/home/jailuser/git/application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy')
if p.exists():
    lines = p.read_text(encoding='utf-8').splitlines()
    for i in range(185, 201):
        print(f"{i}: {lines[i-1]}")
PY

Repository: netgrif/application-engine

Length of output: 164


Use the process identifier builder instead of .processId(...).

CreateCaseParams still defines both processId and processIdentifier, but they represent different values: processId is the PetriNet string id, while processIdentifier is the process identifier used by ActionDelegate.createCase(String identifier, ...). Passing net.getNet().getStringId() here can create the case against the wrong identity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/test/groovy/com/netgrif/application/engine/action/ActionDelegateTest.groovy`
around lines 191 - 195, Update the CreateCaseParams construction in
ActionDelegateTest to use the processIdentifier builder with the net’s process
identifier instead of processId with net.getNet().getStringId(), ensuring
createCase resolves the intended process identity.

package com.netgrif.application.engine.objects.workflow.domain;

import lombok.Getter;
import lombok.Setter;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether a custom Mongo converter exists for ProcessResourceId that would make Lombok setters unnecessary for deserialization.
rg -n -C5 'ProcessResourceId' application-engine/src/main/java/com/netgrif/application/engine/configuration/MongoClientConfiguration.java
rg -n --type=java 'Converter<.*ProcessResourceId|ProcessResourceId.*Converter'

Repository: netgrif/application-engine

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -u

echo "Locate ProcessResourceId file:"
git ls-files | rg 'ProcessResourceId\.java$|MongoClientConfiguration\.java$' || true

echo
echo "ProcessResourceId.java:"
file=$(git ls-files | rg 'nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId\.java$' | head -n1)
if [ -n "${file:-}" ]; then
  wc -l "$file"
  cat -n "$file"
fi

echo
echo "Search ProcessResourceId usages:"
rg -n --type=java 'ProcessResourceId|shortProcessIdentifier|objectId' . | head -n 200

Repository: netgrif/application-engine

Length of output: 43733


🌐 Web query:

Spring Data MongoDB DeserializationMutatingObjectInstantiator private constructor annotation field name getter setter

💡 Result:

In Spring Data MongoDB, entity instantiation during deserialization is handled by the mapping subsystem, which uses reflection to call appropriate constructors or factory methods [1][2]. While "DeserializationMutatingObjectInstantiator" is not a standard, public API class, Spring Data allows you to control object creation effectively using constructor annotations [1][3][4]. To use a private constructor for deserialization, annotate it with @PersistenceCreator (formerly known as @PersistenceConstructor) [1][3][4][5]. Spring Data can invoke private, protected, or package-private constructors once they are marked with this annotation [3][2][5]. Key requirements and behaviors for this approach: 1. Constructor Argument Matching: Spring Data maps constructor parameters to document fields by name [3][2][4]. For this to work, your compiled Java class must contain parameter name information [3][6]. This is typically achieved by compiling with the -parameters flag (standard in modern Java/Maven/Gradle setups) or by including @ConstructorProperties on the constructor [4][7][6]. 2. Field Names: The parameter names in your constructor should match the field names in the MongoDB document [4][6]. If your MongoDB field names differ (e.g., using @Field(name = "custom_name")), the mapping subsystem will use that custom name during resolution [4][6]. 3. Getters and Setters: While Spring Data uses the constructor to instantiate the object, it still requires appropriate getters to access field values and (if the object is not immutable) setters or other population mechanisms to fully hydrate the entity after instantiation [8][6]. 4. Alternative for Advanced Customization: If you need to perform complex custom logic during instantiation that goes beyond simple constructor mapping, you can implement the EntityInstantiator interface or use a static factory method annotated with @PersistenceCreator [1][2][9]. For most use cases, a private constructor annotated with @PersistenceCreator is the recommended way to enforce immutability or handle specific initialization logic while keeping business logic clean [10][5].

Citations:


🌐 Web query:

site:docs.spring.io/spring-data/mongodb/reference Spring Data MongoDB no setter required annotations field constructor immutable

💡 Result:

To map immutable objects in Spring Data MongoDB without requiring setter methods, you should use constructor-based mapping [1][2]. Spring Data MongoDB automatically detects constructors to materialize entities, and using immutable objects is a recommended practice as it avoids mutable state and can offer performance benefits [1]. To ensure Spring Data correctly uses your constructor for mapping: 1. Provide an all-arguments constructor that includes all properties of the entity [1][2]. 2. If your class has multiple constructors, annotate the desired constructor (or a static factory method) with @PersistenceCreator [1][2]. 3. Ensure the constructor parameters are mapped by name to the keys in the MongoDB document. This often requires compiling your code with debug information enabled (-g) or using the -parameters flag with the Java compiler (javac) to preserve parameter names [1]. Using immutable objects (such as Java Records, which are ideal for this purpose) allows Spring Data to instantiate objects directly through the constructor [1][3]. If you cannot use an all-arguments constructor or require more complex instantiation logic, the @PersistenceCreator annotation explicitly instructs the mapping subsystem on which constructor or static factory method to invoke during object materialization [1][2]. [1][2][3]

Citations:


🏁 Script executed:

#!/bin/bash
set -u

echo "Check whether Lombok `@Setter` produces public setters:"
python3 - <<'PY'
from pathlib import Path
import re
text = Path('nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java').read_text()
print('`@Setter` present:', '`@Setter`' in text)
for name in ['objectId', 'shortProcessIdentifier', 'shortProcessId']:
    for method in [f'void set{name.capitalize()}({_name})', f'public void set{name.capitalize()}({_name})', f'private void set{name.capitalize()}({_name})']:
        print(f'{name} setter signature found:', method.replace('_', ''))
PY

echo
echo "Check whether any ProcessResourceId setter is called in Java sources:"
rg -n '\.set(ObjectId|setObjectId|setShortProcess|setShortProcessIdentifier|setShortProcessId)\(' --type=java . || true

echo
echo "Check whether hash-based collections are populated with ProcessResourceId before mutation:"
rg -n --type=java 'new (HashSet|HashMap|LinkedHashMap|TreeMap)|\bSet<ProcessResourceId>\b|Map<ProcessResourceId|Collection<ProcessResourceId>|roleIds|negateRoleIds' .

Repository: netgrif/application-engine

Length of output: 50382


Make ProcessResourceId fields immutable.

@Setter exposes public setters for objectId, shortProcessIdentifier, and shortProcessId, but hashCode() and equals() depend on the identity fields. Keep these fields effectively immutable after construction, and scope or remove @Setter to the fields that really need external mutation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java`
at line 4, Remove the class-level `@Setter` from ProcessResourceId and eliminate
setters for the identity fields objectId, shortProcessIdentifier, and
shortProcessId; preserve constructor initialization and retain or scope setters
only for fields that genuinely require external mutation.

Comment on lines +28 to +59

private String shortProcessIdentifier;

@Deprecated(since = "7.0.0", forRemoval = true)
private String shortProcessId;

public ProcessResourceId() {
this.objectId = new ObjectId();
this.shortProcessId = NONE_SHORT_ID_VALUE;
}

public ProcessResourceId(ObjectId processId) {
this.objectId = new ObjectId();
this.shortProcessId = generateShortProcessId(processId.toString());
this.shortProcessIdentifier = NONE_SHORT_ID_VALUE;
}

public ProcessResourceId(String processId, String objectId) {
public ProcessResourceId(String processIdentifier, String objectId) {
this.objectId = new ObjectId(objectId);
this.shortProcessId = generateShortProcessId(processId);
this.shortProcessIdentifier = generateShortProcessIdentifier(processIdentifier);
}

public ProcessResourceId(String processId, ObjectId objectId) {
public ProcessResourceId(String processIdentifier, ObjectId objectId) {
this.objectId = objectId;
this.shortProcessId = generateShortProcessId(processId);
this.shortProcessIdentifier = generateShortProcessIdentifier(processIdentifier);
}

public ProcessResourceId(String compositeId) {
String[] parts = compositeId.split("-");
String[] parts = compositeId.split(ID_SEPARATOR);
if (parts.length != 2) {
throw new IllegalArgumentException("Invalid composite ID format: " + compositeId);
}
this.shortProcessId = parts[0];
this.shortProcessIdentifier = parts[0];
this.objectId = new ObjectId(parts[1]);
}

public String getFullId() {
return shortProcessId + ID_SEPARATOR + objectId.toHexString();
return shortProcessIdentifier + ID_SEPARATOR + objectId.toHexString();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find remaining single-argument ProcessResourceId constructions.
rg -n --type=java 'new ProcessResourceId\(\s*[A-Za-z_][A-Za-z0-9_.]*\s*\)'

# Check migration helpers for shortProcessIdentifier backfill logic.
fd . application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers --exec rg -n -C5 'shortProcessIdentifier|shortProcessId'

Repository: netgrif/application-engine

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -e

echo "== locate ProcessResourceId and related symbols =="
fd -i 'ProcessResourceId|CaseEventHandler|migration' .

echo
echo "== ProcessResourceId outline and relevant contents =="
file=$(fd -i 'ProcessResourceId.java' . | head -n 1)
echo "file=$file"
wc -l "$file"
cat -n "$file"

echo
echo "== raw ProcessResourceId.java grep =="
rg -n 'ProcessResourceId|shortProcessIdentifier|shortProcessId|generateShortProcessIdentifier|getFullId|equals|hashCode' "$file"

echo
echo "== Java usages of ProcessResourceId =="
rg -n --type=java 'ProcessResourceId' .

echo
echo "== non-Java usages of ProcessResourceId/shortProcessIdentifier/shortProcessId =="
rg -n 'ProcessResourceId|shortProcessIdentifier|shortProcessId' . --glob '!target/**' --glob '!build/**' --glob '!node_modules/**' | head -n 200

echo
echo "== migration-related files =="
git ls-files | rg -i 'migration|migrate|helper' | head -n 200

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -u

echo "== candidate files =="
fd -i -a 'ProcessResourceId.java|CaseEventHandler.java|.*migration.*Helper.*|.*MigrationHelper.*' . | sed 's#^\./##' | head -n 200

echo
echo "== ProcessResourceId.java =="
file=$(fd -i 'ProcessResourceId.java' . | head -n 1)
if [ -n "${file:-}" ]; then
  wc -l "$file"
  cat -n "$file"
fi

echo
echo "== targeted ProcessResourceId constructors/usages == "
rg -n --type=java --glob '*.java' 'new ProcessResourceId\s*\(' . || true

echo
echo "== targeted shortProcessIdentifier/shortProcessId direct reads == "
rg -n --type=java --glob '*.java' 'shortProcessIdentifier|shortProcessId' . --max-count 120 || true

echo
echo "== migration helpers target =="
mig=application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers
if [ -d "$mig" ]; then
  find "$mig" -maxdepth 2 -type f 2>/dev/null | sort
  for f in "$mig"/CaseMigrationHelper.* "$mig"/MigrationHelper.* "$mig"/*MigrationHelper.* "$mig"/*Helper.*; do
    [ -f "$f" ] || continue
    echo "--- $f"
    wc -l "$f"
    rg -n -C4 'shortProcessIdentifier|shortProcessId|ProcessResourceId|case|Case|collection' "$f" || true
  done
else
  echo "migration helpers dir missing: $mig"
fi

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -u

echo "== ProcessRole constructors and _id usages =="
file=$(fd -i 'ProcessRole.java' . | head -n 1)
if [ -n "${file:-}" ]; then
  wc -l "$file"
  rg -n -C6 'ProcessResourceId|ProcessRole|set_id|_id|processIdentifier|shortProcessIdentifier|shortProcessId' "$file"
fi

echo
echo "== ProcessResource usages by id(String id) =="
rg -n --type=java --glob '*.java' '\.set_id\(new ProcessResourceId\([^(]+\)\)|new ProcessResourceId\([^(]+\)' .

echo
echo "== CaseEventHandler relevant section =="
casefile=$(fd -i 'CaseEventHandler.java' . | head -n 1)
if [ -n "${casefile:-}" ]; then
  wc -l "$casefile"
  sed -n '1,110p' "$casefile" | cat -n
fi

echo
echo "== behavioral probe: current ProcessResourceId(String) null behavior =="
tmpdir=$(mktemp -d)
cat > "$tmpdir/ProcessResourceIdProbe.java" <<'JAVA'
public class ProcessResourceIdProbe {
    private static final String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static String generateShortProcessIdentifier(String processIdentifier) {
        if (processIdentifier == null || processIdentifier.isEmpty()) return null;
        try {
            java.math.BigInteger number = new java.math.BigInteger(1, processIdentifier.getBytes());
            StringBuilder sb = new StringBuilder();
            while (!number.equals(java.math.BigInteger.ZERO)) {
                sb.append(CHARS.charAt(number.mod(new java.math.BigInteger(String.valueOf(CHARS.length()))).intValue()));
                number = number.divide(new java.math.BigInteger(String.valueOf(CHARS.length())));
            }
            return sb.reverse().toString();
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid input string for encoding: " + processIdentifier);
        }
    }
    public static void main(String[] args) {
        String[] inputs = {null, "", "none", "process-foo"};
        for (String s : inputs) {
            String generated = generateShortProcessIdentifier(s);
            String fullId = generated + "-" + "123456789012345678901234";
            System.out.println("input=" + (s == null ? "null" : '"' + s + '"') + " generated=" + (generated == null ? "null" : '"' + generated + '"') + " fullId=" + fullId);
        }
    }
}
JAVA
cd "$tmpdir"
javac ProcessResourceIdProbe.java 2>/tmp/javac.out
java ProcessResourceIdProbeProbe
echo
echo "javac out:"
cat /tmp/javac.out

echo
echo "== migration helper targeted field searches =="
for f in \
  application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/AbstractMigrationHelper.groovy \
  application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/CaseMigrationHelper.groovy \
  application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/TaskMigrationHelper.groovy \
  application-engine/src/main/groovy/com/netgrif/application/engine/migration/helpers/PetriNetMigrationHelper.groovy
do
  echo "--- $f ---"
  wc -l "$f"
  rg -n -C3 'shortProcessIdentifier|shortProcessId|migrate|ProcessResourceId|updateAll|forEach|mongoTemplate|collection' "$f" || true
done

Repository: netgrif/application-engine

Length of output: 7575


🏁 Script executed:

#!/bin/bash
set -u

echo "== search all tracked files for shortProcessId field writes =="
rg -n 'shortProcessId|shortProcessIdentifier' . --glob '!build/**' --glob '!target/**' || true

echo
echo "== relevant source files =="
for f in \
  nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java \
  nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Case.java \
  nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java \
  nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/roles/ProcessRole.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/repositories/CaseRepository.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/repositories/TaskSearchService.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseEventHandler.java
do
  echo "--- $f ---"
  wc -l "$f" 2>/dev/null || true
  rg -n -C3 'ProcessResourceId|shortProcessId|shortProcessIdentifier|_id' "$f" 2>/dev/null || true
done

echo
echo "== migration scripts/usages =="
rg -n -C4 'shortProcessIdentifier|shortProcessId|migrate|rename|renameCollection|updateMulti|updateFirst|setId|stringId|processIdentifier|ObjectId' application-engine/src/main/groovy application-engine/src/main/java --glob '!build/**' --glob '!target/**' --glob '!docs/**' || true

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -u

python3 - <<'PY'
CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def generate_short_process_identifier(process_identifier: str | None) -> str | None:
    if process_identifier is None or process_identifier == "":
        return None
    number = int.from_bytes(process_identifier.encode(), "big")
    short_id_builder = []
    char_array_length = len(CHARS)
    while number > 0:
        remainder = number % char_array_length
        short_id_builder.append(CHARS[remainder])
        number //= char_array_length
    return "".join(reversed(short_id_builder))

def full_id(short_process_identifier: str | None, oid: str = "123456789012345678901234") -> str:
    return (short_process_identifier or "") + "-" + oid

for input_value in [None, "", "none", "process-foo"]:
    short = generate_short_process_identifier(input_value)
    print(f"input={input_value!r} generated={short!r} fullId={full_id(short)}")
PY

echo
echo "== focused ProcessResourceId/processIdentifier shortProcessIdentifier migration-related searches =="
rg -n -C3 'shortProcessIdentifier|shortProcessId' \
  nae-object-library/src/main/java \
  application-engine/src/main/groovy \
  application-engine/src/main/java \
  --glob '!docs/**' || true

Repository: netgrif/application-engine

Length of output: 18467


🏁 Script executed:

#!/bin/bash
set -u

python3 - <<'PY'
CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def generate_short_process_identifier(process_identifier):
    if process_identifier is None or process_identifier == "":
        return None
    number = int.from_bytes(process_identifier.encode(), "big")
    short_id_builder = []
    char_array_length = len(CHARS)
    while number > 0:
        remainder = number % char_array_length
        short_id_builder.append(CHARS[remainder])
        number //= char_array_length
    return "".join(reversed(short_id_builder))

def full_id(short_process_identifier, oid="123456789012345678901234"):
    return (short_process_identifier or "") + "-" + oid

for input_value in [None, "", "none", "process-foo"]:
    short = generate_short_process_identifier(input_value)
    print(f"input={input_value!r} generated={short!r} fullId={full_id(short)}")
PY

Repository: netgrif/application-engine

Length of output: 447


Backfill shortProcessIdentifier for legacy composite IDs.

CaseEventHandler.onAfterDelete reads the persisted document._id.shortProcessIdentifier. Legacy documents only contain the deprecated _id.shortProcessId, while ProcessResourceId.getFullId() creates IDs without a backfill, so deleting those cases builds a malformed "null-<hex>" Elasticsearch document ID. Add migration logic that backfills shortProcessIdentifier from shortProcessId for process resources/Cases, and the same issue affects TaskEventHandler reading shortProcessId.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/ProcessResourceId.java`
around lines 28 - 59, Backfill the non-deprecated shortProcessIdentifier from
legacy shortProcessId when ProcessResourceId data is loaded, including persisted
process resources and Cases, before getFullId() is used. Update
CaseEventHandler.onAfterDelete and TaskEventHandler to use the normalized
shortProcessIdentifier value while retaining compatibility with legacy
documents.

Comment on lines 177 to 181
public Task() {
if (this.processId != null && !this.processId.isEmpty()) {
this._id = new ProcessResourceId(new ObjectId(this.processId));
if (this.processIdentifier != null && !this.processIdentifier.isEmpty()) {
this._id = new ProcessResourceId(this.processIdentifier, new ObjectId());
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fix the field mismatch in get_id()'s lazy initialization.

get_id() checks this.processId != null && !this.processId.isEmpty(), but the branch builds _id from this.processIdentifier. processId and processIdentifier are independently settable (setProcessId does not touch _id; only setProcessIdentifier does). If code calls setProcessId() without also calling setProcessIdentifier(), and _id has not yet been assigned, get_id() passes a null processIdentifier into new ProcessResourceId(...). generateShortProcessIdentifier then returns null for shortProcessIdentifier, and getStringId() produces "null-<hex>" instead of a valid task identifier.

Check processIdentifier in the guard, to match the field actually used to build _id.

As a secondary note, the Task() no-arg constructor at lines 177-181 has the same intent, but this.processIdentifier cannot be non-null at that point since no caller can set fields before a no-arg constructor runs. That branch is currently dead code; the eventual _id value is set later, either through setProcessIdentifier() or through the (fixed) get_id() lazy path.

🐛 Proposed fix
     public ProcessResourceId get_id() {
         if (this._id == null) {
-            this._id = this.processId != null && !this.processId.isEmpty()
+            this._id = this.processIdentifier != null && !this.processIdentifier.isEmpty()
                     ? new ProcessResourceId(this.processIdentifier, new ObjectId())
                     : new ProcessResourceId();
         }
         return this._id;
     }

Also applies to: 195-202

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/Task.java`
around lines 177 - 181, Update the lazy initialization guard in get_id() to
check processIdentifier, matching the field passed to ProcessResourceId and
preventing null-based identifiers. Apply the same field alignment to the Task()
constructor guard if retained, without changing the existing setter-based
initialization behavior.

@sonarqubecloud

sonarqubecloud Bot commented Aug 3, 2026

Copy link
Copy Markdown

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

Labels

breaking change Fix or feature that would cause existing functionality doesn't work as expected improvement A change that improves on an existing feature Large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants