Skip to content

Conversation

@machacjozef
Copy link
Member

@machacjozef machacjozef commented Jun 21, 2025

Description

Implements NAE-2129

Dependencies

Third party dependencies

No new dependencies were introduced

Blocking Pull requests

There are no dependencies on other PR

How Has Been This Tested?

  • <Name of a test [test file](link to the test file)>

Test Configuration

<Please describe configuration for tests to run if applicable, like program parameters, host OS, VM configuration etc.>

Name Tested on
OS
Runtime
Dependency Manager
Framework version
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

    • Introduced a plugin framework enabling dynamic registration and invocation of plugin entry points and methods.
    • Added support for annotating and discovering plugin entry points and methods.
    • Enabled runtime injection and management of plugins during application startup.
    • Provided a mechanism for filtering plugin listeners based on event types and dispatch methods.
  • Documentation

    • Added descriptive annotations and domain classes to clarify plugin structure and usage.

renczesstefan and others added 11 commits June 20, 2025 09:05
Introduced annotations (`EntryPoint` and `EntryPointMethod`) and corresponding domain models (`Plugin`, `EntryPoint`, `Method`, and `ListenerFilter`) to support a plugin architecture. These enable defining and managing plugins, their methods, and listener filters for remote invocation.
Introduced a new interface to define plugin registration configuration. It includes methods for retrieving the plugin name, version, and entry points. This provides a foundation for standardizing plugin integration.
Introduced core classes and services to enable a plugin architecture. This includes plugin injection, entry point management, and dynamic meta-class updates for ActionDelegate. The changes set the foundation for extensible plugin support within the system.
Introduced core classes and services to enable a plugin architecture. This includes plugin injection, entry point management, and dynamic meta-class updates for ActionDelegate. The changes set the foundation for extensible plugin support within the system.
The EntryPointLoaderService interface was unused and redundant, so it has been deleted to simplify the codebase. This helps maintain cleaner and more maintainable code.
Replaced the use of EntryPointLoaderService with ApplicationContext to retrieve PluginRegistrationConfiguration beans. This simplifies the process of loading plugins and their entry points, consolidating configuration management and improving maintainability.
Updated the PluginRunner to assign the plugin version from the configuration file instead of using a hardcoded value. This ensures better flexibility and easier version management for plugins.
Introduced PluginHolder as a new field in ActionDelegate and initialized it in the constructor. This change supports enhanced plugin management within the ActionDelegate logic.
- Enhanced PluginInjector with improved meta-class injection logic and documentation.
- Updated PluginRegistrationConfiguration to include metadata retrieval.
@machacjozef machacjozef requested a review from tuplle June 21, 2025 21:23
@coderabbitai
Copy link

coderabbitai bot commented Jun 21, 2025

Walkthrough

This change introduces a dynamic plugin architecture to the application. It adds new domain classes, annotations, and interfaces for plugin definition and registration, along with runtime injection and method invocation mechanisms. New components facilitate the discovery, registration, and dynamic method invocation of plugin entry points and their methods during application startup.

Changes

File(s) / Group Change Summary
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy Added PluginHolder field and initialized it in the constructor.
application-engine/src/main/groovy/com/netgrif/application/engine/plugin/PluginInjector.groovy Introduced PluginInjector class for injecting plugin entry points and methods into the meta-class system at runtime.
application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/EntryPointMeta.groovy Added empty EntryPointMeta class as a meta-class placeholder for plugin entry points.
application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/PluginHolder.groovy Added empty PluginHolder class as a container for plugins.
application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/PluginMeta.groovy Added empty PluginMeta class as a meta-class placeholder for plugins.
application-engine/src/main/java/com/netgrif/application/engine/PluginServiceImpl.java Added PluginServiceImpl class implementing dynamic method invocation on plugin beans via reflection, with fallback for compatible parameter types.
application-engine/src/main/java/com/netgrif/application/engine/startup/runner/PluginRunner.java Added PluginRunner class to discover and register plugins at application startup, using PluginInjector.
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/EntryPoint.java Added EntryPoint domain class representing plugin entry points with associated methods.
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/ListenerFilter.java Added ListenerFilter domain class for filtering event listeners by event type and dispatch method.
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/Method.java Added Method domain class representing annotated plugin methods, their argument types, return type, and listener filters.
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/Plugin.java Added Plugin domain class encapsulating plugin metadata and entry points.
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/EntryPoint.java Added @EntryPoint annotation for marking plugin entry point classes.
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/EntryPointMethod.java Added @EntryPointMethod annotation for marking remotely callable plugin methods.
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/ListenerFilter.java Added @ListenerFilter annotation for specifying listener filtering criteria on plugin methods.
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/config/PluginRegistrationConfiguration.java Added PluginRegistrationConfiguration interface for plugin registration metadata and entry points.
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/service/PluginService.java Added PluginService interface for generic dynamic method invocation on plugins.

