All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.deltafi.actionkit.action.Action Maven / Gradle / Ivy

/*
 *    DeltaFi - Data transformation and enrichment platform
 *
 *    Copyright 2021-2023 DeltaFi Contributors 
 *
 *    Licensed 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.deltafi.actionkit.action;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.deltafi.actionkit.action.error.ErrorResult;
import org.deltafi.actionkit.action.parameters.ActionParameters;
import org.deltafi.actionkit.action.util.ActionParameterSchemaGenerator;
import org.deltafi.common.content.ContentStorageService;
import org.deltafi.common.types.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;

/**
 * Base class for all DeltaFi Actions.  No action should directly extend this class, but should use
 * specialized classes in the action taxonomy (LoadAction, EgressAction, etc.)
 * @param  The input type
 * @param 

The parameter class that will be used to configure the action instance. * @param The result type */ @RequiredArgsConstructor @Slf4j public abstract class Action { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .registerModule(new JavaTimeModule()) .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); @Autowired @Setter protected ContentStorageService contentStorageService; @Getter private ActionExecution actionExecution = null; /** * Deep introspection to get the ActionParameters type class. This keeps subclasses * from having to pass this type info as a constructor parameter. */ @SuppressWarnings("unchecked") private Class

getGenericParameterType() { Class clazz = getClass(); Type type = clazz.getGenericSuperclass(); while (type != null) { try { Class

typeClz = (Class

) ((ParameterizedType) type).getActualTypeArguments()[0]; if (ActionParameters.class.isAssignableFrom(typeClz)) { return typeClz; } } catch (Throwable t) { // Must be a non-generic class in the inheritance tree } clazz = clazz.getSuperclass(); type = clazz.getGenericSuperclass(); } throw new RuntimeException("Cannot instantiate" + getClass()); } protected final Class

paramClass = getGenericParameterType(); private final ActionType actionType; private final String description; private ActionDescriptor actionDescriptor; private Map definition; /** * Builds the action-specific input instance used by the execute method. * @param actionContext the context for the specific instance of the action being executed * @param deltaFileMessage the DeltaFileMessage to build the input from * @return the action-specific input instance */ protected abstract I buildInput(@NotNull ActionContext actionContext, @NotNull DeltaFileMessage deltaFileMessage); /** * Builds an action-specific input instance used by the execute method from a list of action-specific inputs. This * method is used when the action context includes a collect configuration. * @param actionInputs the list of action-specific inputs * @return the combined action-specific input instance */ protected I collect(@NotNull List actionInputs) { throw new UnsupportedOperationException("Collect is not supported for " + getClassCanonicalName()); } /** * This is the action entry point where all specific action functionality is implemented. * @param context The context for the specific instance of the action being executed. This includes the name * of the action, the flow to which it is attached, the version of the action, and the hostname. * @param input The action-specific input to the action * @param params Any configuration parameters that belong to the specific instance of the action. * @return An action result object. If there is an error, an ErrorResult object should be returned. * @see ErrorResult */ protected abstract R execute(@NotNull ActionContext context, @NotNull I input, @NotNull P params); public R executeAction(@NotNull ActionInput actionInput) { if (actionInput.getDeltaFileMessages() == null || actionInput.getDeltaFileMessages().isEmpty()) { throw new ActionKitException("Received actionInput with no deltaFileMessages for did " + actionInput.getActionContext().getDid()); } actionExecution = new ActionExecution(getClassCanonicalName(), actionInput.getActionContext().getName(), actionInput.getActionContext().getDid(), OffsetDateTime.now()); if (actionInput.getActionContext().getCollect() != null) { return execute(actionInput.getActionContext(), collect(actionInput.getDeltaFileMessages().stream() .map(deltaFileMessage -> buildInput(actionInput.getActionContext(), deltaFileMessage)).toList()), convertToParams(actionInput.getActionParams())); } return execute(actionInput.getActionContext(), buildInput(actionInput.getActionContext(), actionInput.getDeltaFileMessages().get(0)), convertToParams(actionInput.getActionParams())); } public void clearActionExecution() { actionExecution = null; } public ActionDescriptor getActionDescriptor() { if (actionDescriptor == null) { actionDescriptor = buildActionDescriptor(); } return actionDescriptor; } protected ActionDescriptor buildActionDescriptor() { return ActionDescriptor.builder() .name(getClassCanonicalName()) .description(description) .type(actionType) .schema(getDefinition()) .build(); } /** * Generate a key/value map for the parameter schema of this action * @return Map of parameter class used to configure this action */ public Map getDefinition() { if (definition == null) { JsonNode schemaJson = ActionParameterSchemaGenerator.generateSchema(paramClass); definition = OBJECT_MAPPER.convertValue(schemaJson, new TypeReference<>() {}); log.trace("Action schema: {}", schemaJson.toPrettyString()); } return definition; } /** * Safely get the canonical name of this action class * @return the canonical name of the action class as a string */ public String getClassCanonicalName() { return getClass().getCanonicalName(); } /** * Convert a map of key/values to a parameter object for the Action * @param params Key-value map representing the values in the parameter object * @return a parameter object initialized by the params map */ public P convertToParams(@NotNull Map params) { return OBJECT_MAPPER.convertValue(params, paramClass); } }