Skip to content

Conversation

@Smotanka
Copy link

@Smotanka Smotanka commented Aug 27, 2025

Description

Creation of Nullable class to serialize nullable values in Plugin exchange
NAE-2184

Dependencies

No new Dependencies introduced.

Third party dependencies

No new Third Party Dependencies introduced.

Blocking Pull requests

None

How Has Been This Tested?

User tested in cluster environment with docker.

Test Configuration

Name Tested on
OS Fedora Workstation 42
Runtime Java 21
Dependency Manager Maven 3.9.9
Framework version Spring Boot 3.2.5
Run parameters
Other configuration

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes have been checked, personally or remotely, with @...
  • I have commented my code, particularly in hard-to-understand areas
  • I have resolved all conflicts with the target branch of the PR
  • I have updated and synced my code with the target branch
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing tests pass locally with my changes:
    • Lint test
    • Unit tests
    • Integration tests
  • I have checked my contribution with code analysis tools:
  • I have made corresponding changes to the documentation:
    • Developer documentation
    • User Guides
    • Migration Guides

Summary by CodeRabbit

  • New Features

    • Added a Nullable value wrapper offering Optional-like, functional APIs for safer handling and transformations of possibly-null values.
  • Configuration

    • Action import defaults extended to include Nullable and additional engine imports so it's available in action logic by default.
  • Stability

    • Reduces null-related edge cases by providing clear APIs for presence checks, mapping, fallbacks, and exceptions.
  • Chores

    • Minor import and formatting adjustments to integrate the new utility.

smetanka added 2 commits August 27, 2025 13:12
- Add Nullable utility class to handle nullable arguments in Plugins
@Smotanka Smotanka requested review from machacjozef and tuplle August 27, 2025 12:25
@Smotanka Smotanka added the improvement A change that improves on an existing feature label Aug 27, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 27, 2025

Walkthrough

Adds a new Nullable utility class, exposes it to Groovy actions via configuration and GroovyShell default imports, and adds a nullable(T) helper method in ActionDelegate. No existing control-flow semantics were altered.

Changes

Cohort / File(s) Summary
Nullable utility (new class)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java
Adds public final class Nullable<T> (Serializable) with factory methods (of, empty), presence checks (isPresent, isEmpty), functional transforms (map, flatMap, filter), fallback/orElse variants, Optional/Stream conversions, throw-on-empty helpers, and equals/hashCode/toString.
Action delegate updates
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
Adds import for com.netgrif.application.engine.objects.utils.Nullable and a generic helper method <T> Nullable<T> nullable(T value) { return Nullable.of(value) }; no other behavioral changes.
Groovy shell configuration
application-engine/src/main/java/.../configuration/groovy/GroovyShellConfiguration.java
Adds protected String[] getDefaultEngineImports() and applies its star-imports to the Groovy ImportCustomizer alongside existing action imports.
Configuration (YAML)
application-engine/src/main/resources/application.yaml
Extends actions.imports to include com.netgrif.application.engine.objects.utils.Nullable.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Config as GroovyShellConfiguration
    participant Imports as ImportCustomizer
    participant YAML as application.yaml
    participant Script as Groovy Action Script
    participant Delegate as ActionDelegate
    participant Nullable as Nullable<T>

    Note over Config, YAML: Startup/configuration
    Config->>Imports: addStarImports(getDefaultEngineImports())
    Config->>Imports: addImports(from YAML.actions.imports)
    YAML->>Config: actions.imports includes Nullable

    Note over Script, Imports: Script execution context
    Imports-->>Script: provide star-imports & imports (including Nullable)

    Script->>Delegate: calls nullable(value)
    Delegate->>Nullable: Nullable.of(value)
    Nullable-->>Delegate: Nullable<T> instance
    Delegate-->>Script: returns Nullable<T>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch NAE-2184

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java (7)

60-64: Avoid shadowing constant in empty()

Local variable EMPTY shadows the class constant and hurts readability. Return the cast directly.

-    public static <T> Nullable<T> empty() {
-        @SuppressWarnings("unchecked")
-        Nullable<T> EMPTY = (Nullable<T>) Nullable.EMPTY;
-        return EMPTY;
-    }
+    public static <T> Nullable<T> empty() {
+        @SuppressWarnings("unchecked")
+        return (Nullable<T>) EMPTY;
+    }

89-93: Enforce non-null consumer in ifPresent

The Javadoc promises non-null; enforce it to fail fast and match style used elsewhere.

-    public void ifPresent(Consumer<? super T> action) {
-        if (value != null) {
-            action.accept(value);
-        }
-    }
+    public void ifPresent(Consumer<? super T> action) {
+        Objects.requireNonNull(action, "action");
+        if (value != null) {
+            action.accept(value);
+        }
+    }

101-107: Validate both parameters in ifPresentOrElse

Add null checks for action and emptyAction to align with the contract.

-    public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
-        if (value != null) {
-            action.accept(value);
-        } else {
-            emptyAction.run();
-        }
-    }
+    public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
+        Objects.requireNonNull(action, "action");
+        Objects.requireNonNull(emptyAction, "emptyAction");
+        if (value != null) {
+            action.accept(value);
+        } else {
+            emptyAction.run();
+        }
+    }