Sequence Diagram(s)

sequenceDiagram
    participant Startup as PluginRunner
    participant Context as ApplicationContext
    participant Injector as PluginInjector
    participant Holder as PluginHolder
    participant Service as PluginServiceImpl
    participant Bean as EntryPoint Bean

    Startup->>Context: getBeansOfType(PluginRegistrationConfiguration)
    Startup->>Injector: inject(plugin)
    Injector->>Holder: Dynamically add PluginMeta with EntryPointMeta(s)
    Note over Holder: EntryPointMeta methods delegate to PluginService
Loading
sequenceDiagram
    participant Client as Application Code
    participant Holder as PluginHolder
    participant Service as PluginServiceImpl
    participant Bean as EntryPoint Bean

    Client->>Holder: PluginHolder.<plugin>.<entryPoint>.<method>(args)
    Holder->>Service: call(pluginId, entryPoint, method, args)
    Service->>Bean: invoke method reflectively
    Bean-->>Service: return result
    Service-->>Holder: return result
    Holder-->>Client: return result
Loading

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd9c0e and 53de537.

📒 Files selected for processing (1)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/ListenerFilter.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/ListenerFilter.java
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: Build
  • GitHub Check: task-list-completed
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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.

@machacjozef machacjozef self-assigned this Jun 21, 2025
@machacjozef machacjozef added the improvement A change that improves on an existing feature label Jun 21, 2025
tuplle
tuplle previously approved these changes Jun 21, 2025
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: 7

🧹 Nitpick comments (14)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/ListenerFilter.java (4)

12-13: Grammar: Improve class Javadoc.
The Javadoc reads "This filter define, which listener will call methods of this plugin." Consider changing to "This filter defines which listeners will invoke methods of this plugin."


17-18: Consider Lombok’s @NoArgsConstructor.
You manually defined a no-argument constructor; using Lombok’s @NoArgsConstructor would align with existing Lombok usage and reduce boilerplate.


20-22: Enforce non-null on required fields.
Since eventType and dispatchMethod are mandatory for this filter, annotate them with Lombok’s @NonNull to automatically generate null checks.


27-31: Validate builder parameters.
The @Builder constructor doesn’t enforce non-null constraints. Consider annotating its parameters with @NonNull or adding explicit checks to prevent invalid instantiation.

application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/PluginMeta.groovy (1)

4-5: Add class-level Javadoc for PluginMeta.
Include a brief description explaining that this meta-class serves as a dynamic container for injected plugin entry points and their methods at runtime.

application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/PluginHolder.groovy (1)

4-5: Add documentation for PluginHolder.
This class is used by ActionDelegate to hold dynamically injected plugin metadata. A concise Javadoc will improve readability and maintainability.

application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/EntryPointMeta.groovy (1)

3-6: Grammar: Refine class Javadoc.
Remove the unnecessary comma in "Class, that has modified meta class" and rephrase for clarity, e.g., "Class that has a modified meta class and is injected into …".

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

199-224: Suggest inline instantiation for the Plugin holder
Rather than assigning this.Plugin = new PluginHolder() in init(), you can instantiate Plugin at declaration to guarantee it's always non-null and cut boilerplate.
Apply this diff within the selected lines:

@@
-    PluginHolder Plugin
+    PluginHolder Plugin = new PluginHolder()
@@
-        this.Plugin = new PluginHolder()
+        // inline instantiation removes need for explicit init assignment
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/EntryPointMethod.java (1)

15-19: Consider validation for the listeners array.

The annotation design is sound with appropriate targeting and retention. However, consider adding validation to ensure that when listeners is specified, it contains valid configurations.

You might want to add validation annotations or documentation about expected behavior when the listeners array is empty vs. when it contains filters.

nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/service/PluginService.java (1)

