package com.evolveum.midpoint.wf.impl.processors.general; import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.model.api.hooks.HookOperationMode; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.api.WorkflowException; import com.evolveum.midpoint.wf.impl.activiti.ActivitiEngine; import com.evolveum.midpoint.wf.impl.tasks.WfTask; import com.evolveum.midpoint.wf.impl.tasks.WfTaskController; import com.evolveum.midpoint.wf.impl.tasks.WfTaskCreationInstruction; import com.evolveum.midpoint.wf.impl.tasks.WfTaskUtil; import com.evolveum.midpoint.wf.impl.messages.ProcessEvent; import com.evolveum.midpoint.wf.impl.messages.TaskEvent; import com.evolveum.midpoint.wf.impl.processors.*; import com.evolveum.midpoint.wf.impl.processors.general.scenarios.DefaultGcpScenarioBean; import com.evolveum.midpoint.wf.impl.processors.general.scenarios.GcpScenarioBean; import com.evolveum.midpoint.wf.impl.util.SerializationSafeContainer; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Map; /** * @author mederly */ @Component public class GeneralChangeProcessor extends BaseChangeProcessor { private static final Trace LOGGER = TraceManager.getTrace(GeneralChangeProcessor.class); @Autowired private PrismContext prismContext; @Autowired private WfTaskUtil wfTaskUtil; @Autowired private WfTaskController wfTaskController; @Autowired private ActivitiEngine activitiEngine; @Autowired private BaseModelInvocationProcessingHelper baseModelInvocationProcessingHelper; @Autowired private BaseConfigurationHelper baseConfigurationHelper; @Autowired private BaseAuditHelper baseAuditHelper; @Autowired private GcpConfigurationHelper gcpConfigurationHelper; @Autowired private GcpExpressionHelper gcpExpressionHelper; @Autowired private GcpExternalizationHelper gcpExternalizationHelper; //region Initialization and Configuration @PostConstruct public void init() { baseConfigurationHelper.registerProcessor(this); } private GcpScenarioBean getScenarioBean(Map<String, Object> variables) { String beanName = (String) variables.get(GcpProcessVariableNames.VARIABLE_MIDPOINT_SCENARIO_BEAN_NAME); return findScenarioBean(beanName); } public GcpScenarioBean findScenarioBean(String name) { if (name == null) { name = lowerFirstChar(DefaultGcpScenarioBean.class.getSimpleName()); } if (getBeanFactory().containsBean(name)) { return getBeanFactory().getBean(name, GcpScenarioBean.class); } else { throw new IllegalStateException("Scenario bean " + name + " couldn't be found."); } } private String lowerFirstChar(String name) { return Character.toLowerCase(name.charAt(0)) + name.substring(1); } //endregion //region Processing model invocation @Override public HookOperationMode processModelInvocation(@NotNull ModelContext context, WfConfigurationType wfConfigurationType, @NotNull Task taskFromModel, @NotNull OperationResult result) throws SchemaException { if (wfConfigurationType != null && wfConfigurationType.getGeneralChangeProcessor() != null && Boolean.FALSE.equals(wfConfigurationType.getGeneralChangeProcessor().isEnabled())) { LOGGER.trace("{} is disabled", getBeanName()); return null; } if (wfConfigurationType == null || wfConfigurationType.getGeneralChangeProcessor() == null || wfConfigurationType.getGeneralChangeProcessor().getScenario().isEmpty()) { LOGGER.trace("No scenarios for {}", getBeanName()); return null; } GeneralChangeProcessorConfigurationType processorConfigurationType = wfConfigurationType.getGeneralChangeProcessor(); for (GeneralChangeProcessorScenarioType scenarioType : processorConfigurationType.getScenario()) { GcpScenarioBean scenarioBean = findScenarioBean(scenarioType.getBeanName()); if (Boolean.FALSE.equals(scenarioType.isEnabled())) { LOGGER.trace("scenario {} is disabled, skipping", scenarioType.getName()); } else if (!gcpExpressionHelper.evaluateActivationCondition(scenarioType, context, taskFromModel, result)) { LOGGER.trace("activationCondition was evaluated to FALSE for scenario named {}", scenarioType.getName()); } else if (!scenarioBean.determineActivation(scenarioType, context, taskFromModel, result)) { LOGGER.trace("scenarioBean decided to skip scenario named {}", scenarioType.getName()); } else { LOGGER.trace("Applying scenario {} (process name {})", scenarioType.getName(), scenarioType.getProcessName()); return applyScenario(scenarioType, scenarioBean, context, taskFromModel, wfConfigurationType, result); } } LOGGER.trace("No scenario found to be applicable, exiting the change processor."); return null; } private HookOperationMode applyScenario(GeneralChangeProcessorScenarioType scenarioType, GcpScenarioBean scenarioBean, ModelContext context, Task taskFromModel, WfConfigurationType wfConfigurationType, OperationResult result) { try { // ========== preparing root task =========== WfTaskCreationInstruction rootInstruction = baseModelInvocationProcessingHelper.createInstructionForRoot(this, context, taskFromModel, result); WfTask rootWfTask = baseModelInvocationProcessingHelper.submitRootTask(rootInstruction, taskFromModel, wfConfigurationType, result); // ========== preparing child task, starting WF process =========== WfTaskCreationInstruction instruction = scenarioBean.prepareJobCreationInstruction(scenarioType, (LensContext<?>) context, rootWfTask, taskFromModel, result); wfTaskController.submitWfTask(instruction, rootWfTask, wfConfigurationType, result); // ========== complete the action =========== baseModelInvocationProcessingHelper.logJobsBeforeStart(rootWfTask, result); rootWfTask.startWaitingForSubtasks(result); return HookOperationMode.BACKGROUND; } catch (SchemaException|ObjectNotFoundException|CommunicationException|ConfigurationException|ObjectAlreadyExistsException|RuntimeException e) { LoggingUtils.logUnexpectedException(LOGGER, "Workflow process(es) could not be started", e); result.recordFatalError("Workflow process(es) could not be started: " + e, e); return HookOperationMode.ERROR; // todo rollback - at least close open tasks, maybe stop workflow process instances } } //endregion //region Finalizing the processing @Override public void onProcessEnd(ProcessEvent event, WfTask wfTask, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { Task task = wfTask.getTask(); // we simply put model context back into parent task // (or if it is null, we set the task to skip model context processing) // it is safe to directly access the parent, because (1) it is in waiting state, (2) we are its only child Task rootTask = task.getParentTask(result); SerializationSafeContainer<LensContextType> contextContainer = event.getVariable(GcpProcessVariableNames.VARIABLE_MODEL_CONTEXT, SerializationSafeContainer.class); LensContextType lensContextType = null; if (contextContainer != null) { contextContainer.setPrismContext(prismContext); lensContextType = contextContainer.getValue(); } if (lensContextType == null) { LOGGER.debug(GcpProcessVariableNames.VARIABLE_MODEL_CONTEXT + " not present in process, this means we should stop processing. Task = {}", rootTask); wfTaskUtil.storeModelContext(rootTask, (ModelContext) null); } else { LOGGER.debug("Putting (changed or unchanged) value of {} into the task {}", GcpProcessVariableNames.VARIABLE_MODEL_CONTEXT, rootTask); wfTaskUtil.storeModelContext(rootTask, lensContextType); } rootTask.savePendingModifications(result); LOGGER.trace("onProcessEnd ending for task {}", task); } //endregion //region Auditing @Override public AuditEventRecord prepareProcessInstanceAuditRecord(WfTask wfTask, AuditEventStage stage, Map<String, Object> variables, OperationResult result) { return getScenarioBean(variables).prepareProcessInstanceAuditRecord(variables, wfTask, stage, result); } @Override public AuditEventRecord prepareWorkItemCreatedAuditRecord(WorkItemType workItem, TaskEvent taskEvent, WfTask wfTask, OperationResult result) throws WorkflowException { return getScenarioBean(taskEvent.getVariables()).prepareWorkItemCreatedAuditRecord(workItem, wfTask, taskEvent, result); } @Override public AuditEventRecord prepareWorkItemDeletedAuditRecord(WorkItemType workItem, WorkItemEventCauseInformationType cause, TaskEvent taskEvent, WfTask wfTask, OperationResult result) throws WorkflowException { return getScenarioBean(taskEvent.getVariables()) .prepareWorkItemDeletedAuditRecord(workItem, cause, taskEvent, wfTask, result); } //endregion }