Skip to content

Commit

Permalink
Workflows: if all stages result in automaticallyCompleted = skip, no …
Browse files Browse the repository at this point in the history
…wf process is started.
  • Loading branch information
mederly committed Jun 7, 2017
1 parent 21614b6 commit 506ffe1
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 72 deletions.
Expand Up @@ -214,7 +214,7 @@ public static <T extends Containerable> boolean setFieldContainerValue(PrismCont
fieldContainer = new PrismContainer<T>(fieldName, parent.getPrismContext());
fieldContainer.add(fieldContainerValue);
if (parent.getParent() == null) {
parent.add(fieldContainer);
parent.add(fieldContainer); // TODO what if fieldContainer is already there?
} else {
parent.addReplaceExisting(fieldContainer);
}
Expand Down
Expand Up @@ -229,7 +229,7 @@ public static ApprovalStageDefinitionType getStageDefinition(WfContextType wfc,
}
}

private static List<ApprovalStageDefinitionType> getStages(ApprovalSchemaType approvalSchema) {
public static List<ApprovalStageDefinitionType> getStages(ApprovalSchemaType approvalSchema) {
return !approvalSchema.getStage().isEmpty() ? approvalSchema.getStage() : approvalSchema.getLevel();
}

Expand Down
Expand Up @@ -129,9 +129,8 @@ public <F extends FocusType> List<PrismObject<F>> findFocusesByCorrelationRule(C

if (users != null) {
LOGGER.debug(
"SYNCHRONIZATION: CORRELATION: expression for {} returned {} users: {}",
new Object[] { currentShadow, users.size(),
PrettyPrinter.prettyPrint(users, 3) });
"SYNCHRONIZATION: CORRELATION: expression for {} returned {} users: {}", currentShadow, users.size(),
PrettyPrinter.prettyPrint(users, 3));
if (users.size() > 1) {
// remove duplicates
Set<PrismObject<F>> usersWithoutDups = new HashSet<>();
Expand Down Expand Up @@ -242,6 +241,8 @@ private <F extends FocusType> boolean matchUserCorrelationRule(Class<F> focusTyp
return false;
}

// TODO evaluate condition here

ObjectQuery q;
try {
q = QueryJaxbConvertor.createObjectQuery(focusType, conditionalFilter, prismContext);
Expand Down
Expand Up @@ -26,6 +26,7 @@
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
Expand Down Expand Up @@ -164,7 +165,7 @@ private HookOperationMode processModelInvocation(@NotNull ModelContext<? extends
if (hookOperationMode != null) {
return hookOperationMode;
}
} catch (ObjectNotFoundException|SchemaException|RuntimeException e) {
} catch (ObjectNotFoundException|SchemaException|RuntimeException|ExpressionEvaluationException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Exception while running change processor {}", e, changeProcessor.getClass().getName());
result.recordFatalError("Exception while running change processor " + changeProcessor.getClass(), e);
return HookOperationMode.ERROR;
Expand Down
Expand Up @@ -38,10 +38,7 @@
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.wf.impl.util.MiscDataUtil;
import com.evolveum.midpoint.wf.util.ApprovalUtils;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalLevelOutcomeType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.activiti.engine.delegate.DelegateExecution;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -126,37 +123,41 @@ public boolean evaluateBooleanExpression(ExpressionType expressionType, Expressi

public ExpressionVariables getDefaultVariables(@Nullable DelegateExecution execution, Task wfTask, OperationResult result)
throws SchemaException, ObjectNotFoundException {
ExpressionVariables variables = getDefaultVariables(wfTask.getWorkflowContext(), result);
// Activiti process instance variables (use with care)
if (execution != null) {
execution.getVariables().forEach((key, value) -> variables.addVariableDefinition(new QName("_" + key), value));
}
return variables;
}

public ExpressionVariables getDefaultVariables(WfContextType wfContext, OperationResult result)
throws SchemaException, ObjectNotFoundException {

MiscDataUtil miscDataUtil = getMiscDataUtil();

ExpressionVariables variables = new ExpressionVariables();

variables.addVariableDefinition(C_REQUESTER,
miscDataUtil.resolveObjectReference(wfTask.getWorkflowContext().getRequesterRef(), result));
miscDataUtil.resolveObjectReference(wfContext.getRequesterRef(), result));

variables.addVariableDefinition(C_OBJECT,
miscDataUtil.resolveObjectReference(wfTask.getWorkflowContext().getObjectRef(), result));
miscDataUtil.resolveObjectReference(wfContext.getObjectRef(), result));

// might be null
variables.addVariableDefinition(C_TARGET,
miscDataUtil.resolveObjectReference(wfTask.getWorkflowContext().getTargetRef(), result));
miscDataUtil.resolveObjectReference(wfContext.getTargetRef(), result));

ObjectDelta objectDelta;
try {
objectDelta = miscDataUtil.getFocusPrimaryDelta(wfTask.getWorkflowContext(), true);
objectDelta = miscDataUtil.getFocusPrimaryDelta(wfContext, true);
} catch (JAXBException e) {
throw new SchemaException("Couldn't get object delta: " + e.getMessage(), e);
}
variables.addVariableDefinition(SchemaConstants.T_OBJECT_DELTA, objectDelta);

// todo other variables?

// Activiti process instance variables (use with care)
if (execution != null) {
execution.getVariables().entrySet().forEach(e ->
variables.addVariableDefinition(new QName("_" + e.getKey()), e.getValue()));
}

return variables;
}

Expand Down
Expand Up @@ -17,16 +17,28 @@
package com.evolveum.midpoint.wf.impl.processes.itemApproval;

import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.repo.common.expression.ExpressionVariables;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.WfContextUtil;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.wf.impl.processes.common.WfExpressionEvaluationHelper;
import com.evolveum.midpoint.wf.impl.processors.primary.ModelInvocationContext;
import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction;
import com.evolveum.midpoint.wf.impl.tasks.ProcessSpecificContent;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalSchemaType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ItemApprovalProcessStateType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SchemaAttachedPolicyRulesType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.WfProcessSpecificStateType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand All @@ -35,8 +47,10 @@
*/
public class ItemApprovalSpecificContent implements ProcessSpecificContent {

private static final transient Trace LOGGER = TraceManager.getTrace(ItemApprovalSpecificContent.class);

@NotNull private final PrismContext prismContext;
final String taskName;
private final String taskName;
@NotNull final ApprovalSchemaType approvalSchemaType;
@Nullable final SchemaAttachedPolicyRulesType policyRules;

Expand Down Expand Up @@ -64,4 +78,45 @@ public WfProcessSpecificStateType createProcessSpecificState() {
state.setPolicyRules(policyRules);
return state;
}

// skippability because of no approvers was already tested; see ApprovalSchemaHelper.shouldBeSkipped
@Override
public boolean checkEmpty(PcpChildWfTaskCreationInstruction instruction,
WfExpressionEvaluationHelper evaluationHelper, ModelInvocationContext ctx, OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
List<ApprovalStageDefinitionType> stages = WfContextUtil.getStages(approvalSchemaType);
// first pass: if there is any stage that is obviously not skippable, let's return false without checking the expressions
for (ApprovalStageDefinitionType stage : stages) {
if (stage.getAutomaticallyCompleted() == null) {
return false;
}
}
// second pass: check the conditions
for (ApprovalStageDefinitionType stage : stages) {
if (!SchemaConstants.MODEL_APPROVAL_OUTCOME_SKIP.equals(
evaluateAutoCompleteExpression(stage, instruction, evaluationHelper, ctx, result))) {
return false;
}
}
return true;
}

private String evaluateAutoCompleteExpression(ApprovalStageDefinitionType stageDef, PcpChildWfTaskCreationInstruction instruction,
WfExpressionEvaluationHelper evaluationHelper, ModelInvocationContext ctx, OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
ExpressionType expression = stageDef.getAutomaticallyCompleted();
ExpressionVariables variables = evaluationHelper.getDefaultVariables(instruction.getWfContext(), result);
List<String> outcomes = evaluationHelper.evaluateExpression(expression, variables,
"automatic completion expression", String.class,
DOMUtil.XSD_STRING, WfExpressionEvaluationHelper.createOutcomeConvertor(), ctx.taskFromModel, result);
LOGGER.trace("Pre-completed = {} for stage {}", outcomes, stageDef);
Set<String> distinctOutcomes = new HashSet<>(outcomes);
if (distinctOutcomes.isEmpty()) {
return null;
} else if (distinctOutcomes.size() == 1) {
return distinctOutcomes.iterator().next();
} else {
throw new IllegalStateException("Ambiguous result from 'automatically completed' expression: " + distinctOutcomes);
}
}
}
Expand Up @@ -84,7 +84,7 @@ public void execute(DelegateExecution execution) {
} else {
additionalInformation = new AdditionalInformationGenerator().getDefaultAdditionalInformation(wfTask, stageDef.getNumber());
}
if (additionalInformation != null && !additionalInformation.isEmpty()) {
if (!additionalInformation.isEmpty()) {
execution.setVariableLocal(CommonProcessVariableNames.ADDITIONAL_INFORMATION,
new SingleItemSerializationSafeContainerImpl<>(
wrapAdditionalInformationIfNeeded(additionalInformation), prismContext));
Expand Down
Expand Up @@ -23,6 +23,7 @@
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.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
Expand Down Expand Up @@ -79,7 +80,8 @@ public interface ChangeProcessor {
* Actually, the FOREGROUND return value is quite unusual, because the change processor cannot
* know in advance whether other processors would not want to process the invocation from the model.
*/
HookOperationMode processModelInvocation(@NotNull ModelContext<?> context, WfConfigurationType wfConfigurationType, @NotNull Task taskFromModel, @NotNull OperationResult result) throws SchemaException, ObjectNotFoundException;
HookOperationMode processModelInvocation(@NotNull ModelContext<?> context, WfConfigurationType wfConfigurationType, @NotNull Task taskFromModel, @NotNull OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException;

/**
* Handles an event from WfMS that indicates finishing of the workflow process instance.
Expand Down
Expand Up @@ -29,6 +29,7 @@
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
Expand All @@ -37,6 +38,7 @@
import com.evolveum.midpoint.wf.impl.messages.ProcessEvent;
import com.evolveum.midpoint.wf.impl.messages.TaskEvent;
import com.evolveum.midpoint.wf.impl.processes.ProcessInterfaceFinder;
import com.evolveum.midpoint.wf.impl.processes.common.WfExpressionEvaluationHelper;
import com.evolveum.midpoint.wf.impl.processors.BaseAuditHelper;
import com.evolveum.midpoint.wf.impl.processors.BaseChangeProcessor;
import com.evolveum.midpoint.wf.impl.processors.BaseConfigurationHelper;
Expand Down Expand Up @@ -70,32 +72,16 @@ public class PrimaryChangeProcessor extends BaseChangeProcessor {

private static final Trace LOGGER = TraceManager.getTrace(PrimaryChangeProcessor.class);

@Autowired
private PcpConfigurationHelper pcpConfigurationHelper;

@Autowired
private BaseConfigurationHelper baseConfigurationHelper;

@Autowired
private BaseModelInvocationProcessingHelper baseModelInvocationProcessingHelper;

@Autowired
private BaseAuditHelper baseAuditHelper;

@Autowired
private WfTaskUtil wfTaskUtil;

@Autowired
private WfTaskController wfTaskController;

@Autowired
private PcpRepoAccessHelper pcpRepoAccessHelper;

@Autowired
private ProcessInterfaceFinder processInterfaceFinder;

@Autowired
private MiscDataUtil miscDataUtil;
@Autowired private PcpConfigurationHelper pcpConfigurationHelper;
@Autowired private BaseConfigurationHelper baseConfigurationHelper;
@Autowired private BaseModelInvocationProcessingHelper baseModelInvocationProcessingHelper;
@Autowired private BaseAuditHelper baseAuditHelper;
@Autowired private WfTaskUtil wfTaskUtil;
@Autowired private WfTaskController wfTaskController;
@Autowired private PcpRepoAccessHelper pcpRepoAccessHelper;
@Autowired private ProcessInterfaceFinder processInterfaceFinder;
@Autowired private MiscDataUtil miscDataUtil;
@Autowired private WfExpressionEvaluationHelper evaluationHelper;

public static final String UNKNOWN_OID = "?";

Expand All @@ -119,7 +105,7 @@ public void init() {
@Override
public HookOperationMode processModelInvocation(@NotNull ModelContext<?> context, WfConfigurationType wfConfigurationType,
@NotNull Task taskFromModel, @NotNull OperationResult result)
throws SchemaException, ObjectNotFoundException {
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {

if (context.getState() != PRIMARY || context.getFocusContext() == null) {
return null;
Expand All @@ -145,15 +131,28 @@ public HookOperationMode processModelInvocation(@NotNull ModelContext<?> context
List<PcpChildWfTaskCreationInstruction> childTaskInstructions = gatherStartInstructions(changesBeingDecomposed, ctx, result);

// start the process(es)

removeEmptyProcesses(childTaskInstructions, ctx, result);
if (childTaskInstructions.isEmpty()) {
LOGGER.trace("There are no workflow processes to be started, exiting.");
LOGGER.debug("There are no workflow processes to be started, exiting.");
return null;
}
return submitTasks(childTaskInstructions, context, changesBeingDecomposed, taskFromModel, wfConfigurationType, result);
}

private List<PcpChildWfTaskCreationInstruction> gatherStartInstructions(@NotNull ObjectTreeDeltas changesBeingDecomposed,
private void removeEmptyProcesses(List<PcpChildWfTaskCreationInstruction> instructions, ModelInvocationContext ctx,
OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
for (Iterator<PcpChildWfTaskCreationInstruction> iterator = instructions.iterator(); iterator.hasNext(); ) {
PcpChildWfTaskCreationInstruction instruction = iterator.next();
instruction.createProcessorContent(); // brutal hack
if (instruction.startsWorkflowProcess() &&
instruction.getProcessContent().checkEmpty(instruction, evaluationHelper, ctx, result)) {
LOGGER.debug("Skipping empty processing instruction: {}", DebugUtil.debugDumpLazily(instruction));
iterator.remove();
}
}
}

private List<PcpChildWfTaskCreationInstruction> gatherStartInstructions(@NotNull ObjectTreeDeltas changesBeingDecomposed,
ModelInvocationContext ctx, @NotNull OperationResult result) throws SchemaException, ObjectNotFoundException {

PrimaryChangeProcessorConfigurationType processorConfigurationType =
Expand All @@ -166,17 +165,14 @@ private List<PcpChildWfTaskCreationInstruction> gatherStartInstructions(@NotNull
}
List<PcpChildWfTaskCreationInstruction> instructions = aspect.prepareTasks(changesBeingDecomposed, ctx, result);
logAspectResult(aspect, instructions, changesBeingDecomposed);
if (instructions != null) {
startProcessInstructions.addAll(instructions);
}
startProcessInstructions.addAll(instructions);
}
return startProcessInstructions;
}

private Collection<PrimaryChangeAspect> getActiveChangeAspects(PrimaryChangeProcessorConfigurationType processorConfigurationType) {
Collection<PrimaryChangeAspect> rv = getAllChangeAspects().stream()
return getAllChangeAspects().stream()
.filter(aspect -> aspect.isEnabled(processorConfigurationType)).collect(Collectors.toList());
return rv;
}

private void logAspectResult(PrimaryChangeAspect aspect, List<? extends WfTaskCreationInstruction> instructions, ObjectTreeDeltas changesBeingDecomposed) {
Expand Down
Expand Up @@ -17,6 +17,13 @@
package com.evolveum.midpoint.wf.impl.tasks;

import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.wf.impl.processes.common.WfExpressionEvaluationHelper;
import com.evolveum.midpoint.wf.impl.processors.primary.ModelInvocationContext;
import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction;
import com.evolveum.midpoint.xml.ns._public.common.common_3.WfProcessSpecificStateType;

import java.util.Map;
Expand All @@ -31,4 +38,8 @@ public interface ProcessSpecificContent {
void createProcessVariables(Map<String, Object> map, PrismContext prismContext);

WfProcessSpecificStateType createProcessSpecificState();

boolean checkEmpty(PcpChildWfTaskCreationInstruction instruction,
WfExpressionEvaluationHelper evaluationHelper, ModelInvocationContext ctx, OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException;
}

0 comments on commit 506ffe1

Please sign in to comment.