6-8: Consider adding parameter validation and security documentation.

The interface design is clean and appropriate for dynamic plugin invocation. However, consider the following:

  1. Parameter validation: String-based identifiers could lead to runtime errors if invalid
  2. Security implications: Dynamic method invocation should be carefully controlled to prevent unauthorized access

Consider adding validation methods or builder patterns to ensure valid plugin/entry point/method combinations before invocation. Also, document security considerations for implementations.

nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/config/PluginRegistrationConfiguration.java (1)

7-16: Consider documenting expected behavior for Map return values.

The interface design is clean and provides a good contract for plugin registration. Consider adding documentation about:

  1. Null handling: Whether methods can return null or should return empty Maps
  2. Required keys: Any expected keys in the metadata Map
  3. Entry point validation: Expected format/constraints for entry point keys

Adding JavaDoc comments with examples would improve the interface's usability:

 public interface PluginRegistrationConfiguration {

+    /**
+     * @return the plugin name, must not be null or empty
+     */
     String getPluginName();

+    /**
+     * @return the plugin version, must not be null or empty
+     */
     String getVersion();

+    /**
+     * @return map of entry point identifiers to EntryPoint objects, never null
+     */
     Map<String, EntryPoint> getEntryPoints();

+    /**
+     * @return plugin metadata as key-value pairs, never null but may be empty
+     */
     Map<String, String> getMetadata();
 }
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/Method.java (1)

11-15: Improve javadoc comment formatting for better documentation.

Consider using proper @link annotation for the referenced annotation class to enable IDE navigation and documentation generation.

- * and are annotated with {@code com.netgrif.application.engine.adapter.spring.plugin.annotations.EntryPointMethod} annotations. These methods
+ * and are annotated with {@link com.netgrif.application.engine.adapter.spring.plugin.annotations.EntryPointMethod} annotations. These methods
application-engine/src/main/java/com/netgrif/application/engine/PluginServiceImpl.java (2)

40-42: Consider renaming method to better reflect its purpose

The method name findMethodWithSuperClassParams is misleading. It actually finds methods with compatible parameter types (using isAssignableFrom), not just superclass parameters.

-private Method findMethodWithSuperClassParams(Object bean, String methodToExecute, Class<?>[] requestParamTypes,
-                                              NoSuchMethodException caughtException)
+private Method findMethodWithCompatibleParams(Object bean, String methodToExecute, Class<?>[] requestParamTypes,
+                                             NoSuchMethodException caughtException)

40-80: Consider adding support for varargs methods

The current implementation doesn't handle varargs methods, which could limit plugin functionality.

Would you like me to provide an implementation that handles varargs methods?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb581e3 and 3cd9c0e.

