Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve action execution to have data in the action invocation success response #6320

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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;

import org.wso2.carbon.identity.action.execution.impl.DefaultResponseData;
import org.wso2.carbon.identity.action.execution.model.ActionType;
import org.wso2.carbon.identity.action.execution.model.ResponseData;

/**
* This interface is used to provide the classes for the action invocation responses defined by the downStream
* component.
*/
public interface ActionInvocationResponseClassProvider {

/**
* Get the supported action type for the public interface ActionInvocationResponseClassProvider.
*
* @return Supported action type.
*/
ActionType getSupportedActionType();

/**
* Get the extended ResponseData class for action invocation success response defined by the downstream component.
*
* @return The extended ResponseData class.
*/
default Class<? extends ResponseData> getSuccessResponseDataClass() {

return DefaultResponseData.class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ private ActionInvocationResponse executeActionAsynchronously(Action action,

String apiEndpoint = action.getEndpoint().getUri();
CompletableFuture<ActionInvocationResponse> actionExecutor = CompletableFuture.supplyAsync(
() -> apiClient.callAPI(apiEndpoint, authenticationMethod, payload), executorService);
() -> apiClient.callAPI(ActionType.valueOf(action.getType().getActionType()),
apiEndpoint, authenticationMethod, payload), executorService);
try {
return actionExecutor.get();
} catch (InterruptedException | ExecutionException e) {
Expand Down Expand Up @@ -372,7 +373,8 @@ private ActionExecutionStatus<Success> processSuccessResponse(Action action,
validatePerformableOperations(actionRequest, successResponse.getOperations(), action);
ActionInvocationSuccessResponse.Builder successResponseBuilder =
new ActionInvocationSuccessResponse.Builder().actionStatus(ActionInvocationResponse.Status.SUCCESS)
.operations(allowedPerformableOperations);
.operations(allowedPerformableOperations)
.responseData(successResponse.getData());
return actionExecutionResponseProcessor.processSuccessResponse(eventContext,
actionRequest.getEvent(), successResponseBuilder.build());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.impl;

import org.wso2.carbon.identity.action.execution.ActionInvocationResponseClassProvider;
import org.wso2.carbon.identity.action.execution.model.ActionType;
import org.wso2.carbon.identity.action.execution.model.ResponseData;

import java.util.HashMap;
import java.util.Map;

/**
* This class manages ActionInvocationResponseClassProvider implementations that extends action invocation responses for
* different action types.
* The ActionInvocationResponseClassFactory is the component that is responsible for providing the classes
* defined by the downstream component based on the action type.
*/
public class ActionInvocationResponseClassFactory {

private static final Map<ActionType, ActionInvocationResponseClassProvider> classProviders = new HashMap<>();

/**
* Register the ActionInvocationResponseClassProvider based on the action type.
*
* @param provider The ActionInvocationResponseClassProvider.
*/
public static void registerActionInvocationResponseClassProvider(
ActionInvocationResponseClassProvider provider) {

classProviders.put(provider.getSupportedActionType(), provider);
}

/**
* Unregister the ActionInvocationResponseClassProvider based on the action type.
*
* @param provider The ActionInvocationResponseClassProvider.
*/
public static void unregisterActionInvocationResponseClassProvider(
ActionInvocationResponseClassProvider provider) {

classProviders.remove(provider.getSupportedActionType());
}

/**
* Get the extended ResponseData class for extended implementations of action invocation responses based on the
* action type.
*
* @param actionType Action type.
* @return The extended ResponseData class.
*/
public static Class<? extends ResponseData> getInvocationSuccessResponseDataClass(ActionType actionType) {

ActionInvocationResponseClassProvider classProvider = classProviders.get(actionType);
if (classProvider != null) {
return classProvider.getSuccessResponseDataClass();
}
return DefaultActionInvocationResponseClassProvider.getInstance().getSuccessResponseDataClass();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.impl;

import org.wso2.carbon.identity.action.execution.ActionInvocationResponseClassProvider;
import org.wso2.carbon.identity.action.execution.model.ActionType;

/**
* Default implementation of the ActionInvocationResponseClassProvider. The downStream components need to extend
* this class, when implementing the ActionInvocationResponseClassProvider.
*/
public class DefaultActionInvocationResponseClassProvider implements ActionInvocationResponseClassProvider {

static DefaultActionInvocationResponseClassProvider instance = new DefaultActionInvocationResponseClassProvider();

public static DefaultActionInvocationResponseClassProvider getInstance() {
return instance;
}

@Override
public ActionType getSupportedActionType() {

throw new UnsupportedOperationException(
"This method is not allowed for DefaultActionInvocationResponseClassProvider.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.impl;

import org.wso2.carbon.identity.action.execution.model.ResponseData;

/**
* Default ResponseData implementation, which can be used when there are no extended ResponseData class for
* the action type.
*/
public class DefaultResponseData implements ResponseData {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.impl;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.wso2.carbon.identity.action.execution.model.ActionType;
import org.wso2.carbon.identity.action.execution.model.ResponseData;

import java.io.IOException;

/**
* Dynamic deserializer for the ResponseData class.
*/
public class ResponseDataDeserializer extends JsonDeserializer<ResponseData> {

public static final String ACTION_TYPE_ATTR_NAME = "actionType";

@Override
public ResponseData deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {

ActionType actionType = (ActionType) ctxt.getAttribute(ACTION_TYPE_ATTR_NAME);
JsonNode node = p.getCodec().readTree(p);
ObjectMapper mapper = (ObjectMapper) p.getCodec();
return mapper.treeToValue(node,
ActionInvocationResponseClassFactory.getInvocationSuccessResponseDataClass(actionType));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
import org.wso2.carbon.identity.action.execution.ActionExecutionRequestBuilder;
import org.wso2.carbon.identity.action.execution.ActionExecutionResponseProcessor;
import org.wso2.carbon.identity.action.execution.ActionExecutorService;
import org.wso2.carbon.identity.action.execution.ActionInvocationResponseClassProvider;
import org.wso2.carbon.identity.action.execution.impl.ActionExecutionRequestBuilderFactory;
import org.wso2.carbon.identity.action.execution.impl.ActionExecutionResponseProcessorFactory;
import org.wso2.carbon.identity.action.execution.impl.ActionExecutorServiceImpl;
import org.wso2.carbon.identity.action.execution.impl.ActionInvocationResponseClassFactory;
import org.wso2.carbon.identity.action.management.service.ActionManagementService;
import org.wso2.carbon.identity.rule.evaluation.service.RuleEvaluationService;

Expand Down Expand Up @@ -162,4 +164,26 @@ protected void unsetRuleEvaluationService(RuleEvaluationService ruleEvaluationSe
LOG.debug("Unregistering reference for RuleEvaluationService in the ActionExecutionServiceComponent.");
ActionExecutionServiceComponentHolder.getInstance().setRuleEvaluationService(ruleEvaluationService);
}

@Reference(
name = "action.execution.response.ActionInvocationResponseClassProvider",
service = ActionInvocationResponseClassProvider.class,
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetInvocationSuccessResponseContextClass"
)
protected void setInvocationSuccessResponseContextClass(ActionInvocationResponseClassProvider classProvider) {

LOG.debug("Registering ActionInvocationResponseClassProvider: " + classProvider.getClass().getName() +
" in the ActionExecutionServiceComponent.");
ActionInvocationResponseClassFactory.registerActionInvocationResponseClassProvider(
classProvider);
}

protected void unsetInvocationSuccessResponseContextClass(ActionInvocationResponseClassProvider classProvider) {

LOG.debug("Unregistering ActionInvocationResponseClassProvider: " + classProvider.getClass().getName() +
" in the ActionExecutionServiceComponent.");
ActionInvocationResponseClassFactory.unregisterActionInvocationResponseClassProvider(classProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import org.wso2.carbon.identity.action.execution.impl.ResponseDataDeserializer;

import java.util.ArrayList;
import java.util.List;

/**
Expand All @@ -32,13 +34,14 @@
public class ActionInvocationSuccessResponse implements ActionInvocationResponse.APIResponse {

private final ActionInvocationResponse.Status actionStatus;

private final List<PerformableOperation> operations;
private final ResponseData data;

private ActionInvocationSuccessResponse(Builder builder) {

this.actionStatus = builder.actionStatus;
this.operations = builder.operations;
this.data = builder.data;
}

@Override
Expand All @@ -52,14 +55,20 @@ public List<PerformableOperation> getOperations() {
return operations;
}

public ResponseData getData() {

return data;
}

/**
* This class is used to build the {@link ActionInvocationSuccessResponse}.
*/
@JsonPOJOBuilder(withPrefix = "")
public static class Builder {

private ActionInvocationResponse.Status actionStatus;
private List<PerformableOperation> operations;
private List<PerformableOperation> operations = new ArrayList<>();
private ResponseData data;

@JsonProperty("actionStatus")
public Builder actionStatus(ActionInvocationResponse.Status actionStatus) {
Expand All @@ -75,6 +84,14 @@ public Builder operations(@JsonProperty("operations") List<PerformableOperation>
return this;
}

@JsonDeserialize(using = ResponseDataDeserializer.class)
@JsonProperty("data")
public Builder responseData(@JsonProperty("data") ResponseData data) {

this.data = data;
return this;
}

public ActionInvocationSuccessResponse build() {

if (this.actionStatus == null) {
Expand All @@ -85,10 +102,6 @@ public ActionInvocationSuccessResponse build() {
throw new IllegalArgumentException("actionStatus must be SUCCESS.");
}

if (this.operations == null) {
throw new IllegalArgumentException("operations must not be null.");
}

return new ActionInvocationSuccessResponse(this);
}
}
Expand Down
Loading
Loading