Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.netgrif.application.engine.objects.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Indexable {
}
Comment on lines +3 to +11
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider making @Indexable @Inherited to support entity hierarchies

Right now @Indexable is not @Inherited, so subclasses won’t be treated as indexable unless they also declare the annotation. If your Mongo root entities are often concrete subclasses of abstract bases like Case, Task, or PetriNet, adding @Inherited would reduce the risk of missing indexes when new subclasses are introduced.

Example:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Indexable {
}

If you intentionally want only explicitly annotated concrete types to be indexable, the current setup is fine.

🤖 Prompt for AI Agents
In
nae-object-library/src/main/java/com/netgrif/application/engine/objects/annotations/Indexable.java
around lines 3 to 11, the annotation is not marked @Inherited so subclasses
won't inherit it; add the java.lang.annotation.Inherited import and annotate the
interface with @Inherited (keeping existing @Retention(RetentionPolicy.RUNTIME)
and @Target(ElementType.TYPE)) so concrete subclasses of annotated base types
are treated as indexable.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.netgrif.application.engine.objects.petrinet.domain;

import com.netgrif.application.engine.objects.annotations.Indexable;
import com.netgrif.application.engine.objects.annotations.Indexed;
import com.netgrif.application.engine.objects.auth.domain.ActorRef;
import com.netgrif.application.engine.objects.petrinet.domain.arcs.Arc;
import com.netgrif.application.engine.objects.petrinet.domain.arcs.reference.Referencable;
Expand All @@ -24,6 +26,7 @@
import java.util.*;
import java.util.stream.Collectors;

@Indexable
@QueryEntity
public abstract class PetriNet extends PetriNetObject {

Expand Down Expand Up @@ -57,6 +60,7 @@ public abstract class PetriNet extends PetriNetObject {

@Getter
@Setter
@Indexed
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify that initials is the optimal field to index.

The @Indexed annotation on the initials field addresses the missing indexes issue. However, the identifier field on line 36 has a comment suggesting it should be part of a compound index with version for uniqueness enforcement. Consider whether identifier should also be indexed independently or as part of a compound index, depending on query patterns.

Run the following script to analyze query patterns for both initials and identifier fields:


🏁 Script executed:

#!/bin/bash
# Description: Search for query patterns using initials and identifier fields to determine indexing priorities

echo "=== Searching for queries using 'initials' field ==="
rg -n --type=java -C3 'findBy.*Initials|initials.*equals|criteria.*initials|query.*initials' -i

echo -e "\n=== Searching for queries using 'identifier' field ==="
rg -n --type=java -C3 'findBy.*Identifier|identifier.*equals|criteria.*identifier|query.*identifier' -i

echo -e "\n=== Checking PetriNet repository/query usage ==="
ast-grep --pattern 'class $_ implements $$$Repository<PetriNet$$$> {
  $$$
}'

Length of output: 38196


Now I need to inspect the PetriNet.java file to see the actual index definitions and the comment mentioned in the review:<!-- [scratchpad_continue]
[key_finding_from_web_search]

  • MongoDB compound indexes support queries on the index prefix: a compound index on {identifier: 1, version: 1} can efficiently support:
    • Queries on identifier alone (prefix match)
    • Queries on identifier AND version together
    • But NOT queries on version alone (violates index prefix requirement)
  • If query patterns are:
    1. findByIdentifier() - single field query
    2. findByIdentifierAndVersion() - compound query
      Then a compound index on (identifier, version) covers both efficiently

[critical_missing_info]

  • Need to see PetriNet.java file to check:
    1. Current index definitions on identifier field
    2. Whether a compound index with version already exists
    3. The exact comment on line 36 mentioning the index strategy
    4. Whether @Indexed annotation is already on identifier

[next_action]

  • Search PetriNet.java for index definitions and line 36 comment
    [scratchpad_continue_end] -->

Now let me inspect the PetriNet.java file to see the current index definitions:

[/run_scripts]

Based on the query pattern analysis from the script output, here are my findings:

Query patterns for initials:

  • Single regex query: Criteria.where("initials").regex() (line 484 in PetriNetService.java)
  • Test assertion on retrieved object

Query patterns for identifier:

  • Direct repository lookups: findByIdentifier(), findByIdentifierAndVersion() (Repository methods)
  • Regex queries: Criteria.where("identifier").regex() (line 478)
  • Aggregation/grouping: Criteria.where("identifier").in() (line 305)
  • Multiple service calls using the identifier queries (lines 276, 289)
  • Heavy usage across search criteria (line 477)

The evidence shows:

  1. initials field: Used only in regex-based search criteria → @Indexed annotation is appropriate for this single-field query pattern
  2. identifier field: Used extensively in both single-field lookups (findByIdentifier()) AND compound queries (findByIdentifierAndVersion()). The comment on line 36 suggests a compound index with version for uniqueness enforcement, which aligns with MongoDB's compound index prefix capabilities—a compound index on (identifier, version) efficiently supports both single-field lookups on identifier and compound lookups.

Verify that initials is the optimal field to index.

The @Indexed annotation on initials is appropriate for the current query patterns, which primarily use single-field regex-based searches. However, query analysis reveals the identifier field is used far more extensively, including both standalone lookups (findByIdentifier()) and compound lookups with version (findByIdentifierAndVersion()). The comment on line 36 suggests identifier should be part of a compound index with version for uniqueness enforcement, which is architecturally sound: a compound index can support queries on all indexed fields including the first field.

Consider adding a compound index on (identifier, version) to support both query patterns efficiently, rather than indexing identifier alone. This aligns with the design intent noted in the comment and would improve performance for the frequently-used findByIdentifierAndVersion() lookups while also supporting findByIdentifier() queries via the index prefix.


🏁 Script executed:

#!/bin/bash
# Extract the PetriNet class definition with focus on indexes and line 36

cat nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java | head -100

Length of output: 2927


Add compound index on identifier and version fields to address the stated uniqueness requirement and optimize query performance.

The PR adds @Indexed to initials, but query pattern analysis reveals a critical gap: the identifier field is used far more extensively (6+ distinct query patterns including findByIdentifierAndVersion() and findByIdentifier()), yet has no index. The comment on line 36 explicitly states that "combination of identifier and version must be unique" and questions whether @CompoundIndex should be used—indicating this was the original intent.

The current implementation indexes only initials while leaving the heavier-queried identifier field unindexed. A compound index on (identifier, version) would:

  • Enforce the stated uniqueness constraint
  • Support both single-field findByIdentifier() lookups (via index prefix) and compound findByIdentifierAndVersion() queries
  • Dramatically improve query performance for the most frequently-used lookups

Add the missing compound index definition to the class level (using Spring Data's @CompoundIndex or MongoDB's equivalent) before or alongside the @Indexed annotation on initials.

🤖 Prompt for AI Agents
In
nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
around line 63, add a class-level compound index for the identifier and version
fields to enforce their uniqueness and improve query performance; specifically,
annotate the class with Spring Data's @CompoundIndex (or @CompoundIndexes)
declaring fields = "{'identifier': 1, 'version': 1}" and unique = true, leaving
the existing @Indexed on initials intact so both the compound
(identifier+version) and the initials index coexist.

private String initials;

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.netgrif.application.engine.objects.workflow.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.netgrif.application.engine.objects.annotations.Indexable;
import com.netgrif.application.engine.objects.auth.domain.ActorRef;
import com.netgrif.application.engine.objects.annotations.Indexed;
import com.netgrif.application.engine.objects.petrinet.domain.I18nString;
Expand All @@ -22,6 +23,7 @@
import java.util.stream.Collectors;

@Getter
@Indexable
@QueryEntity
public abstract class Case implements Serializable {

Expand All @@ -35,6 +37,7 @@ public abstract class Case implements Serializable {
private LocalDateTime lastModified;

@Setter
@Indexed
private String visualId;

@NotNull
Expand All @@ -48,6 +51,7 @@ public abstract class Case implements Serializable {

@NotNull
@Setter
@Indexed
private String processIdentifier;

@Setter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.netgrif.application.engine.objects.workflow.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.netgrif.application.engine.objects.annotations.Indexable;
import com.netgrif.application.engine.objects.annotations.Indexed;
import com.netgrif.application.engine.objects.auth.domain.AbstractUser;
import com.netgrif.application.engine.objects.petrinet.domain.I18nString;
Expand All @@ -24,6 +25,7 @@
import java.util.*;

@QueryEntity
@Indexable
@AllArgsConstructor
public abstract class Task implements Serializable {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.netgrif.application.engine.adapter.spring.configuration;

import com.netgrif.application.engine.objects.annotations.Indexable;
import com.netgrif.application.engine.objects.annotations.Indexed;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
Expand Down Expand Up @@ -36,7 +37,7 @@ public AbstractMongoIndexesConfigurator(MongoTemplate mongoTemplate) {
public void resolveIndexes() {
mappingContext.getPersistentEntities()
.stream()
.filter(it -> it.isAnnotationPresent(Document.class) && !getEntityIndexBlacklist().contains(it.getType()))
.filter(it -> it.isAnnotationPresent(Indexable.class) && !getEntityIndexBlacklist().contains(it.getType()))
.forEach(mongoPersistentEntity -> resolveIndexes(mongoPersistentEntity.getCollection(), mongoPersistentEntity.getType()));
}

Expand Down
Loading