📒 Files selected for processing (16)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (3 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/plugin/PluginInjector.groovy (1 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/EntryPointMeta.groovy (1 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/PluginHolder.groovy (1 hunks)
  • application-engine/src/main/groovy/com/netgrif/application/engine/plugin/meta/PluginMeta.groovy (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/PluginServiceImpl.java (1 hunks)
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/PluginRunner.java (1 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/EntryPoint.java (1 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/ListenerFilter.java (1 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/Method.java (1 hunks)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/Plugin.java (1 hunks)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/EntryPoint.java (1 hunks)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/EntryPointMethod.java (1 hunks)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/ListenerFilter.java (1 hunks)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/config/PluginRegistrationConfiguration.java (1 hunks)
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/service/PluginService.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
  • GitHub Check: Build
  • GitHub Check: task-list-completed
🔇 Additional comments (9)
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1)

55-55: Import PluginHolder for plugin DSL support
The new Plugin field relies on PluginHolder. The import is correct and necessary for the dynamic plugin injection.

nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/plugin/annotations/EntryPoint.java (1)

11-28: Well-designed annotation following Spring patterns.

The annotation is properly structured with correct use of @AliasFor for value/name aliasing, appropriate retention policy, and clear documentation. The integration with Spring's @Service annotation is a good design choice.

nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/EntryPoint.java (1)

15-40: Well-designed domain class with proper serialization support.

The implementation correctly:

  • Implements Serializable with explicit serialVersionUID
  • Uses Lombok annotations for boilerplate reduction
  • Initializes the methods map in the default constructor to prevent NPE
  • Provides a builder pattern constructor for flexible object creation
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/Method.java (1)

16-38: Consistent and well-implemented domain class.

The implementation follows the same solid patterns as EntryPoint:

  • Proper serialization support with explicit serialVersionUID
  • Defensive programming with list initialization in default constructor
  • Clean builder pattern usage with Lombok annotations
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/Plugin.java (1)

11-57: Comprehensive plugin domain model with excellent design patterns.

The implementation demonstrates:

  • Complete plugin metadata model with network service ports (REST/gRPC)
  • Proper serialization support and defensive initialization
  • Flexible activation control via the active boolean flag
  • Consistent with other domain classes in the plugin package

The design supports both standalone plugins and those exposing network services, providing a solid foundation for the plugin architecture.

application-engine/src/main/groovy/com/netgrif/application/engine/plugin/PluginInjector.groovy (1)

22-40: Well-structured component with clear separation of concerns.

The design appropriately separates the public interface (inject) from the implementation details (updateMetaClasses), and the delegation to PluginService maintains good architectural boundaries.

application-engine/src/main/java/com/netgrif/application/engine/startup/runner/PluginRunner.java (1)

22-26: Verify the default plugin enabling behavior aligns with security requirements.

The matchIfMissing = true means plugins are enabled by default when the property is not set. This could be a security concern in production environments where plugin functionality should be explicitly enabled.

Consider whether plugins should be disabled by default for security:

 @ConditionalOnProperty(
         value = "nae.plugin.enabled",
         havingValue = "true",
-        matchIfMissing = true
+        matchIfMissing = false
 )

Please verify this aligns with your security and deployment requirements.

application-engine/src/main/java/com/netgrif/application/engine/PluginServiceImpl.java (2)

31-38: Method resolution strategy looks good

The two-phase approach (exact match followed by compatible match) is a good design pattern for flexible method resolution.


25-26: Add null safety checks

The code could throw NullPointerException in two scenarios:

  1. If any element in args is null, Object::getClass will fail
  2. If the bean is not found in the application context

Add null safety checks:

-Class<?>[] paramTypesFromRequest = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
-Object bean = applicationContext.getBean(entryPoint);
+Class<?>[] paramTypesFromRequest = Arrays.stream(args)
+        .map(arg -> arg != null ? arg.getClass() : null)
+        .toArray(Class[]::new);
+Object bean = applicationContext.getBean(entryPoint);
+if (bean == null) {
+    throw new IllegalArgumentException("Entry point bean not found: " + entryPoint);
+}

Likely an incorrect or invalid review comment.

Comment on lines +12 to +14
* Domain class for entry points. Entry points are beans annotated with {@link com.netgrif.plugin.core.annotations.EntryPoint}
* annotation. These beans contain methods, that can be run from server where the plugin is registered.
* */
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the incorrect package reference in the javadoc comment.

The comment references com.netgrif.plugin.core.annotations.EntryPoint but according to the AI summary, the actual annotation is located at com.netgrif.application.engine.adapter.spring.plugin.annotations.EntryPoint.

- * Domain class for entry points. Entry points are beans annotated with {@link com.netgrif.plugin.core.annotations.EntryPoint}
+ * Domain class for entry points. Entry points are beans annotated with {@link com.netgrif.application.engine.adapter.spring.plugin.annotations.EntryPoint}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* Domain class for entry points. Entry points are beans annotated with {@link com.netgrif.plugin.core.annotations.EntryPoint}
* annotation. These beans contain methods, that can be run from server where the plugin is registered.
* */
* Domain class for entry points. Entry points are beans annotated with {@link com.netgrif.application.engine.adapter.spring.plugin.annotations.EntryPoint}
* annotation. These beans contain methods, that can be run from server where the plugin is registered.
* */
🤖 Prompt for AI Agents
In
nae-object-library/src/main/java/com/netgrif/application/engine/objects/plugin/domain/EntryPoint.java
around lines 12 to 14, update the Javadoc comment to correct the package
reference for the EntryPoint annotation. Replace the incorrect package
`com.netgrif.plugin.core.annotations.EntryPoint` with the correct one
`com.netgrif.application.engine.adapter.spring.plugin.annotations.EntryPoint` to
accurately reflect the annotation's location.

Comment on lines +50 to +72
protected void updateMetaClasses(Plugin plugin) {
def pluginMeta = new PluginMeta()

plugin.entryPoints.values().each { EntryPoint ep ->
def epMeta = new EntryPointMeta()

ep.methods.values().each { Method method ->
/**
* Dynamically generated method closure for entry point invocation.
*
* @param args variable-length list of Serializable arguments
* @return the result returned by PluginService.call(...)
*/
epMeta.metaClass."${method.name}" = { Serializable... args ->
pluginService.call(plugin.identifier, ep.name, method.name, args)
}
}

pluginMeta.metaClass."${ep.name}" = epMeta
}

PluginHolder.metaClass."${plugin.name}" = pluginMeta
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding validation and error handling to the dynamic injection mechanism.

While the metaClass approach is appropriate for Groovy, consider these improvements:

  1. Validation: Verify plugin and entry point data before injection
  2. Error handling: The dynamic closures lack error handling for method invocation failures
  3. Security: Using Serializable... args may be overly permissive
 protected void updateMetaClasses(Plugin plugin) {
+    if (plugin?.name == null || plugin.entryPoints == null) {
+        log.warn("Skipping injection for invalid plugin: ${plugin}")
+        return
+    }
+    
     def pluginMeta = new PluginMeta()

     plugin.entryPoints.values().each { EntryPoint ep ->
+        if (ep?.name == null || ep.methods == null) {
+            log.warn("Skipping invalid entry point: ${ep}")
+            return
+        }
         def epMeta = new EntryPointMeta()

         ep.methods.values().each { Method method ->
+            if (method?.name == null) {
+                log.warn("Skipping invalid method: ${method}")
+                return
+            }
             epMeta.metaClass."${method.name}" = { Serializable... args ->
+                try {
                     pluginService.call(plugin.identifier, ep.name, method.name, args)
+                } catch (Exception e) {
+                    log.error("Error calling plugin method ${plugin.identifier}.${ep.name}.${method.name}", e)
+                    throw e
+                }
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected void updateMetaClasses(Plugin plugin) {
def pluginMeta = new PluginMeta()
plugin.entryPoints.values().each { EntryPoint ep ->
def epMeta = new EntryPointMeta()
ep.methods.values().each { Method method ->
/**
* Dynamically generated method closure for entry point invocation.
*
* @param args variable-length list of Serializable arguments
* @return the result returned by PluginService.call(...)
*/
epMeta.metaClass."${method.name}" = { Serializable... args ->
pluginService.call(plugin.identifier, ep.name, method.name, args)
}
}
pluginMeta.metaClass."${ep.name}" = epMeta
}
PluginHolder.metaClass."${plugin.name}" = pluginMeta
}
protected void updateMetaClasses(Plugin plugin) {
// Validation: skip entirely if the plugin is malformed
if (plugin?.name == null || plugin.entryPoints == null) {
log.warn("Skipping injection for invalid plugin: ${plugin}")
return
}
def pluginMeta = new PluginMeta()
plugin.entryPoints.values().each { EntryPoint ep ->
// Validation: skip individual entry point if malformed
if (ep?.name == null || ep.methods == null) {
log.warn("Skipping invalid entry point: ${ep}")
return
}
def epMeta = new EntryPointMeta()
ep.methods.values().each { Method method ->
// Validation: skip individual method if malformed
if (method?.name == null) {
log.warn("Skipping invalid method: ${method}")
return
}
epMeta.metaClass."${method.name}" = { Serializable... args ->
// Error handling around the dynamic call
try {
pluginService.call(plugin.identifier, ep.name, method.name, args)
} catch (Exception e) {
log.error("Error calling plugin method ${plugin.identifier}.${ep.name}.${method.name}", e)
throw e
}
}
}
pluginMeta.metaClass."${ep.name}" = epMeta
}
PluginHolder.metaClass."${plugin.name}" = pluginMeta
}
🤖 Prompt for AI Agents
In
application-engine/src/main/groovy/com/netgrif/application/engine/plugin/PluginInjector.groovy
between lines 50 and 72, add validation to check that the plugin and its entry
points are not null or empty before injecting metaClasses. Wrap the dynamic
method closures in try-catch blocks to handle and log any exceptions during
pluginService.call invocations. Also, restrict or validate the types of
arguments accepted instead of allowing any Serializable to improve security and
prevent misuse.

Comment on lines +39 to +56
public void run(ApplicationArguments args) throws Exception {
log.info("Registering plugins.");
Map<String, Plugin> pluginMap = new HashMap<>();

applicationContext.getBeansOfType(PluginRegistrationConfiguration.class).forEach((key, config) -> {
if (!pluginMap.containsKey(config.getPluginName())) {
Plugin plugin = Plugin.builder()
.identifier(config.getPluginName())
.name(config.getPluginName())
.version(config.getVersion())
.description(StringUtils.EMPTY)
.entryPoints(config.getEntryPoints())
.build();
pluginMap.put(config.getPluginName(), plugin);
}
});
pluginMap.values().forEach(pluginInjector::inject);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and improve plugin metadata handling.

The current implementation has several areas for improvement:

  1. Error handling: No exception handling during plugin collection or injection
  2. Plugin metadata: Using plugin name as identifier and setting empty description
  3. Validation: No validation of plugin configurations
 @Override
 public void run(ApplicationArguments args) throws Exception {
     log.info("Registering plugins.");
     Map<String, Plugin> pluginMap = new HashMap<>();

     applicationContext.getBeansOfType(PluginRegistrationConfiguration.class).forEach((key, config) -> {
-        if (!pluginMap.containsKey(config.getPluginName())) {
-            Plugin plugin = Plugin.builder()
-                    .identifier(config.getPluginName())
-                    .name(config.getPluginName())
-                    .version(config.getVersion())
-                    .description(StringUtils.EMPTY)
-                    .entryPoints(config.getEntryPoints())
-                    .build();
-            pluginMap.put(config.getPluginName(), plugin);
-        }
+        try {
+            if (StringUtils.isBlank(config.getPluginName())) {
+                log.warn("Skipping plugin registration with blank name: {}", key);
+                return;
+            }
+            
+            if (!pluginMap.containsKey(config.getPluginName())) {
+                Plugin plugin = Plugin.builder()
+                        .identifier(config.getPluginName()) // Consider using a separate identifier
+                        .name(config.getPluginName())
+                        .version(config.getVersion())
+                        .description(config.getDescription() != null ? config.getDescription() : StringUtils.EMPTY)
+                        .entryPoints(config.getEntryPoints())
+                        .active(true) // Set explicit active state
+                        .build();
+                pluginMap.put(config.getPluginName(), plugin);
+                log.debug("Registered plugin: {}", config.getPluginName());
+            }
+        } catch (Exception e) {
+            log.error("Error registering plugin configuration: {}", key, e);
+        }
     });
-    pluginMap.values().forEach(pluginInjector::inject);
+    
+    pluginMap.values().forEach(plugin -> {
+        try {
+            pluginInjector.inject(plugin);
+            log.debug("Injected plugin: {}", plugin.getName());
+        } catch (Exception e) {
+            log.error("Error injecting plugin: {}", plugin.getName(), e);
+        }
+    });
+    
+    log.info("Plugin registration completed. Registered {} plugins.", pluginMap.size());
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void run(ApplicationArguments args) throws Exception {
log.info("Registering plugins.");
Map<String, Plugin> pluginMap = new HashMap<>();
applicationContext.getBeansOfType(PluginRegistrationConfiguration.class).forEach((key, config) -> {
if (!pluginMap.containsKey(config.getPluginName())) {
Plugin plugin = Plugin.builder()
.identifier(config.getPluginName())
.name(config.getPluginName())
.version(config.getVersion())
.description(StringUtils.EMPTY)
.entryPoints(config.getEntryPoints())
.build();
pluginMap.put(config.getPluginName(), plugin);
}
});
pluginMap.values().forEach(pluginInjector::inject);
}
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("Registering plugins.");
Map<String, Plugin> pluginMap = new HashMap<>();
applicationContext.getBeansOfType(PluginRegistrationConfiguration.class).forEach((key, config) -> {
try {
if (StringUtils.isBlank(config.getPluginName())) {
log.warn("Skipping plugin registration with blank name: {}", key);
return;
}
if (!pluginMap.containsKey(config.getPluginName())) {
Plugin plugin = Plugin.builder()
.identifier(config.getPluginName()) // Consider using a separate identifier
.name(config.getPluginName())
.version(config.getVersion())
.description(config.getDescription() != null ? config.getDescription() : StringUtils.EMPTY)
.entryPoints(config.getEntryPoints())
.active(true) // Set explicit active state
.build();
pluginMap.put(config.getPluginName(), plugin);
log.debug("Registered plugin: {}", config.getPluginName());
}
} catch (Exception e) {
log.error("Error registering plugin configuration: {}", key, e);
}
});
pluginMap.values().forEach(plugin -> {
try {
pluginInjector.inject(plugin);
log.debug("Injected plugin: {}", plugin.getName());
} catch (Exception e) {
log.error("Error injecting plugin: {}", plugin.getName(), e);
}
});
log.info("Plugin registration completed. Registered {} plugins.", pluginMap.size());
}
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/startup/runner/PluginRunner.java
between lines 39 and 56, add try-catch blocks around the plugin collection and
injection logic to handle and log exceptions gracefully. Replace using the
plugin name as the identifier with a unique identifier if available, and
populate the description field with meaningful metadata from the plugin
configuration. Also, add validation checks for plugin configurations before
creating Plugin objects to ensure required fields are present and valid,
skipping or logging invalid entries.

Comment on lines +69 to +70
throw new IllegalArgumentException(String.format("Method %s is ambiguous for the param types %s",
methodToExecute, Arrays.toString(requestParamTypes)));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve ambiguous method error message

When multiple methods match, include information about which methods were found to help with debugging.

-throw new IllegalArgumentException(String.format("Method %s is ambiguous for the param types %s",
-        methodToExecute, Arrays.toString(requestParamTypes)));
+throw new IllegalArgumentException(String.format(
+        "Method %s is ambiguous for param types %s. Found: %s and %s",
+        methodToExecute, Arrays.toString(requestParamTypes),
+        methodToInvoke.toString(), method.toString()));

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/PluginServiceImpl.java
around lines 69 to 70, the error message for ambiguous methods only states the
method and parameter types but lacks details on which methods were found. Modify
the exception message to include a list or description of all matching methods
that caused the ambiguity, so debugging is easier by knowing exactly which
methods conflicted.

Class<?>[] paramTypesFromRequest = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
Object bean = applicationContext.getBean(entryPoint);
Method methodToInvoke = findMethod(bean, method, paramTypesFromRequest);
return methodToInvoke.invoke(bean, Arrays.stream(args).toArray());
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove redundant array conversion

The args parameter is already a Serializable[] array. Converting it to an array again is unnecessary.

-return methodToInvoke.invoke(bean, Arrays.stream(args).toArray());
+return methodToInvoke.invoke(bean, args);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return methodToInvoke.invoke(bean, Arrays.stream(args).toArray());
- return methodToInvoke.invoke(bean, Arrays.stream(args).toArray());
+ return methodToInvoke.invoke(bean, args);
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/PluginServiceImpl.java
at line 28, remove the redundant conversion of the args parameter to an array.
Since args is already a Serializable[] array, pass it directly to
methodToInvoke.invoke without wrapping it in Arrays.stream(args).toArray().

Comment on lines +23 to +24
public Object call(String pluginId, String entryPoint, String method, Serializable... args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
log.info("Executing entry point [{}] with method [{}]...", entryPoint, method);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use or remove the unused pluginId parameter

The pluginId parameter is never used in the method implementation. Either use it (e.g., in logging or validation) or remove it from the method signature.

Consider including it in the log message:

-log.info("Executing entry point [{}] with method [{}]...", entryPoint, method);
+log.info("Executing plugin [{}] entry point [{}] with method [{}]...", pluginId, entryPoint, method);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Object call(String pluginId, String entryPoint, String method, Serializable... args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
log.info("Executing entry point [{}] with method [{}]...", entryPoint, method);
public Object call(String pluginId, String entryPoint, String method, Serializable... args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
log.info("Executing plugin [{}] entry point [{}] with method [{}]...", pluginId, entryPoint, method);
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/PluginServiceImpl.java
around lines 23 to 24, the method call has an unused parameter pluginId. To fix
this, either remove the pluginId parameter from the method signature if it is
unnecessary, or incorporate it into the method logic, such as including it in
the log message to provide more context about which plugin is being executed.

- Introduced @target and @retention annotations for ListenerFilter definition.
- Ensured compatibility with method-level usage and set retention policy at runtime.
@machacjozef machacjozef merged commit 30584cb into release/7.0.0-rev2 Jun 21, 2025
6 of 7 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 20, 2025
18 tasks
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