231-233: Guard supplier in orElseGet

Objects.requireNonNull(supplier) prevents NPEs and documents intent.

-    public T orElseGet(Supplier<? extends T> supplier) {
-        return value != null ? value : supplier.get();
-    }
+    public T orElseGet(Supplier<? extends T> supplier) {
+        Objects.requireNonNull(supplier, "supplier");
+        return value != null ? value : supplier.get();
+    }

259-265: Guard exception supplier in orElseThrow(Supplier)

Validate the exceptionSupplier to avoid surprising NPEs.

-    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
-        if (value != null) {
-            return value;
-        } else {
-            throw exceptionSupplier.get();
-        }
-    }
+    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
+        Objects.requireNonNull(exceptionSupplier, "exceptionSupplier");
+        if (value != null) {
+            return value;
+        } else {
+            throw exceptionSupplier.get();
+        }
+    }

296-296: Canonicalize empty on deserialization (optional)

If you care about a single empty instance post-serialization, add readResolve() to return the canonical empty.

@@
     @Override
     public int hashCode() {
         return Objects.hashCode(value);
     }
 
+    private Object readResolve() {
+        return isEmpty() ? Nullable.empty() : this;
+    }
+
     /**
      * Returns a string representation of the object.

15-21: Unit tests request: core API and serialization

Please add focused tests: presence checks, map/flatMap/or semantics, both orElseThrow variants, equals/hashCode, stream(), and Java serialization round-trip (including empty canonicalization if added).

Also applies to: 89-107, 127-152, 164-195, 203-209, 217-247, 259-265

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1)

93-93: Drop the trailing semicolon; optional style cleanup

Groovy imports don’t require semicolons; align with surrounding imports.

-import com.netgrif.application.engine.objects.utils.Nullable;
+import com.netgrif.application.engine.objects.utils.Nullable
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3a75366 and 25e9980.

📒 Files selected for processing (3)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1 hunks)
  • application-engine/src/main/resources/application.yaml (1 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: Build
🔇 Additional comments (3)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java (1)

15-21: Clarify intended serialization channel (Java vs JSON)

If Plugin exchange uses JSON (Jackson), this class won’t round-trip by default (no bean-style getter/ctor). Consider a separate Jackson adapter or annotations (e.g., @jsonvalue on value exposure and a @JsonCreator factory) in a jackson-dedicated module to avoid adding compile deps here. Confirm the channel.

application-engine/src/main/resources/application.yaml (1)

162-162: Registered Nullable for Actions DSL — LGTM

Import addition looks correct and non-invasive.

application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1)

93-93: Confirm import necessity

Nullable isn’t referenced in this class. If DSL availability is handled solely via actions.imports, this import can be removed to avoid unused-import warnings.

coderabbitai[bot]
coderabbitai bot previously approved these changes Aug 27, 2025
- Added default engine imports in `GroovyShellConfiguration` for better functionality.
- Introduced a generic `nullable` method in `ActionDelegate` to handle optional values.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java (1)

38-45: Minor: document intent and narrow surface for future readers.

Consider adding a brief Javadoc explaining why these defaults are included and that subpackages aren’t covered (e.g., utils). This helps maintainers avoid “why isn’t X visible?” confusion.

Apply within this region:

@@
-    protected String[] getDefaultEngineImports() {
+    /**
+     * Default Groovy star-imports available to action scripts.
+     * Note: Applies only to the listed packages (not their subpackages).
+     */
+    protected String[] getDefaultEngineImports() {
         return new String[]{
                 "com.netgrif.application.engine.objects",
                 "com.netgrif.application.engine.adapter.spring",
                 "java.time"
         };
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 25e9980 and 8d8b1ce.

📒 Files selected for processing (2)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (3 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java (2)

11-12: No behavior change here.

This import doesn’t affect runtime or bean wiring in this class. If it’s intentional for upcoming edits, keep it; otherwise your linter may suggest removing it.


23-27: Adding default star-imports is fine; please confirm no ambiguity is introduced.

Star-importing additional packages can surface ambiguous simple names in Groovy scripts if another star-import provides a class with the same simple name. Your explicit imports (actionsProperties.getImports) still win, but unqualified references could now fail resolution. Verify scripts compile cleanly across modules after this change. If collisions appear, prefer explicit imports or fully qualified names in actions.

coderabbitai[bot]
coderabbitai bot previously approved these changes Aug 27, 2025
- Removed unnecessary static EMPTY field to simplify code and avoid redundancy.
- Simplified `toString` method to return an empty string when no value is present.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java (6)

24-26: Use a singleton EMPTY to avoid per-call allocations.

Optional-style reuse reduces GC churn while keeping equals/hashCode semantics.

     @Serial
     private static final long serialVersionUID = 8683452581122892189L;
 
+    private static final Nullable<?> EMPTY = new Nullable<>(null);
@@
-    public static <T> Nullable<T> empty() {
-        return new Nullable<>(null);
-    }
+    @SuppressWarnings("unchecked")
+    public static <T> Nullable<T> empty() {
+        return (Nullable<T>) EMPTY;
+    }

Also applies to: 58-60


182-191: Preserve laziness for suppliers and add explicit null checks only when needed.

Avoid failing when value is present; validate inputs only on the executed branch.

-    public Nullable<T> or(Supplier<? extends Nullable<? extends T>> supplier) {
-        Objects.requireNonNull(supplier);
-        if (isPresent()) {
-            return this;
-        } else {
-            @SuppressWarnings("unchecked")
-            Nullable<T> r = (Nullable<T>) supplier.get();
-            return Objects.requireNonNull(r);
-        }
-    }
+    public Nullable<T> or(Supplier<? extends Nullable<? extends T>> supplier) {
+        if (isPresent()) {
+            return this;
+        }
+        Objects.requireNonNull(supplier, "supplier must not be null");
+        @SuppressWarnings("unchecked")
+        Nullable<T> r = (Nullable<T>) supplier.get();
+        return Objects.requireNonNull(r, "supplier.get() returned null");
+    }
@@
-    public T orElseGet(Supplier<? extends T> supplier) {
-        return value != null ? value : supplier.get();
-    }
+    public T orElseGet(Supplier<? extends T> supplier) {
+        if (value != null) return value;
+        Objects.requireNonNull(supplier, "supplier must not be null");
+        return supplier.get();
+    }
@@
-    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
-        if (value != null) {
-            return value;
-        } else {
-            throw exceptionSupplier.get();
-        }
-    }
+    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
+        if (value != null) return value;
+        Objects.requireNonNull(exceptionSupplier, "exceptionSupplier must not be null");
+        throw exceptionSupplier.get();
+    }

