package com.sequenceiq.cloudbreak.core.flow2; import static com.sequenceiq.cloudbreak.core.flow2.Flow2Handler.FLOW_CHAIN_ID; import static com.sequenceiq.cloudbreak.core.flow2.Flow2Handler.FLOW_ID; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.action.Action; import com.sequenceiq.cloudbreak.cloud.event.Payload; import com.sequenceiq.cloudbreak.cloud.event.Selectable; import reactor.bus.Event; import reactor.bus.EventBus; public abstract class AbstractAction<S extends FlowState, E extends FlowEvent, C extends CommonContext, P extends Payload> implements Action<S, E> { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAction.class); private static final String FLOW_START_TIME = "FLOW_START_TIME"; private static final String FLOW_START_EXEC_TIME = "FLOW_START_EXEC_TIME"; private static final String FLOW_STATE_NAME = "FLOW_STATE_NAME"; private static final int MS_PER_SEC = 1000; @Inject private EventBus eventBus; @Inject private FlowRegister runningFlows; private Class<P> payloadClass; private List<PayloadConverter<P>> payloadConverters; private E failureEvent; protected AbstractAction(Class<P> payloadClass) { this.payloadClass = payloadClass; } @PostConstruct public void init() { payloadConverters = new ArrayList<>(); initPayloadConverterMap(payloadConverters); } @Override public void execute(StateContext<S, E> context) { String flowId = (String) context.getMessageHeader(MessageFactory.HEADERS.FLOW_ID.name()); P payload = convertPayload(context.getMessageHeader(MessageFactory.HEADERS.DATA.name())); C flowContext = null; try { Map<Object, Object> variables = context.getExtendedState().getVariables(); prepareExecution(payload, variables); flowContext = createFlowContext(flowId, context, payload); Object flowStartTime = variables.get(FLOW_START_TIME); if (flowStartTime != null) { Object execTime = variables.get(FLOW_START_EXEC_TIME); long flowElapsed = (System.currentTimeMillis() - (long) flowStartTime) / MS_PER_SEC; long execElapsed = (System.currentTimeMillis() - (long) execTime) / MS_PER_SEC; LOGGER.info("Stack: {}, flow state: {}, phase: {}, execution time {} sec", payload.getStackId(), variables.get(FLOW_STATE_NAME), execElapsed > flowElapsed ? "doExec" : "service", execElapsed > flowElapsed ? execElapsed : flowElapsed); } variables.put(FLOW_STATE_NAME, context.getStateMachine().getState().getId()); variables.put(FLOW_START_EXEC_TIME, System.currentTimeMillis()); doExecute(flowContext, payload, variables); variables.put(FLOW_START_TIME, System.currentTimeMillis()); } catch (Exception ex) { LOGGER.error("Error during execution of " + getClass().getName(), ex); if (failureEvent != null) { sendEvent(flowId, failureEvent.event(), getFailurePayload(payload, Optional.ofNullable(flowContext), ex)); } else { LOGGER.error("Missing error handling for " + getClass().getName()); } } } public void setFailureEvent(E failureEvent) { if (this.failureEvent != null && !this.failureEvent.equals(failureEvent)) { throw new UnsupportedOperationException("Failure event already configured. Actions reusable not allowed!"); } this.failureEvent = failureEvent; } protected Flow getFlow(String flowId) { return runningFlows.get(flowId); } protected void sendEvent(C context) { Selectable payload = createRequest(context); sendEvent(context.getFlowId(), payload.selector(), payload); } protected void sendEvent(String flowId, Selectable payload) { sendEvent(flowId, payload.selector(), payload); } protected void sendEvent(String flowId, String selector, Object payload) { LOGGER.info("Triggering event: {}", payload); Map<String, Object> headers = new HashMap<>(); headers.put(FLOW_ID, flowId); String flowChainId = runningFlows.getFlowChainId(flowId); if (flowChainId != null) { headers.put(FLOW_CHAIN_ID, flowChainId); } eventBus.notify(selector, new Event<>(new Event.Headers(headers), payload)); } protected void initPayloadConverterMap(List<PayloadConverter<P>> payloadConverters) { // By default payloadconvertermap is empty. } protected void prepareExecution(P payload, Map<Object, Object> variables) { } protected Selectable createRequest(C context) { throw new UnsupportedOperationException("Context based request creation is not supported by default"); } protected abstract C createFlowContext(String flowId, StateContext<S, E> stateContext, P payload); protected abstract void doExecute(C context, P payload, Map<Object, Object> variables) throws Exception; protected abstract Object getFailurePayload(P payload, Optional<C> flowContext, Exception ex); private P convertPayload(Object payload) { P result = null; try { if (payload == null || payloadClass.isAssignableFrom(payload.getClass())) { result = (P) payload; } else { for (PayloadConverter<P> payloadConverter : payloadConverters) { if (payloadConverter.canConvert(payload.getClass())) { result = payloadConverter.convert(payload); break; } } if (result == null) { LOGGER.error("No payload converter found for {}, payload will be null", payload); } } } catch (Exception ex) { LOGGER.error("Error happened during payload conversion, converted payload will be null! ", ex); } return result; } }