diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessor.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessor.java index e40a067a28cb..f84e0a096204 100644 --- a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessor.java +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutionResponseProcessor.java @@ -22,6 +22,7 @@ import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; import org.wso2.carbon.identity.action.execution.model.ActionInvocationErrorResponse; import org.wso2.carbon.identity.action.execution.model.ActionInvocationFailureResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationIncompleteResponse; import org.wso2.carbon.identity.action.execution.model.ActionInvocationSuccessResponse; import org.wso2.carbon.identity.action.execution.model.ActionType; import org.wso2.carbon.identity.action.execution.model.Error; @@ -29,6 +30,8 @@ import org.wso2.carbon.identity.action.execution.model.Event; import org.wso2.carbon.identity.action.execution.model.FailedStatus; import org.wso2.carbon.identity.action.execution.model.Failure; +import org.wso2.carbon.identity.action.execution.model.Incomplete; +import org.wso2.carbon.identity.action.execution.model.IncompleteStatus; import org.wso2.carbon.identity.action.execution.model.Success; import java.util.Map; @@ -47,6 +50,24 @@ ActionExecutionStatus processSuccessResponse(Map eventC ActionInvocationSuccessResponse successResponse) throws ActionExecutionResponseProcessorException; + /** + * This method processes the incomplete response received from the action execution. + * + * @param eventContext The event context. + * @param actionEvent The action event. + * @param incompleteResponse The incomplete response. + * @return The incomplete status. + * @throws ActionExecutionResponseProcessorException If an error occurs while processing the response. + */ + default ActionExecutionStatus processIncompleteResponse(Map eventContext, + Event actionEvent, ActionInvocationIncompleteResponse incompleteResponse) throws + ActionExecutionResponseProcessorException { + + return new IncompleteStatus.Builder() + .incomplete(new Incomplete(incompleteResponse.getOperations())) + .responseContext(eventContext).build(); + } + default ActionExecutionStatus processErrorResponse(Map eventContext, Event actionEvent, ActionInvocationErrorResponse errorResponse) throws diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorService.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorService.java index 7ade719e2eea..7c645213f93e 100644 --- a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorService.java +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/ActionExecutorService.java @@ -51,4 +51,18 @@ public interface ActionExecutorService { ActionExecutionStatus execute(ActionType actionType, Map eventContext, String tenantDomain) throws ActionExecutionException; + /** + * Resolve the actions by given the action id list and execute them. + * + * @param actionType Action Type. + * @param actionIdList Lis of action Ids of the actions that need to be executed. + * @param eventContext The event context of the corresponding flow. + * @param tenantDomain Tenant domain. + * @return {@link ActionExecutionStatus} The status of the action execution and the response context. + * @throws ActionExecutionException If an error occurs while executing the action. + */ + ActionExecutionStatus execute(ActionType actionType, String[] actionIdList, + Map eventContext, String tenantDomain) + throws ActionExecutionException; + } diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/impl/ActionExecutorServiceImpl.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/impl/ActionExecutorServiceImpl.java index d4c1ffc4eee8..0c00a88134f2 100644 --- a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/impl/ActionExecutorServiceImpl.java +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/impl/ActionExecutorServiceImpl.java @@ -36,12 +36,14 @@ import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus; import org.wso2.carbon.identity.action.execution.model.ActionInvocationErrorResponse; import org.wso2.carbon.identity.action.execution.model.ActionInvocationFailureResponse; +import org.wso2.carbon.identity.action.execution.model.ActionInvocationIncompleteResponse; import org.wso2.carbon.identity.action.execution.model.ActionInvocationResponse; import org.wso2.carbon.identity.action.execution.model.ActionInvocationSuccessResponse; import org.wso2.carbon.identity.action.execution.model.ActionType; import org.wso2.carbon.identity.action.execution.model.AllowedOperation; import org.wso2.carbon.identity.action.execution.model.Error; import org.wso2.carbon.identity.action.execution.model.Failure; +import org.wso2.carbon.identity.action.execution.model.Incomplete; import org.wso2.carbon.identity.action.execution.model.PerformableOperation; import org.wso2.carbon.identity.action.execution.model.Request; import org.wso2.carbon.identity.action.execution.model.Success; @@ -106,6 +108,7 @@ public boolean isExecutionEnabled(ActionType actionType) { * @param tenantDomain Tenant domain. * @return Action execution status. */ + @Override public ActionExecutionStatus execute(ActionType actionType, Map eventContext, String tenantDomain) throws ActionExecutionException { @@ -131,6 +134,7 @@ public ActionExecutionStatus execute(ActionType actionType, Map execute(ActionType actionType, String[] actionIdList, Map eventContext, String tenantDomain) throws ActionExecutionException { @@ -310,6 +314,10 @@ private ActionExecutionStatus processActionResponse(Action action, return processSuccessResponse(action, (ActionInvocationSuccessResponse) actionInvocationResponse.getResponse(), eventContext, actionRequest, actionExecutionResponseProcessor); + } else if (actionInvocationResponse.isIncomplete()) { + return processIncompleteResponse(action, + (ActionInvocationIncompleteResponse) actionInvocationResponse.getResponse(), + eventContext, actionRequest, actionExecutionResponseProcessor); } else if (actionInvocationResponse.isFailure() && actionInvocationResponse.getResponse() != null) { return processFailureResponse(action, (ActionInvocationFailureResponse) actionInvocationResponse .getResponse(), eventContext, actionRequest, actionExecutionResponseProcessor); @@ -333,14 +341,35 @@ private ActionExecutionStatus processSuccessResponse(Action action, logSuccessResponse(action, successResponse); List allowedPerformableOperations = - validatePerformableOperations(actionRequest, successResponse, action); + validatePerformableOperations(actionRequest, successResponse.getOperations(), action); ActionInvocationSuccessResponse.Builder successResponseBuilder = new ActionInvocationSuccessResponse.Builder().actionStatus(ActionInvocationResponse.Status.SUCCESS) - .operations(allowedPerformableOperations); + .operations(allowedPerformableOperations) + .data(successResponse.getData()); return actionExecutionResponseProcessor.processSuccessResponse(eventContext, actionRequest.getEvent(), successResponseBuilder.build()); } + private ActionExecutionStatus processIncompleteResponse( + Action action, + ActionInvocationIncompleteResponse incompleteResponse, + Map eventContext, + ActionExecutionRequest actionRequest, + ActionExecutionResponseProcessor actionExecutionResponseProcessor) + throws ActionExecutionResponseProcessorException { + + //logSuccessResponse(action, successResponse); + + List allowedPerformableOperations = + validatePerformableOperations(actionRequest, incompleteResponse.getOperations(), action); + ActionInvocationIncompleteResponse.Builder incompleteResponseBuilder = + new ActionInvocationIncompleteResponse.Builder().actionStatus( + ActionInvocationResponse.Status.INCOMPLETE) + .operations(allowedPerformableOperations); + return actionExecutionResponseProcessor.processIncompleteResponse(eventContext, + actionRequest.getEvent(), incompleteResponseBuilder.build()); + } + private ActionExecutionStatus processErrorResponse(Action action, ActionInvocationErrorResponse errorResponse, Map eventContext, @@ -472,11 +501,11 @@ private String serializeFailureResponse(ActionInvocationFailureResponse response } private List validatePerformableOperations( - ActionExecutionRequest request, ActionInvocationSuccessResponse response, Action action) { + ActionExecutionRequest request, List operations, Action action) { List allowedOperations = request.getAllowedOperations(); - List allowedPerformableOperations = response.getOperations().stream() + List allowedPerformableOperations = operations.stream() .filter(performableOperation -> allowedOperations.stream() .anyMatch(allowedOperation -> OperationComparator.compare(allowedOperation, performableOperation))) @@ -486,7 +515,7 @@ private List validatePerformableOperations( List allowedOps = new ArrayList<>(); List notAllowedOps = new ArrayList<>(); - response.getOperations().forEach(operation -> { + operations.forEach(operation -> { String operationDetails = "Operation: " + operation.getOp() + " Path: " + operation.getPath(); if (allowedPerformableOperations.contains(operation)) { allowedOps.add(operationDetails); diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionStatus.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionStatus.java index dfe00c29a212..336344b88413 100644 --- a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionStatus.java +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionExecutionStatus.java @@ -25,7 +25,8 @@ * Action Execution Status is the status object that is returned by the Action Executor Service after executing an * action. It contains the status of the action execution and the response context. * - * @param Status type (i.e. SUCCESS {@link Success}, FAILED {@link Failure}, ERROR {@link Error}) + * @param Status type (i.e. SUCCESS {@link Success}, FAILED {@link Failure}, ERROR {@link Error}, + * INCOMPLETE {@link Incomplete}) */ public abstract class ActionExecutionStatus { diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationIncompleteResponse.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationIncompleteResponse.java new file mode 100644 index 000000000000..e69faa54a74d --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationIncompleteResponse.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.action.execution.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import java.util.List; + +/** + * This class is used to represent the incomplete response of an action invocation. + * This response will contain the list of operations that need to be performed. + */ +@JsonDeserialize(builder = ActionInvocationIncompleteResponse.Builder.class) +public class ActionInvocationIncompleteResponse implements ActionInvocationResponse.APIResponse { + + private final ActionInvocationResponse.Status actionStatus; + + private final List operations; + + private ActionInvocationIncompleteResponse(Builder builder) { + + this.actionStatus = builder.actionStatus; + this.operations = builder.operations; + } + + @Override + public ActionInvocationResponse.Status getActionStatus() { + + return actionStatus; + } + + public List getOperations() { + + return operations; + } + + /** + * This class is used to build the {@link ActionInvocationIncompleteResponse}. + */ + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private ActionInvocationResponse.Status actionStatus; + private List operations; + + @JsonProperty("actionStatus") + public Builder actionStatus(ActionInvocationResponse.Status actionStatus) { + + this.actionStatus = actionStatus; + return this; + } + + @JsonProperty("operations") + public Builder operations(@JsonProperty("operations") List operations) { + + this.operations = operations; + return this; + } + + public ActionInvocationIncompleteResponse build() { + + if (this.actionStatus == null) { + throw new IllegalArgumentException("actionStatus must not be null."); + } + + if (!ActionInvocationResponse.Status.INCOMPLETE.equals(actionStatus)) { + throw new IllegalArgumentException("actionStatus must be INCOMPLETE."); + } + + if (this.operations == null) { + throw new IllegalArgumentException("operations must not be null."); + } + + return new ActionInvocationIncompleteResponse(this); + } + } +} + diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationResponse.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationResponse.java index abe512e8db43..f7af78736e7d 100644 --- a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationResponse.java +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationResponse.java @@ -45,6 +45,11 @@ public boolean isSuccess() { return Status.SUCCESS.equals(actionStatus); } + public boolean isIncomplete() { + + return Status.INCOMPLETE.equals(actionStatus); + } + public boolean isFailure() { return Status.FAILED.equals(actionStatus); @@ -70,6 +75,7 @@ public String getErrorLog() { */ public enum Status { SUCCESS, + INCOMPLETE, FAILED, ERROR } diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationSuccessResponse.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationSuccessResponse.java index 402580e60f93..56e3d167ed7b 100644 --- a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationSuccessResponse.java +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/ActionInvocationSuccessResponse.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import org.apache.commons.lang.StringUtils; import java.util.List; @@ -32,13 +33,14 @@ public class ActionInvocationSuccessResponse implements ActionInvocationResponse.APIResponse { private final ActionInvocationResponse.Status actionStatus; - private final List operations; + private final String data; private ActionInvocationSuccessResponse(Builder builder) { this.actionStatus = builder.actionStatus; this.operations = builder.operations; + this.data = builder.data; } @Override @@ -52,6 +54,11 @@ public List getOperations() { return operations; } + public String getData() { + + return data; + } + /** * This class is used to build the {@link ActionInvocationSuccessResponse}. */ @@ -60,6 +67,7 @@ public static class Builder { private ActionInvocationResponse.Status actionStatus; private List operations; + private String data = StringUtils.EMPTY; @JsonProperty("actionStatus") public Builder actionStatus(ActionInvocationResponse.Status actionStatus) { @@ -75,6 +83,13 @@ public Builder operations(@JsonProperty("operations") List return this; } + @JsonProperty("data") + public Builder data(@JsonProperty("data") String data) { + + this.data = data; + return this; + } + public ActionInvocationSuccessResponse build() { if (this.actionStatus == null) { diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Incomplete.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Incomplete.java new file mode 100644 index 000000000000..63308592a228 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/Incomplete.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.action.execution.model; + +import java.util.List; + +/** + * This interface models the Incomplete status. + * If the downstream extension needs to compose the responses with INCOMPLETE status and communicate it back, + * this class can be used by consumers to implement the model for that incomplete response. + */ +public class Incomplete { + + private final List performableOperations; + + public Incomplete(List incompleteReasons) { + + this.performableOperations = incompleteReasons; + } + + public List getPerformableOperations() { + + return performableOperations; + } +} diff --git a/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/IncompleteStatus.java b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/IncompleteStatus.java new file mode 100644 index 000000000000..a3bbbfabf891 --- /dev/null +++ b/components/action-mgt/org.wso2.carbon.identity.action.execution/src/main/java/org/wso2/carbon/identity/action/execution/model/IncompleteStatus.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.action.execution.model; + +import java.util.Map; + +/** + * This class models the IncompleteStatus. + */ +public class IncompleteStatus extends ActionExecutionStatus { + + private final Incomplete incomplete; + + private IncompleteStatus(Builder builder) { + + this.status = Status.INCOMPLETE; + this.incomplete = builder.incomplete; + this.responseContext = builder.responseContext; + } + + @Override + public Incomplete getResponse() { + + return incomplete; + } + + /** + * This class is the builder for IncompleteStatus. + */ + public static class Builder { + + private Incomplete incomplete; + private Map responseContext; + + public Builder incomplete(Incomplete incomplete) { + + this.incomplete = incomplete; + return this; + } + + public Builder responseContext(Map responseContext) { + + this.responseContext = responseContext; + return this; + } + + public IncompleteStatus build() { + + return new IncompleteStatus(this); + } + } +}