Also applies to: 227-229, 255-261


85-89: Branch-specific null checks for callbacks.

Validate only the callback that is actually used; improves diagnostics while preserving laziness.

-        if (value != null) {
-            action.accept(value);
-        }
+        if (value != null) {
+            Objects.requireNonNull(action, "action must not be null").accept(value);
+        }
@@
-        if (value != null) {
-            action.accept(value);
-        } else {
-            emptyAction.run();
-        }
+        if (value != null) {
+            Objects.requireNonNull(action, "action must not be null").accept(value);
+        } else {
+            Objects.requireNonNull(emptyAction, "emptyAction must not be null").run();
+        }

Also applies to: 97-103


199-205: Minor: simplify stream() implementation.

-        if (isEmpty()) {
-            return Stream.empty();
-        } else {
-            return Stream.of(value);
-        }
+        return isEmpty() ? Stream.empty() : Stream.of(value);

301-303: Make toString() informative and align with Javadoc.

Empty string is ambiguous; prefer Optional-like format.

-        return value != null ? value.toString() : "";
+        return value != null ? "Nullable[" + value + "]" : "Nullable.empty";

22-31: Add tests for JSON round-trip and supplier laziness.

Two quick unit tests will de-risk usage in Groovy/Plugin exchange.

I can add:

  • Jackson round-trip: serialize Nullable.of("x") -> "x"; deserialize -> Nullable.of("x"); and Nullable.empty() -> null payload round-trip.
  • Laziness: nullable("v").or(null) doesn’t throw; nullable(null).or(() -> null) throws NPE with message.
    Do you want me to open a follow-up PR with these tests?
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8d8b1ce and 3f8aa8c.

📒 Files selected for processing (1)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (1)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java (1)

273-291: equals/hashCode: LGTM.

Value-based equality and Objects.hashCode are correct.

Comment on lines +33 to +41
/**
* Returns the value held by this instance.
*
* @return the value contained in this instance, or {@code null} if the instance is empty
*/
public T get() {
return value;
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add Jackson annotations to guarantee JSON round-trip (aligns with PR goal).

Without a bean-style getter (e.g., getValue) Jackson will likely serialize this as {}. Expose the wrapped value via @jsonvalue and enable delegating construction via @JsonCreator.

Apply:

 import java.io.Serial;
 import java.io.Serializable;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
@@
-    public T get() {
+    @JsonValue
+    public T get() {
         return value;
     }
@@
-    public static <T> Nullable<T> of(T value) {
+    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+    public static <T> Nullable<T> of(T value) {
         return new Nullable<>(value);
     }

Also applies to: 48-51

🤖 Prompt for AI Agents
In
nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java
around lines 33-41 (and also apply to lines 48-51), the wrapped value isn't
exposed for Jackson so instances serialize as {} — annotate the getter with
@JsonValue to serialize the contained value, and add @JsonCreator (use
Mode.DELEGATING) to the constructor or factory so Jackson can deserialize
directly into the wrapper; add the necessary imports for
com.fasterxml.jackson.annotation.JsonValue and
com.fasterxml.jackson.annotation.JsonCreator and apply the annotations to the
existing get() and constructor/factory accordingly.

@tuplle tuplle merged commit 5f2f4b5 into release/7.0.0-rev6.1 Aug 27, 2025
6 of 7 checks passed
@tuplle tuplle deleted the NAE-2184 branch August 27, 2025 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement A change that improves on an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants