Skip to content

Add notification support to the workflows#33

Open
nilasini wants to merge 13 commits intomainfrom
notification
Open

Add notification support to the workflows#33
nilasini wants to merge 13 commits intomainfrom
notification

Conversation

@nilasini
Copy link

@nilasini nilasini commented Feb 13, 2026

Purpose

Resolves wso2/product-is#26873
Depends on wso2/identity-api-server#1063 and wso2/carbon-identity-framework#7812

DB storage
Screenshot 2026-03-06 at 10 55 48

Parameters are retrieved as below.
Screenshot 2026-03-06 at 14 20 27

Summary by CodeRabbit

  • New Features

    • Multi-channel notifications for workflow approval tasks (email, SMS, etc.) with per-channel content, resilient delivery, and per-channel fallbacks.
    • Role-based approver expansion so assigned users receive notifications.
    • Requester notifications triggered on workflow completion.
  • Chores

    • Integrated event service support and added governance dependency and version properties for notification/configuration.

Copilot AI review requested due to automatic review settings February 13, 2026 08:13
Copy link

@wso2-engineering wso2-engineering bot left a comment

Choose a reason for hiding this comment

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

AI Agent Log Improvement Checklist

⚠️ Warning: AI-Generated Review Comments

  • The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
  • Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.

✅ Before merging this pull request:

  • Review all AI-generated comments for accuracy and relevance.
  • Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
Comment Accepted (Y/N) Reason
#### Log Improvement Suggestion No: 1
#### Log Improvement Suggestion No: 2

@nilasini nilasini changed the title Add notification support Add notification support to the workflows Feb 13, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds notification support to the workflow approval system, enabling notifications to be sent to both approvers (when a new approval task is created) and requesters (when a decision is made on their request). The implementation supports multiple notification channels including email and SMS.

Changes:

  • Added notification triggering functionality for approvers when approval tasks are created and for requesters when workflows are completed
  • Implemented multi-channel notification support with channel-specific property building
  • Extended the completeWorkflowRequest method signature to include workflowId parameter for retrieving notification preferences

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Integrated IdentityEventService and multi-channel notifications into the workflow engine: added governance dependency and OSGi imports, wired IdentityEventService in the component/data holder, and implemented channel parsing, contact-claim resolution, role→user expansion, per-channel property builders, and notification triggering in approval and completion flows.

Changes

Cohort / File(s) Summary
Maven & OSGi
pom.xml, components/org.wso2.carbon.identity.workflow.engine/pom.xml
Added org.wso2.carbon.identity.governance dependency and version properties; expanded maven-bundle-plugin Import-Package to include identity event, auth framework util, and governance packages.
Service Component / Data Holder
components/org.wso2.carbon.identity.workflow.engine/src/main/java/.../WorkflowEngineServiceComponent.java, components/org.wso2.carbon.identity.workflow.engine/src/main/java/.../WorkflowEngineServiceDataHolder.java
Registered a DS reference for IdentityEventService (bind/unbind) and exposed it via getter/setter on the data holder.
Notification & Workflow Logic
components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java
Added multi-channel notification support: new constants and parameter keys, channel parsing/validation, contact-claim resolution, role→user assignee expansion, per-channel property builders for approver/requester, and triggering IdentityEvent notifications during task creation, approval/rejection handling, and workflow completion. Includes tolerant error handling and enhanced workflow parameter population.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant ApprovalSvc as ApprovalTaskServiceImpl
    participant WorkflowStore as Workflow DB
    participant EventSvc as IdentityEventService
    participant Channel as Notification Channel (Email/SMS)

    Client->>ApprovalSvc: Create workflow request (with notification params)
    ApprovalSvc->>WorkflowStore: Persist request & tasks
    ApprovalSvc->>ApprovalSvc: Expand roles -> getAssignedUserIds()
    ApprovalSvc->>ApprovalSvc: Resolve contact claims for recipients
    ApprovalSvc->>ApprovalSvc: Build per-channel notification properties
    ApprovalSvc->>EventSvc: Trigger channel-specific notification events
    EventSvc->>Channel: Deliver notification

    Client->>ApprovalSvc: Approve/Reject task
    ApprovalSvc->>WorkflowStore: Update task & workflow status
    ApprovalSvc->>ApprovalSvc: Build requester notification properties
    ApprovalSvc->>EventSvc: Trigger completion notification events
    EventSvc->>Channel: Deliver decision notification
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code with nimble feet,

