org.cloudfoundry.multiapps.controller.process.steps.SyncFlowableStep Maven / Gradle / Ivy
package org.cloudfoundry.multiapps.controller.process.steps;
import java.util.function.BiFunction;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.apache.commons.lang3.StringUtils;
import org.cloudfoundry.multiapps.common.ContentException;
import org.cloudfoundry.multiapps.common.SLException;
import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider;
import org.cloudfoundry.multiapps.controller.core.util.ApplicationConfiguration;
import org.cloudfoundry.multiapps.controller.core.util.LoggingUtil;
import org.cloudfoundry.multiapps.controller.persistence.services.FileService;
import org.cloudfoundry.multiapps.controller.persistence.services.ProcessLoggerPersister;
import org.cloudfoundry.multiapps.controller.persistence.services.ProcessLoggerProvider;
import org.cloudfoundry.multiapps.controller.persistence.services.ProgressMessageService;
import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper;
import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper.CloudComponents;
import org.cloudfoundry.multiapps.controller.process.util.ProcessHelper;
import org.cloudfoundry.multiapps.controller.process.util.StepLogger;
import org.cloudfoundry.multiapps.controller.process.variables.VariableHandling;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sap.cloudfoundry.client.facade.CloudControllerException;
import com.sap.cloudfoundry.client.facade.CloudOperationException;
import com.sap.cloudfoundry.client.facade.CloudServiceBrokerException;
import io.netty.handler.timeout.TimeoutException;
public abstract class SyncFlowableStep implements JavaDelegate {
protected final Logger logger = LoggerFactory.getLogger(getClass());
@Inject
protected CloudControllerClientProvider clientProvider;
@Inject
protected ProgressMessageService progressMessageService;
@Inject
@Named("fileService")
protected FileService fileService;
protected ProcessStepHelper stepHelper;
@Inject
protected ApplicationConfiguration configuration;
@Inject
private StepLogger.Factory stepLoggerFactory;
@Inject
private ProcessEngineConfiguration processEngineConfiguration;
@Inject
private ProcessLoggerProvider processLoggerProvider;
@Inject
private ProcessLoggerPersister processLoggerPersister;
private StepLogger stepLogger;
@Inject
private ProcessHelper processHelper;
@Override
public void execute(DelegateExecution execution) {
LoggingUtil.logWithCorrelationId(VariableHandling.get(execution, Variables.CORRELATION_ID), () -> executeInternal(execution));
}
private void executeInternal(DelegateExecution execution) {
initializeStepLogger(execution);
ProcessContext context = createProcessContext(execution);
StepPhase stepPhase = getInitialStepPhase(context);
try {
getStepHelper().failStepIfProcessIsAborted(context);
getStepHelper().preExecuteStep(context, stepPhase);
stepPhase = executeStep(context);
if (stepPhase == StepPhase.RETRY) {
throw new SLException("A step of the process has failed. Retrying it may solve the issue.");
}
} catch (Exception e) {
stepPhase = StepPhase.RETRY;
handleException(context, e);
} finally {
context.setVariable(Variables.STEP_PHASE, stepPhase);
postExecuteStep(context, stepPhase);
}
}
protected StepPhase getInitialStepPhase(ProcessContext context) {
return StepPhase.EXECUTE;
}
protected ProcessContext createProcessContext(DelegateExecution execution) {
return new ProcessContext(execution, stepLogger, clientProvider);
}
private void handleException(ProcessContext context, Exception e) {
try {
StepPhase stepPhase = context.getVariable(Variables.STEP_PHASE);
if (stepPhase == StepPhase.POLL) {
throw e;
}
onStepError(context, e);
} catch (Exception ex) {
ex = getWithProperMessage(ex);
getStepHelper().logExceptionAndStoreProgressMessage(context, ex);
throw ex instanceof RuntimeException ? (RuntimeException) ex : new RuntimeException(ex);
}
}
/**
*
* Handle exception thrown during {@link #executeStep(ProcessContext) executeStep}
*
*
* Can be overridden if standard behavior does not fulfill custom step requirements. For example, exception can be parsed to other
* exception or not thrown at all.
*
*
*
* @param context flowable context of the step
* @param e thrown exception from {@link #executeStep(ProcessContext) executeStep} and pre-processed by
* {@link #handleException(ProcessContext, Exception) handleException}
* @throws Exception in case derivative methods throw exception
*/
protected void onStepError(ProcessContext context, Exception e) throws Exception {
processException(e, getStepErrorMessage(context), getErrorMessageAdditionalDescription(e, context));
}
protected void processException(Exception e, String detailedMessage, String description) throws Exception {
e = handleControllerException(e);
throw getExceptionConstructor(e).apply(e, detailedMessage + ": " + e.getMessage() + " " + description);
}
private Exception handleControllerException(Exception e) {
if (e instanceof CloudOperationException && !(e instanceof CloudServiceBrokerException)) {
return new CloudControllerException((CloudOperationException) e);
}
if (e instanceof TimeoutException) {
return new SLException(e,
Messages.TIMEOUT_0_EXCEEDED_WHILE_WAITING_CLOUD_CONTROLLER,
configuration.getControllerClientResponseTimeout()
.toSeconds());
}
return e;
}
private BiFunction getExceptionConstructor(Exception e) {
if (e instanceof ContentException) {
return ContentException::new;
}
return SLException::new;
}
protected String getErrorMessageAdditionalDescription(Exception e, ProcessContext context) {
if (e instanceof ContentException) {
return StringUtils.EMPTY;
}
if (e instanceof CloudServiceBrokerException) {
return getStepErrorMessageAdditionalDescription(context);
}
if (e instanceof CloudOperationException || e instanceof CloudControllerException) {
return ExceptionMessageTailMapper.map(configuration, CloudComponents.CLOUD_CONTROLLER, null);
}
return ExceptionMessageTailMapper.map(configuration, CloudComponents.DEPLOY_SERVICE, null);
}
protected void postExecuteStep(ProcessContext context, StepPhase stepState) {
try {
getStepHelper().postExecuteStep(context, stepState);
} catch (SLException e) {
getStepHelper().logExceptionAndStoreProgressMessage(context, e);
throw e;
}
}
protected abstract String getStepErrorMessage(ProcessContext context);
protected abstract StepPhase executeStep(ProcessContext context) throws Exception;
protected StepLogger getStepLogger() {
if (stepLogger == null) {
throw new IllegalStateException(Messages.STEP_LOGGER_NOT_INITIALIZED);
}
return stepLogger;
}
protected void initializeStepLogger(DelegateExecution execution) {
stepLogger = stepLoggerFactory.create(execution, progressMessageService, processLoggerProvider, logger);
}
protected Exception getWithProperMessage(Exception e) {
if (StringUtils.isEmpty(e.getMessage())) {
return new Exception("An unknown error occurred", e);
}
return e;
}
protected ProcessStepHelper getStepHelper() {
if (stepHelper == null) {
stepHelper = ImmutableProcessStepHelper.builder()
.progressMessageService(getProgressMessageService())
.stepLogger(getStepLogger())
.processLoggerPersister(processLoggerPersister)
.processEngineConfiguration(processEngineConfiguration)
.processHelper(processHelper)
.build();
}
return stepHelper;
}
protected ProgressMessageService getProgressMessageService() {
return progressMessageService;
}
protected String getStepErrorMessageAdditionalDescription(ProcessContext context) {
return StringUtils.EMPTY;
}
protected ProcessLoggerPersister getProcessLogsPersister() {
return processLoggerPersister;
}
}