Implement custom plugin
In this section, we provide some steps to follow to successfully implement and deploy a custom plugin.
Currently, only Java Spring-framework-based applications are supported to be registered as plugins. Any Spring Boot application can have a plugin as long as:
- It has Plugin SDK as dependency (
com.netgrif:plugin-client:5.0.0). - At least one Spring bean is annotated as EntryPoint.
- It is visible on network to the platform cluster - plugin, and cluster nodes can communicate with each other.
Plugin SDK
To make the plugin creation easier, we published a Plugin SDK library that can be added to your Java Spring project with Maven dependency:
<dependency>
<groupId>com.netgrif</groupId>
<artifactId>plugin-client</artifactId>
<version>5.0.0</version>
</dependecy>The artifact is called plugin-client because your plugin is a client-to-plugin ecosystem in the Netgrif Platform.
- Java 21
- Spring Boot 3.2.5
- plugin id
Configuration properties
The following properties are needed for a successful plugin registration to the Admin Node.
netgrif.plugin.service.registration-server.host- Admin node host address
- Default value:
localhost
- Default value:
- Admin node host address
netgrif.plugin.service.registration-server.port- Admin node port for gRPC communication
- Default value:
9999
- Default value:
- Admin node port for gRPC communication
netgrif.plugin.service.grpc.host- Plugin grpc server address (this host will be called when the plugin service is invoked in workflow)
- Default value:
localhost
- Default value:
- Plugin grpc server address (this host will be called when the plugin service is invoked in workflow)
netgrif.plugin.service.grpc.port- Plugin grpc server port
- Default value:
9090
- Default value:
- Plugin grpc server port
netgrif.plugin.groupId- Plugin group ID
- Default value:
@project.groupId@
- Default value:
- Plugin group ID
netgrif.plugin.artifactId- Plugin Artifact ID
- Default value:
@project.groupId@
- Default value:
- Plugin Artifact ID
netgrif.plugin.version- Plugin version
- Default value:
@project.version@
- Default value:
- Plugin version
Spring Boot configuration
Before running the plugin, it is necessary to configure packageScan and propertyScan. This ensures that the Plugin starts its own gRPC server and registers it to the provided Admin node via the netgrif.plugin.service.registration-server.host property. This server provides gRPC methods for executing plugin internal service methods.
EntryPoint
Upon plugin application startup (PluginLibraryApplication must be added to your application sources), the plugin starts and shuts down when the application is stopped. The remote methods can be called from any NAE application from inside any PetriNet action. If we want to create such a remotely executable plugin method, we have to create a Java class and annotate it with @EntryPoint annotation. Then we can implement the executable method inside that class. We have to mark the executable method with @EntryPointMethod annotation.
NOTE
Method arguments shouldn't be of a primitive type and can also be any of the domain classes inside com.netgrif.engine.application.engine.
@EntryPoint("myCustomClass")
public class MyCustomClass {
@EntryPointMethod
public String getMessage(String name) {
return "Hello " + name + ", from MyCustomClass!";
}
}After successful plugin registration, we can execute this plugin method from within any process action using the plugin's name from Plugin object. To call the method, use the name of the entry point, in which the method is implemented. As in an example below:
<transition>
...
<event type="finish">
<id>finish_event</id>
<actions phase="post">
<action>
println Plugin.myExamplePlugin.myCustomClass.getMessage("John")
</action>
</actions>
</event>
</transition>The following output can be seen in the logs:
Hello John, from MyCustomClass!Event listener
Event propagation is composed of two main concepts: Listeners and Dispatchers.
Dispatchers
A Dispatcher is what the name evokes - a system that dispatches (sends) events to a provided set of Listeners (events handlers). This lightweight class takes a set of Listeners objects and iterates through them, calling a method with an event as their parameter. There are two possible methods to invoke - onEvent and onAsyncEvent.
Synchronous function onEvent is invoked by dispatch method, which holds handling of onEvent method in Listener and dispatchAsync handles all onAsyncEvent methods. Both of those methods are invoking asynchronously, but the difference between them is that dispatchAsync doesn't wait for code execution to end, so there is no possibility of thread starvation. On the other hand, the dispatch method waits for all iterated events to complete their execution, so there is a possibility of thread blocking.
Listeners
A Listener is a receiver of provided Events sent through a Dispatcher. Developers can override the onEvent and onAsyncEvent methods to customize event handling as needed. Before a listener can receive events, it must be registered with a Dispatcher. Once registered, the Dispatcher invokes the onEvent/onAsyncEvent methods. This registration mechanism ensures that the Dispatcher has full control over an event dispatching process. A Listener can receive only the events provided by the Dispatcher it is registered with. For example, if a Listener is registered with the CaseDispatcher, it can listen only to events such as CreateCaseEvent, DeleteCaseEvent or IndexCaseEvent.
Registration
a Registration of a listener is a simple and straightforward process, where it is necessary to call register method with Dispatcher, Class and DispatchMethod. The Dispatcher takes Class (which represents the event) and creates an internal record. Every time the event of Class is fired, the dispatcher invokes the corresponding handler.
Events
A Dispatcher can handle any event that is a child of EventObject. It can have internal method and custom fields that are passed from the dispatcher to the listener.
Custom handlers
In the code block below, you can see a simple onAsyncEvent implementation.
@Slf4j
@Component
public class MyCustomListener extends Listener {
public AllEventListenr(ApplicationContext ctx) {
listenAll(ctx);
}
@Override
public void onEvent(EventObject event, AbstractDispatcher dispatcher) {
// do nothing
}
@Override
public <E extends EventObject> void onAsyncEvent(E event, AbstractDispatcher dispatcher) {
if (event instanceof MyCustomEvent customEvent) {
log.info("Catching custom event with message {}", customEvent.getMessage());
}
}
private void listenAll(ApplicationContext ctx) {
// register to all events
ctx.getBeansOfType(AbstractDispatcher.class).values()
.forEach(dispatcher ->
this.registerAll(dispatcher,
dispatcher.getAllowedEvents(),
AbstractDispatcher.DispatchMethod.ASYNC));
}
}List of all listenable metrics :
- ACTION_RUNTIME: Measures the execution duration of an action
- ACTION_START: Records the total number of events signaling the start of an Engine Action execution
- ACTION_STOP: Records the total number of events signaling the termination of an Engine Action execution
- ACTION_SUCCESS: Records the total number of Engine Action executions that complete without exceptions
- ADMIN_ACTION: Monitors Admin actions
- ASSIGN_TASK_POST: Records task assignment events (after execution)
- ASSIGN_TASK_PRE: Records task assignment events (before execution)
- CANCEL_TASK_POST: Records task cancellation events (after execution)
- CANCEL_TASK_PRE: Records task cancellation events (before execution)
- CREATE_CASE_POST: Tracks the creation of new Case entities (after execution)
- CREATE_CASE_PRE: Tracks the creation of new Case entities (before execution)
- CREATE_TASK_POST: Records task creation events (after execution)
- CREATE_TASK_PRE: Records task creation events (before execution)
- DELEGATE_TASK_POST: Records task delegation events (after execution)
- DELEGATE_TASK_PRE: Records task delegation events (before execution)
- DELETE_CASE_POST: Records the deletion of Case instances (after execution)
- DELETE_CASE_PRE: Records the deletion of Case instances (before execution)
- FINISH_TASK: Records the total number of completed tasks
- FINISH_TASK_PRE: Records task completion events (before execution)
- DATABASE_STORAGE: Represents used storage for database persistence
- DISK_STORAGE: Represents used disk storage capacity
- ELASTIC_STORAGE: Represents used storage for Elasticsearch persistence
- GET_DATA_POST: Records data retrieval actions (after execution)
- GET_DATA_PRE: Records data retrieval actions (before execution)
- IMPERSONATION: Records the total number of impersonation events
- INDEX_CASE: Records case indexing operations
- INDEX_TASK: Records task indexing operations
- PROCESS_DELETE_POST: Records process deletion events (after execution)
- PROCESS_DELETE_PRE: Records process deletion events (before execution)
- PROCESS_DEPLOY_POST: Records the total number of new process deployments
- PROCESS_DEPLOY_PRE: Records process deployment events (before execution)
- SET_DATA_POST: Records data modification actions (after execution)
- SET_DATA_PRE: Records data modification actions (before execution)
- USER_LOGIN: Records the total number of user login events
- USER_LOGOUT: Records the total number of user logout events
- USER_REGISTRATION: Records the creation of new user accounts
- USER_ROLE_CHANGE: Records the total number of user role changes