Channels mapped and contacts neat,
Roles became the users' song,
Messages carried out along,
Hooray — the workflows sing along.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is largely incomplete and missing critical sections required by the template. Add missing required sections: Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, Samples, Related PRs, Migrations, Test environment, and Learning. The current description only provides Purpose with issue links and some screenshots.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add notification support to the workflows' directly and clearly summarizes the main change across all modified files.
Linked Issues check ✅ Passed Changes implement core requirements from issue #26711: notification infrastructure added, approver/initiator notification methods implemented, channel-aware notification triggering with event support.
Out of Scope Changes check ✅ Passed All changes are directly related to adding notification support as specified in the linked issue; no unrelated or out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch notification

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

❤️ Share

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

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: 6

🧹 Nitpick comments (5)
components/org.wso2.carbon.identity.workflow.engine/pom.xml (1)

131-134: Wildcard Import-Package patterns differ from existing style.

Existing Import-Package entries specify exact packages (e.g., org.wso2.carbon.identity.workflow.mgt;), whereas the new entries use wildcards (org.wso2.carbon.identity.event.*;). This works with BND/maven-bundle-plugin but is stylistically inconsistent with the rest of this configuration. Consider whether explicit package names would be preferable for consistency and to avoid importing unintended sub-packages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/org.wso2.carbon.identity.workflow.engine/pom.xml` around lines 131
- 134, The Import-Package entries use wildcard patterns (e.g.,
"org.wso2.carbon.identity.event.*;",
"org.wso2.carbon.identity.application.authentication.framework.util.*;",
"org.wso2.carbon.identity.governance.*;") which is stylistically inconsistent
with existing explicit imports like "org.wso2.carbon.identity.workflow.mgt;";
change these wildcard imports to explicit package names to match the project
style and avoid pulling unintended sub-packages: list each concrete package
under org.wso2.carbon.identity.event,
org.wso2.carbon.identity.application.authentication.framework.util, and
org.wso2.carbon.identity.governance that are actually used and replace the
wildcard tokens with those exact package entries (keeping the same version range
properties).
components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java (4)

491-496: Dead code: resolveEventName never returns blank.

resolveEventName always returns either the SMS event name or IdentityEventConstants.Event.TRIGGER_NOTIFICATION, so the StringUtils.isBlank(eventName) check on line 493 will never be true. This is harmless defensive code, but the log.debug("Unsupported notification channel: {}", ch) message could be misleading if someone reads it expecting it to fire.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 491 - 496, The StringUtils.isBlank(eventName) branch in
ApprovalTaskServiceImpl's loop over channels is dead because
resolveEventName(...) never returns blank; remove the conditional and its
misleading log.debug("Unsupported notification channel: {}", ch) to avoid
confusion. Specifically, delete the check that tests eventName for blank and the
continue/log inside that branch (the loop using variable ch and calling
resolveEventName), leaving only the resolved eventName usage; if you want
explicit validation instead, perform a channel-level check before calling
resolveEventName and log a clear message about unrecognized channels rather than
relying on a never-empty return value.

484-486: Misleading parameter name recipientUserId — it's actually the approver for requester notifications.

In triggerNotification, the recipientUserId parameter is documented as "The user ID of the notification recipient." However, when isInitialNotification=false, the value passed (line 1028) is the approver's userId, and the actual recipient (requester) is resolved from workflowRequest.getCreatedBy() inside buildRequesterNotificationProperties.

This dual-purpose parameter is confusing and error-prone. Consider renaming to actorUserId or splitting into separate methods for approver vs. requester notifications.

Also applies to: 530-546, 739-744

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 484 - 486, The parameter recipientUserId in triggerNotification is
misleading because it sometimes holds the approver's userId while the real
recipient for requester notifications is resolved via
workflowRequest.getCreatedBy() inside buildRequesterNotificationProperties;
rename recipientUserId to actorUserId (or similar) across triggerNotification,
its callers, and Javadoc to reflect that it represents the acting user
(approver) rather than the final recipient, and update usages in methods
referenced (triggerNotification, buildRequesterNotificationProperties, and all
callers that pass approver IDs) to avoid confusion; alternatively, split
triggerNotification into two explicit methods (triggerApproverNotification and
triggerRequesterNotification) so one accepts approverUserId and the other
resolves requester from workflowRequest.getCreatedBy() and update call sites
accordingly.

293-301: Reduce visibility of resolveEventName to private static.

The method is called only internally at line 492 within the same class. Changing from public static to private static improves encapsulation and reduces unnecessary API surface exposure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 293 - 301, The method resolveEventName is declared public static
but is only used internally within this class; change its declaration to private
static to reduce API exposure and improve encapsulation by updating the method
signature for resolveEventName accordingly (ensure all internal calls within
ApprovalTaskServiceImpl still compile against the new private static method).

118-118: Inconsistent claim URI sourcing — email uses framework constant, mobile is hardcoded.

FrameworkConstants.EMAIL_ADDRESS_CLAIM is used for the email channel at line 461, but the mobile claim is sourced from a local constant CLAIM_MOBILE defined at line 118 with a hardcoded string value "http://wso2.org/claims/mobile". For consistency, consider defining the mobile claim using a unified approach — either both as local constants or both from FrameworkConstants if a mobile claim constant is available there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
at line 118, The mobile claim is hardcoded via the local constant CLAIM_MOBILE
in ApprovalTaskServiceImpl while the email uses
FrameworkConstants.EMAIL_ADDRESS_CLAIM; update the code to be consistent by
removing/avoiding CLAIM_MOBILE and using the FrameworkConstants mobile claim
constant (e.g., FrameworkConstants.MOBILE_NUMBER_CLAIM or the actual mobile
claim identifier defined in FrameworkConstants) wherever CLAIM_MOBILE is
referenced in ApprovalTaskServiceImpl; if FrameworkConstants does not provide a
mobile constant, instead define both email and mobile as local constants in
ApprovalTaskServiceImpl with consistent naming (e.g., LOCAL_CLAIM_EMAIL and
LOCAL_CLAIM_MOBILE) and replace usages accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 559-582: The buildApproverNotificationProperties method currently
puts values from getContact(...) and getUserClaimValue(...) directly into
properties; first validate that approverContact and approverUsername are
non-null/non-empty after calling getContact and getUserClaimValue, and if
approverContact is missing, log a warning (using the class logger) and
abort/skip building/sending the notification (e.g., return early or throw a
WorkflowEngineException) to avoid inserting a null "send-to" property; likewise
handle a missing approverUsername by using a sensible fallback (e.g., the
approverUserId or "Unknown") or logging and skipping, and ensure no null values
are inserted into properties keys "send-to" or "approverName" before returning
from buildApproverNotificationProperties.
- Line 783: In ApprovalTaskServiceImpl, fix the checkstyle violation by adding a
space after the if keyword in the conditional that checks log.isDebugEnabled();
locate the occurrence of "if(log.isDebugEnabled()) {" and change it to use the
correct spacing ("if (log.isDebugEnabled()) {") so the if keyword is followed by
whitespace and the style linter passes.
- Around line 506-508: The call to
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService().handleEvent(notificationEvent)
in ApprovalTaskServiceImpl can NPE if getIdentityEventService() returns null;
add a null guard around the service lookup: retrieve IdentityEventService into a
local variable (e.g., identityEventService), check for null before calling
handleEvent(notificationEvent), and if null either log a warning/error via the
existing logger and skip calling handleEvent or throw/capture an
IdentityEventException as appropriate to preserve the existing exception
handling flow.
- Around line 346-350: In ApprovalTaskServiceImpl, avoid NPE by guarding the
qName call: inside the block that checks
parameter.getParamName().equalsIgnoreCase(PARAM_NOTIFICATION), ensure
parameter.getqName() is not null before calling
startsWith(Q_NAME_APPROVER_CHANNELS_PREFIX) (e.g., add an explicit null check or
use a null-safe startsWith helper); then set approverNotificationChannels =
parameter.getParamValue() only when qName is non-null and startsWith the prefix.
Ensure you reference PARAM_NOTIFICATION, Q_NAME_APPROVER_CHANNELS_PREFIX,
approverNotificationChannels and the local variable parameter when making this
change.
- Around line 116-124: Remove the duplicate local constant APPROVER_TYPE_ROLES
from ApprovalTaskServiceImpl and replace all usages of that local constant with
WorkflowEngineConstants.APPROVER_TYPE_ROLES (e.g., the usage currently
referencing APPROVER_TYPE_ROLES around the approval-type check). Delete the
private static final String APPROVER_TYPE_ROLES declaration and update any
references (including the occurrence noted near the approval-type handling) to
use WorkflowEngineConstants.APPROVER_TYPE_ROLES for consistency.
- Around line 1004-1032: completeWorkflowRequest currently calls
wsWorkflowCallBackService.onCallback(...) and then runs notification logic
(getApprovalWorkflowParameters, Utils.resolveUserID, triggerNotification) but
re-throws WorkflowEngineException which can make callers think completion failed
even after callback succeeded; wrap the notification block (the call to
getApprovalWorkflowParameters/Utils.resolveUserID/triggerNotification) in a
try-catch that catches WorkflowEngineException (and any RuntimeException from
notification) and logs a warning (e.g., log.warn or the class logger) with the
exception and context (workflowRequestId, workflowId) instead of re-throwing, so
the callback/commit remains successful and notification failures are best-effort
only.

---

Nitpick comments:
In `@components/org.wso2.carbon.identity.workflow.engine/pom.xml`:
- Around line 131-134: The Import-Package entries use wildcard patterns (e.g.,
"org.wso2.carbon.identity.event.*;",
"org.wso2.carbon.identity.application.authentication.framework.util.*;",
"org.wso2.carbon.identity.governance.*;") which is stylistically inconsistent
with existing explicit imports like "org.wso2.carbon.identity.workflow.mgt;";
change these wildcard imports to explicit package names to match the project
style and avoid pulling unintended sub-packages: list each concrete package
under org.wso2.carbon.identity.event,
org.wso2.carbon.identity.application.authentication.framework.util, and
org.wso2.carbon.identity.governance that are actually used and replace the
wildcard tokens with those exact package entries (keeping the same version range
properties).

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 491-496: The StringUtils.isBlank(eventName) branch in
ApprovalTaskServiceImpl's loop over channels is dead because
resolveEventName(...) never returns blank; remove the conditional and its
misleading log.debug("Unsupported notification channel: {}", ch) to avoid
confusion. Specifically, delete the check that tests eventName for blank and the
continue/log inside that branch (the loop using variable ch and calling
resolveEventName), leaving only the resolved eventName usage; if you want
explicit validation instead, perform a channel-level check before calling
resolveEventName and log a clear message about unrecognized channels rather than
relying on a never-empty return value.
- Around line 484-486: The parameter recipientUserId in triggerNotification is
misleading because it sometimes holds the approver's userId while the real
recipient for requester notifications is resolved via
workflowRequest.getCreatedBy() inside buildRequesterNotificationProperties;
rename recipientUserId to actorUserId (or similar) across triggerNotification,
its callers, and Javadoc to reflect that it represents the acting user
(approver) rather than the final recipient, and update usages in methods
referenced (triggerNotification, buildRequesterNotificationProperties, and all
callers that pass approver IDs) to avoid confusion; alternatively, split
triggerNotification into two explicit methods (triggerApproverNotification and
triggerRequesterNotification) so one accepts approverUserId and the other
resolves requester from workflowRequest.getCreatedBy() and update call sites
accordingly.
- Around line 293-301: The method resolveEventName is declared public static but
is only used internally within this class; change its declaration to private
static to reduce API exposure and improve encapsulation by updating the method
signature for resolveEventName accordingly (ensure all internal calls within
ApprovalTaskServiceImpl still compile against the new private static method).
- Line 118: The mobile claim is hardcoded via the local constant CLAIM_MOBILE in
ApprovalTaskServiceImpl while the email uses
FrameworkConstants.EMAIL_ADDRESS_CLAIM; update the code to be consistent by
removing/avoiding CLAIM_MOBILE and using the FrameworkConstants mobile claim
constant (e.g., FrameworkConstants.MOBILE_NUMBER_CLAIM or the actual mobile
claim identifier defined in FrameworkConstants) wherever CLAIM_MOBILE is
referenced in ApprovalTaskServiceImpl; if FrameworkConstants does not provide a
mobile constant, instead define both email and mobile as local constants in
ApprovalTaskServiceImpl with consistent naming (e.g., LOCAL_CLAIM_EMAIL and
LOCAL_CLAIM_MOBILE) and replace usages accordingly.

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.

🧹 Nitpick comments (2)
components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java (2)

572-580: Consider extracting hardcoded notification property keys into constants.

Both buildApproverNotificationProperties and buildRequesterNotificationProperties use raw string literals for property keys ("TEMPLATE_TYPE", "send-to", "approverName", "tenant-domain", "workflowId", "requesterName", etc.). Extracting these into private static final String constants would reduce typo risk and improve maintainability.

Also applies to: 753-761

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 572 - 580, The notification property keys in
ApprovalTaskServiceImpl (used in buildApproverNotificationProperties and
buildRequesterNotificationProperties) are hardcoded string literals; extract
each unique key (e.g., "TEMPLATE_TYPE", "approverName", "send-to",
"tenant-domain", "approvalActionUrl", "workflowId", "requesterName",
"submittedDate", "workflowType", etc.) into private static final String
constants at the top of the class and replace the raw literals in both methods
with those constants to avoid typos and improve maintainability (apply the same
change for the other occurrence referenced around the 753-761 region).

116-123: Change Q_NAME_INITIATOR_CHANNELS_PREFIX visibility from public to private.

This constant is only used internally at line 1017 within the same class. All sibling notification constants (CHANNEL_SMS, PARAM_NOTIFICATION, Q_NAME_APPROVER_CHANNELS_PREFIX, etc.) are private, and this one should be as well to minimize the public API surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 116 - 123, Change the visibility of the constant
Q_NAME_INITIATOR_CHANNELS_PREFIX in ApprovalTaskServiceImpl from public to
private to match the other notification constants (CHANNEL_SMS,
PARAM_NOTIFICATION, Q_NAME_APPROVER_CHANNELS_PREFIX, etc.) and reduce the
class's public API surface; ensure no external references exist and keep the
constant name and value unchanged so internal uses (e.g., where it's referenced
within ApprovalTaskServiceImpl) continue to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 344-349: The code in ApprovalTaskServiceImpl accesses
parameter.getqName() without null-safety which can NPE; update the conditional
that sets approverNotificationChannels to first ensure parameter.getqName() is
not null (e.g., check parameter.getqName() != null) before calling startsWith,
keeping the existing checks for approverNotificationChannels == null and
parameter.getParamName().equalsIgnoreCase(PARAM_NOTIFICATION) and still
comparing against Q_NAME_APPROVER_CHANNELS_PREFIX; ensure the null-guard
preserves short-circuit evaluation so startsWith is only invoked when getqName()
is non-null.
- Around line 558-581: In buildApproverNotificationProperties validate the
results of getContact(...) and getUserClaimValue(...) before putting them into
properties: if approverContact or approverUsername is null (from
getContact/getUserClaimValue) throw a WorkflowEngineException with a clear
message including approverUserId, tenantId and claim/channel (use
resolveContactClaimUri(channel) to indicate which claim), otherwise populate
properties.put("send-to", approverContact) and properties.put("approverName",
approverUsername) as currently done; this prevents sending null destinations and
surfaces the missing-claim error early.
- Around line 1010-1029: The notification block currently throws a
WorkflowEngineServerException on any WorkflowEngineException from
getApprovalWorkflowParameters/triggerNotification, which can cause the API to
report failure after wsWorkflowCallBackService.onCallback() has already
committed the workflow; change this to make notifications best-effort: catch
exceptions around the calls to getApprovalWorkflowParameters(...) and
triggerNotification(...), log the error (using the class logger) including the
exception and context (workflowId/workflowRequestId/userId/status), and do not
re-throw WorkflowEngineServerException so execution returns normally after
logging; keep existing logic for resolving userId via Utils.resolveUserID(...)
and do not alter wsWorkflowCallBackService.onCallback().
- Around line 505-507: The call to
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService() can
return null and lead to an NPE outside the IdentityEventException catch block;
before constructing/dispatching the Event (notificationEvent) check that
getIdentityEventService() is non-null and only call
handleEvent(notificationEvent) when the service exists (otherwise skip or log a
warning). Ensure the null-check wraps the invocation of handleEvent (referencing
getIdentityEventService(), handleEvent(...), Event/notificationEvent) so any
absent OSGi binding is safely handled.

---

Nitpick comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 572-580: The notification property keys in ApprovalTaskServiceImpl
(used in buildApproverNotificationProperties and
buildRequesterNotificationProperties) are hardcoded string literals; extract
each unique key (e.g., "TEMPLATE_TYPE", "approverName", "send-to",
"tenant-domain", "approvalActionUrl", "workflowId", "requesterName",
"submittedDate", "workflowType", etc.) into private static final String
constants at the top of the class and replace the raw literals in both methods
with those constants to avoid typos and improve maintainability (apply the same
change for the other occurrence referenced around the 753-761 region).
- Around line 116-123: Change the visibility of the constant
Q_NAME_INITIATOR_CHANNELS_PREFIX in ApprovalTaskServiceImpl from public to
private to match the other notification constants (CHANNEL_SMS,
PARAM_NOTIFICATION, Q_NAME_APPROVER_CHANNELS_PREFIX, etc.) and reduce the
class's public API surface; ensure no external references exist and keep the
constant name and value unchanged so internal uses (e.g., where it's referenced
within ApprovalTaskServiceImpl) continue to work.

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 (1)
components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java (1)

479-480: Misleading recipientUserId parameter name in triggerNotification.

When isInitialNotification=false (requester notification path), the value passed as recipientUserId is actually the approver's user ID (see line 1019–1020), not the notification recipient. The actual recipient is resolved internally from workflowRequest.getCreatedBy(). Consider renaming the parameter to contextUserId or splitting into two explicit parameters to avoid confusion in future maintenance.

Also applies to: 733-738

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 479 - 480, The parameter name recipientUserId on
triggerNotification is misleading because when isInitialNotification is false
the value passed is actually the approver/context user (not the final recipient
resolved from workflowRequest.getCreatedBy()); rename recipientUserId to
contextUserId (or add a second parameter actualRecipientUserId) in the
triggerNotification(String recipientUserId, String workflowRequestId, boolean
isInitialNotification, String decision, String channel) signature, update all
callers to pass the approver/context id as contextUserId (and the real recipient
where you add a second param), and update internal uses and JavaDoc so the code
explicitly resolves workflowRequest.getCreatedBy() as the recipient only when
appropriate (ensure you change the two caller sites mentioned in the review to
match the new parameter name/shape).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 460-467: getContact currently falls through to
getUserClaimValueByUsername when userId is blank even if username is null, which
can cause a UserStoreException; update getContact to defensively check that
username is not blank (use StringUtils.isNotBlank or equivalent) before calling
getUserClaimValueByUsername and return null or throw a
WorkflowEngineServerException with a clear message if both userId and username
are missing (this affects callers like buildRequesterNotificationProperties
which pass workflowRequest.getCreatedBy()); reference getContact,
getUserClaimValueByUsername, buildRequesterNotificationProperties, and
workflowRequest.getCreatedBy() when making the change.

---

Duplicate comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 345-349: In ApprovalTaskServiceImpl, protect against NPE by
checking that parameter.getqName() is not blank before calling startsWith:
inside the block where you check
parameter.getParamName().equalsIgnoreCase(PARAM_NOTIFICATION) (and where you
assign approverNotificationChannels), add a guard using
StringUtils.isNotBlank(parameter.getqName()) (same approach used in
completeWorkflowRequest) and only call
parameter.getqName().startsWith(Q_NAME_APPROVER_CHANNELS_PREFIX) when that guard
passes; ensure approverNotificationChannels is set only within that guarded
branch.
- Around line 996-1025: In completeWorkflowRequest, after calling
wsWorkflowCallBackService.onCallback (which commits the workflow), wrap the
retrieval of parameters (getApprovalWorkflowParameters) and the call to
triggerNotification in a try/catch that does not rethrow; instead catch
WorkflowEngineException (and any RuntimeException) and log the error with
context (e.g., workflowRequestId, workflowId, status, and
requesterNotificationChannels) so notification failures are best-effort and do
not propagate; remove the current throw new WorkflowEngineServerException in
that catch block so callers of handleApproval/handleReject see success when the
callback succeeded.
- Around line 500-502: ApprovalTaskServiceImpl currently calls
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService().handleEvent(notificationEvent)
without guarding for a null IdentityEventService; add a null-check for
getIdentityEventService() before invoking handleEvent (e.g., store in a local
variable like identityEventService), and if it is null, log an error and throw
an IdentityEventException (or return appropriately) so you avoid an NPE and
still hit the IdentityEventException handling path.
- Around line 553-576: buildApproverNotificationProperties is inserting
potentially null values from getContact(...) and getUserClaimValue(...) into
properties ("send-to" and "approverName"), which can break notification
delivery; update buildApproverNotificationProperties (and similarly
buildRequesterNotificationProperties) to validate the results of getContact and
getUserClaimValue before putting them into properties — if null or empty, log a
warning via the workflow logger (or throw a WorkflowEngineException if
notification must not proceed) and skip adding the "send-to" / "approverName"
keys (or set an explicit safe fallback) so you never insert null into properties
and handlers receive a clear, non-null value or the flow fails fast.

---

Nitpick comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 479-480: The parameter name recipientUserId on triggerNotification
is misleading because when isInitialNotification is false the value passed is
actually the approver/context user (not the final recipient resolved from
workflowRequest.getCreatedBy()); rename recipientUserId to contextUserId (or add
a second parameter actualRecipientUserId) in the triggerNotification(String
recipientUserId, String workflowRequestId, boolean isInitialNotification, String
decision, String channel) signature, update all callers to pass the
approver/context id as contextUserId (and the real recipient where you add a
second param), and update internal uses and JavaDoc so the code explicitly
resolves workflowRequest.getCreatedBy() as the recipient only when appropriate
(ensure you change the two caller sites mentioned in the review to match the new
parameter name/shape).

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.

♻️ Duplicate comments (1)
components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java (1)

503-505: ⚠️ Potential issue | 🟠 Major

Potential NullPointerException if IdentityEventService is unavailable.

getIdentityEventService() can return null if the OSGi service is temporarily unbound (the reference uses DYNAMIC policy). Calling handleEvent(...) on a null reference will throw an unhandled NPE that bypasses the IdentityEventException catch block.

🛡️ Proposed fix — add a null guard
             try {
                 // Build properties specific to this channel.
                 Map<String, Object> notificationProperties = new HashMap<>();
                 String notificationChannel = getServerSupportedNotificationChannel(ch);
                 notificationProperties.put("notificationChannel", notificationChannel);
                 buildNotificationPropertiesForChannel(workflowRequestId, recipientUserId, isInitialNotification,
                         decision, ch, notificationProperties);

+                var identityEventService = WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService();
+                if (identityEventService == null) {
+                    log.warn("IdentityEventService is not available. Skipping notification for channel: {}", ch);
+                    continue;
+                }
                 Event notificationEvent = new Event(eventName, notificationProperties);
-                WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService()
-                        .handleEvent(notificationEvent);
+                identityEventService.handleEvent(notificationEvent);
             } catch (IdentityEventException e) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 503 - 505, ApprovalTaskServiceImpl currently calls
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService().handleEvent(notificationEvent)
without guarding for a null IdentityEventService; add a null check on the result
of getIdentityEventService() before invoking handleEvent (e.g., retrieve the
service into a local variable, if null then log a warning/error and either skip
notification or throw an IdentityEventException), ensuring you reference the
existing Event/notificationEvent and call to handleEvent so behavior remains
consistent when the dynamic OSGi service is temporarily unavailable.
🧹 Nitpick comments (1)
components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java (1)

396-399: Consider wrapping notification loop in try-catch for robustness.

While triggerNotification internally handles IdentityEventException and WorkflowEngineException, any unexpected runtime exception could still propagate and fail the workflow task creation. Since notifications are best-effort, consider wrapping the entire loop.

♻️ Proposed fix
         // Trigger notifications after all parameters are processed and channels are extracted.
         for (String approverIdentifier : approversToNotify) {
+            try {
                 triggerNotification(approverIdentifier, workflowRequestId, true,
                         null, approverNotificationChannels);
+            } catch (Exception e) {
+                log.error("Error while triggering notification for approver: {}", approverIdentifier, e);
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 396 - 399, The notification loop in ApprovalTaskServiceImpl calling
triggerNotification should be made best-effort by wrapping each iteration (or
the whole loop) in a try-catch to prevent unexpected runtime exceptions from
escaping and failing task creation; catch Exception (or RuntimeException), log
the error with context (approverIdentifier and workflowRequestId) using the
existing logger, and continue to the next approver so failures in
triggerNotification (method triggerNotification) do not abort the workflow task
creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 503-505: ApprovalTaskServiceImpl currently calls
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService().handleEvent(notificationEvent)
without guarding for a null IdentityEventService; add a null check on the result
of getIdentityEventService() before invoking handleEvent (e.g., retrieve the
service into a local variable, if null then log a warning/error and either skip
notification or throw an IdentityEventException), ensuring you reference the
existing Event/notificationEvent and call to handleEvent so behavior remains
consistent when the dynamic OSGi service is temporarily unavailable.

---

Nitpick comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 396-399: The notification loop in ApprovalTaskServiceImpl calling
triggerNotification should be made best-effort by wrapping each iteration (or
the whole loop) in a try-catch to prevent unexpected runtime exceptions from
escaping and failing task creation; catch Exception (or RuntimeException), log
the error with context (approverIdentifier and workflowRequestId) using the
existing logger, and continue to the next approver so failures in
triggerNotification (method triggerNotification) do not abort the workflow task
creation.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9652ad4 and 562ef62.

📒 Files selected for processing (1)
  • components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java

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: 3

♻️ Duplicate comments (1)
components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java (1)

504-506: ⚠️ Potential issue | 🟠 Major

Guard IdentityEventService before invoking handleEvent.

Directly calling getIdentityEventService().handleEvent(...) can throw NPE when the service is temporarily unavailable, which breaks the intended best-effort notification behavior.

🛡️ Proposed fix
-                Event notificationEvent = new Event(eventName, notificationProperties);
-                WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService()
-                        .handleEvent(notificationEvent);
+                Event notificationEvent = new Event(eventName, notificationProperties);
+                if (WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService() == null) {
+                    log.warn("IdentityEventService is not available. Skipping notification for channel: {}", ch);
+                    continue;
+                }
+                WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService()
+                        .handleEvent(notificationEvent);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`
around lines 504 - 506, The code in ApprovalTaskServiceImpl calls
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService().handleEvent(notificationEvent)
without a null check; guard the IdentityEventService reference before invoking
handleEvent by retrieving it into a local variable (e.g., identityEventService =
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService()), check
for null and only call identityEventService.handleEvent(notificationEvent) when
non-null, and wrap the call in a try-catch to swallow/log unexpected exceptions
so the notification remains best-effort; reference getIdentityEventService(),
handleEvent(...), WorkflowEngineServiceDataHolder and notificationEvent when
making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 1013-1018: The loop in ApprovalTaskServiceImpl that iterates
parameterList only extracts requesterNotificationChannels when qName starts with
Q_NAME_INITIATOR_CHANNELS_PREFIX and ignores any configured events (e.g.,
onApproval); modify the logic in the block that checks PARAM_NOTIFICATION/qName
so that you parse the qName suffix or another parameter field to determine the
configured event (e.g., "onApproval") and only assign
requesterNotificationChannels when that event matches the current completion
event being processed (the same fix should be applied to the other occurrence
around the code handling lines 1023-1025); update the condition that sets
requesterNotificationChannels to validate both the channel prefix
(Q_NAME_INITIATOR_CHANNELS_PREFIX) and the event name before breaking out of the
loop.
- Around line 345-349: The code in ApprovalTaskServiceImpl only reads
PARAM_NOTIFICATION into approverNotificationChannels when parameter.getqName()
startsWith Q_NAME_APPROVER_CHANNELS_PREFIX, which ignores payload-level event
qualifiers (like onAssignment/onRelease) so notifications are always treated as
assignment-time and onRelease is never reached; update the parsing of
parameter.getqName() in the parameter loop (where parameter.getParamName() ==
PARAM_NOTIFICATION and where similar logic appears again) to extract the event
qualifier suffix (e.g., the token after Q_NAME_APPROVER_CHANNELS_PREFIX) and
store notification channels keyed by event (or separate variables such as
approverNotificationChannelsOnAssignment and
approverNotificationChannelsOnRelease), then when sending notifications check
the current event context (assignment vs release) and pick the corresponding
channels instead of always using approverNotificationChannels. Ensure you update
both occurrences referenced so payload-level event controls are honored.
- Around line 490-500: The code computes eventName from the raw channel variable
ch before applying server normalization, which can cause a channel/event
mismatch; change the order to first call
getServerSupportedNotificationChannel(ch) to obtain a normalized
notificationChannel, then call resolveEventName(notificationChannel) and use
that eventName; update any debug log that references ch to reflect
notificationChannel when applicable (e.g., log unsupported channel using
notificationChannel) and ensure notificationProperties still uses the normalized
notificationChannel variable.

---

Duplicate comments:
In
`@components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java`:
- Around line 504-506: The code in ApprovalTaskServiceImpl calls
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService().handleEvent(notificationEvent)
without a null check; guard the IdentityEventService reference before invoking
handleEvent by retrieving it into a local variable (e.g., identityEventService =
WorkflowEngineServiceDataHolder.getInstance().getIdentityEventService()), check
for null and only call identityEventService.handleEvent(notificationEvent) when
non-null, and wrap the call in a try-catch to swallow/log unexpected exceptions
so the notification remains best-effort; reference getIdentityEventService(),
handleEvent(...), WorkflowEngineServiceDataHolder and notificationEvent when
making this change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 562ef62 and 150ccbf.

📒 Files selected for processing (1)
  • components/org.wso2.carbon.identity.workflow.engine/src/main/java/org/wso2/carbon/identity/workflow/engine/ApprovalTaskServiceImpl.java

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support approver and initiator notifications for workflow request via backend services

3 participants