com.evolveum.midpoint.wf.impl.processors.primary.policy.ProcessSpecifications.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.wf.impl.processors.primary.policy.ProcessSpecifications.java

Source

/*
 * Copyright (c) 2010-2017 Evolveum
 *
 * 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 com.evolveum.midpoint.wf.impl.processors.primary.policy;

import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.util.PrismPrettyPrinter;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalPolicyActionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyActionsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.WfProcessSpecificationType;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;

/**
 * Maintains "process specifications" i.e. recipes how to
 *  - analyze incoming deltas (WfProcessSpecificationType.deltaFrom),
 *  - create approval processes
 *  - fill-in their approval schema (list of approval actions with policy rules)
 *
 * TODO find better names
 *
 * @author mederly
 */
public class ProcessSpecifications implements DebugDumpable {

    private final List<ProcessSpecification> specifications = new ArrayList<>();
    private final PrismContext prismContext;

    // use createFromRules instead
    private ProcessSpecifications(PrismContext prismContext) {
        this.prismContext = prismContext;
    }

    public class ProcessSpecification implements DebugDumpable {
        final WfProcessSpecificationType basicSpec;
        final List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>> actionsWithRules;

        ProcessSpecification(
                Map.Entry<WfProcessSpecificationType, List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>>> entry) {
            this.basicSpec = entry.getKey();
            this.actionsWithRules = entry.getValue();
        }

        @Override
        public String debugDump(int indent) {
            StringBuilder sb = new StringBuilder();
            DebugUtil.debugDumpLabelLn(sb, "process specification", indent);
            PrismPrettyPrinter.debugDumpValue(sb, indent + 1, basicSpec, prismContext,
                    ApprovalPolicyActionType.F_PROCESS_SPECIFICATION, PrismContext.LANG_YAML);
            sb.append("\n");
            DebugUtil.debugDumpLabelLn(sb, "actions with rules", indent);
            for (Pair<ApprovalPolicyActionType, EvaluatedPolicyRule> actionWithRule : actionsWithRules) {
                DebugUtil.debugDumpLabelLn(sb, "action", indent + 1);
                PrismPrettyPrinter.debugDumpValue(sb, indent + 2, actionWithRule.getKey(), prismContext,
                        PolicyActionsType.F_APPROVAL, PrismContext.LANG_YAML);
                sb.append("\n");
                if (actionWithRule.getValue() != null) {
                    sb.append("\n").append(actionWithRule.getValue().debugDump(indent + 2));
                }
            }
            return sb.toString();
        }
    }

    static ProcessSpecifications createFromRules(List<EvaluatedPolicyRule> rules, PrismContext prismContext)
            throws ObjectNotFoundException {
        // Step 1: plain list of approval actions -> map: process-spec -> list of related actions/rules ("collected")
        LinkedHashMap<WfProcessSpecificationType, List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>>> collectedSpecifications = new LinkedHashMap<>();
        for (EvaluatedPolicyRule rule : rules) {
            for (ApprovalPolicyActionType approvalAction : rule.getEnabledActions(ApprovalPolicyActionType.class)) {
                WfProcessSpecificationType spec = approvalAction.getProcessSpecification();
                collectedSpecifications.computeIfAbsent(spec, s -> new ArrayList<>())
                        .add(new ImmutablePair<>(approvalAction, rule));
            }
        }
        // Step 2: resolve references
        for (WfProcessSpecificationType spec : new HashSet<>(collectedSpecifications.keySet())) { // cloned to avoid concurrent modification exception
            if (spec != null && spec.getRef() != null) {
                List<Map.Entry<WfProcessSpecificationType, List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>>>> matching = collectedSpecifications
                        .entrySet().stream()
                        .filter(e -> e.getKey() != null && spec.getRef().equals(e.getKey().getName()))
                        .collect(Collectors.toList());
                if (matching.isEmpty()) {
                    throw new IllegalStateException("Process specification named '" + spec.getRef()
                            + "' referenced from an approval action couldn't be found");
                } else if (matching.size() > 1) {
                    throw new IllegalStateException("More than one process specification named '" + spec.getRef()
                            + "' referenced from an approval action: " + matching);
                } else {
                    // move all actions/rules to the referenced process specification
                    List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>> referencedSpecActions = matching
                            .get(0).getValue();
                    referencedSpecActions.addAll(collectedSpecifications.get(spec));
                    collectedSpecifications.remove(spec);
                }
            }
        }

        Map<String, Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>> actionsMap = null;

        // Step 3: include other actions
        for (Map.Entry<WfProcessSpecificationType, List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>>> processSpecificationEntry : collectedSpecifications
                .entrySet()) {
            WfProcessSpecificationType spec = processSpecificationEntry.getKey();
            if (spec == null || spec.getIncludeAction().isEmpty() && spec.getIncludeActionIfPresent().isEmpty()) {
                continue;
            }
            if (actionsMap == null) {
                actionsMap = createActionsMap(collectedSpecifications.values());
            }
            for (String actionToInclude : spec.getIncludeAction()) {
                processActionToInclude(actionToInclude, actionsMap, processSpecificationEntry, true);
            }
            for (String actionToInclude : spec.getIncludeActionIfPresent()) {
                processActionToInclude(actionToInclude, actionsMap, processSpecificationEntry, false);
            }
        }

        // Step 4: sorts process specifications and wraps into ProcessSpecification objects
        ProcessSpecifications rv = new ProcessSpecifications(prismContext);
        collectedSpecifications.entrySet().stream().sorted((ps1, ps2) -> {
            WfProcessSpecificationType key1 = ps1.getKey();
            WfProcessSpecificationType key2 = ps2.getKey();
            if (key1 == null) {
                return key2 == null ? 0 : 1; // non-empty (key2) records first
            } else if (key2 == null) {
                return -1; // non-empty (key1) record first
            }
            int order1 = defaultIfNull(key1.getOrder(), Integer.MAX_VALUE);
            int order2 = defaultIfNull(key2.getOrder(), Integer.MAX_VALUE);
            return Integer.compare(order1, order2);
        }).forEach(e -> rv.specifications.add(rv.new ProcessSpecification(e)));
        return rv;
    }

    private static void processActionToInclude(String actionToInclude,
            Map<String, Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>> actionsMap,
            Map.Entry<WfProcessSpecificationType, List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>>> processSpecificationEntry,
            boolean mustBePresent) throws ObjectNotFoundException {
        Pair<ApprovalPolicyActionType, EvaluatedPolicyRule> actionWithRule = actionsMap.get(actionToInclude);
        if (actionWithRule != null) {
            processSpecificationEntry.getValue().add(actionWithRule);
        } else if (mustBePresent) {
            throw new ObjectNotFoundException("Approval action '" + actionToInclude + "' cannot be found");
        }
    }

    private static Map<String, Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>> createActionsMap(
            Collection<List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>>> allActionsWithRules) {
        Map<String, Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>> rv = new HashMap<>();
        for (List<Pair<ApprovalPolicyActionType, EvaluatedPolicyRule>> actionsWithRules : allActionsWithRules) {
            for (Pair<ApprovalPolicyActionType, EvaluatedPolicyRule> actionWithRule : actionsWithRules) {
                if (actionWithRule.getLeft().getName() != null) {
                    rv.put(actionWithRule.getLeft().getName(), actionWithRule);
                }
            }
        }
        return rv;
    }

    Collection<ProcessSpecification> getSpecifications() {
        return specifications;
    }

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.debugDumpWithLabel(sb, "Process specifications", specifications, indent);
        return sb.toString();
    }
}