<commit_before>/*
 * Copyright (c) 2011 Evolveum
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.opensource.org/licenses/cddl1 or
 * CDDLv1.0.txt file in the source code distribution.
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 *
 * Portions Copyrighted 2011 [name of copyright owner]
 */
package com.evolveum.midpoint.model.synchronizer;

import com.evolveum.midpoint.common.refinery.ResourceAccountType;
import com.evolveum.midpoint.common.valueconstruction.ValueConstruction;
import com.evolveum.midpoint.common.valueconstruction.ValueConstructionFactory;
import com.evolveum.midpoint.model.AccountSyncContext;
import com.evolveum.midpoint.model.PolicyDecision;
import com.evolveum.midpoint.model.SyncContext;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.SchemaRegistry;
import com.evolveum.midpoint.schema.delta.DeltaSetTriple;
import com.evolveum.midpoint.schema.delta.PropertyDelta;
import com.evolveum.midpoint.schema.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.schema.exception.ObjectNotFoundException;
import com.evolveum.midpoint.schema.exception.SchemaException;
import com.evolveum.midpoint.schema.processor.Property;
import com.evolveum.midpoint.schema.processor.PropertyValue;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.DebugUtil;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_1.AccountSynchronizationSettingsType;
import com.evolveum.midpoint.xml.ns._public.common.common_1.AssignmentPolicyEnforcementType;
import com.evolveum.midpoint.xml.ns._public.common.common_1.AssignmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_1.ObjectType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.xml.namespace.QName;
import java.util.*;
import java.util.Map.Entry;

/**
 * @author semancik
 */
@Component
public class AssignmentProcessor {

    @Autowired(required = true)
    private RepositoryService repositoryService;

    @Autowired(required = true)
    private ObjectResolver objectResolver;

    @Autowired(required = true)
    private SchemaRegistry schemaRegistry;

    @Autowired(required = true)
    private ValueConstructionFactory valueConstructionFactory;

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

    public void processAssignments(SyncContext context, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {

        AccountSynchronizationSettingsType accountSynchronizationSettings = context.getAccountSynchronizationSettings();
        if (accountSynchronizationSettings != null) {
            AssignmentPolicyEnforcementType assignmentPolicyEnforcement = accountSynchronizationSettings.getAssignmentPolicyEnforcement();
            if (assignmentPolicyEnforcement == AssignmentPolicyEnforcementType.NONE) {
                // No assignment processing
                LOGGER.trace("Assignment enforcement policy set to NONE, skipping assignment processing");

                // But mark all accounts as assigned, so they will be synchronized as expected
                for (AccountSyncContext accCtx : context.getAccountContexts()) {
                    accCtx.setAssigned(true);
                }

                return;
            }
        }

        Collection<PropertyValue<AssignmentType>> assignmentsOld = new HashSet<PropertyValue<AssignmentType>>();
        if (context.getUserTypeOld() != null) {
            List<AssignmentType> assignments = context.getUserTypeOld().getAssignment();
            for (AssignmentType assignment : assignments) {
                assignmentsOld.add(new PropertyValue<AssignmentType>(assignment));
            }
        }

        PropertyDelta assignmentDelta = context.getAssignmentDelta();

        LOGGER.trace("Assignment delta {}", assignmentDelta.dump());

        // TODO: preprocess assignment delta. It is is replace, then we need to convert it to: delete all existing assignments, add all new assignments
        Collection<PropertyValue<AssignmentType>> changedAssignments = assignmentDelta.getValues(AssignmentType.class);

        AssignmentEvaluator assignmentEvaluator = new AssignmentEvaluator();
        assignmentEvaluator.setRepository(repositoryService);
        assignmentEvaluator.setUser(context.getUserNew());
        assignmentEvaluator.setObjectResolver(objectResolver);
        assignmentEvaluator.setSchemaRegistry(schemaRegistry);
        assignmentEvaluator.setValueConstructionFactory(valueConstructionFactory);

        Map<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>> zeroAccountMap = new HashMap<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>>();
        Map<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>> plusAccountMap = new HashMap<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>>();
        Map<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>> minusAccountMap = new HashMap<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>>();

        LOGGER.trace("Old assignments {}", DebugUtil.prettyPrint(assignmentsOld));
        LOGGER.trace("Changed assignments {}", DebugUtil.prettyPrint(changedAssignments));

        ObjectType source = context.getUserTypeOld();
        if (source == null) {
            source = context.getUserNew().getOrParseObjectType();
        }

        Collection<AssignmentType> newAssignments = new HashSet<AssignmentType>();
        Collection<PropertyValue<AssignmentType>> allAssignments = MiscUtil.union(assignmentsOld, changedAssignments);
        for (PropertyValue<AssignmentType> propertyValue : allAssignments) {
            AssignmentType assignmentType = propertyValue.getValue();

            LOGGER.trace("Processing assignment {}", DebugUtil.prettyPrint(assignmentType));

            Assignment evaluatedAssignment = assignmentEvaluator.evaluate(assignmentType, source, result);

            if (assignmentsOld.contains(assignmentType)) {
                // TODO: remember old state
            }

            context.rememberResources(evaluatedAssignment.getResources(result));

            // Sort assignments to sets: unchanged (zero), added (plus), removed (minus)
            if (changedAssignments.contains(assignmentType)) {
                // There was some change

                if (assignmentDelta.isValueToAdd(assignmentType)) {
                    collectToAccountMap(plusAccountMap, evaluatedAssignment, result);
                }
                if (assignmentDelta.isValueToDelete(assignmentType)) {
                    collectToAccountMap(minusAccountMap, evaluatedAssignment, result);
                }

            } else {
                // No change in assignment
                collectToAccountMap(zeroAccountMap, evaluatedAssignment, result);
                newAssignments.add(assignmentType);
            }
        }

        if (LOGGER.isTraceEnabled()) {
            // Dump the maps
            LOGGER.trace("Account maps:\nZERO:\n{}\nPLUS:\n{}\nMINUS:\n{}\n", new Object[]{dumpAccountMap(zeroAccountMap),
                    dumpAccountMap(plusAccountMap), dumpAccountMap(minusAccountMap)});
        }

        Collection<ResourceAccountType> allAccountTypes = MiscUtil.union(zeroAccountMap.keySet(), plusAccountMap.keySet(), minusAccountMap.keySet());
        for (ResourceAccountType rat : allAccountTypes) {

            if (rat.getResourceOid() == null) {
                throw new IllegalStateException("Resource OID null in ResourceAccountType during outbound processing");
            }
            if (rat.getAccountType() == null) {
                throw new IllegalStateException("Account type is null in ResourceAccountType during outbound processing");
            }

            DeltaSetTriple<AccountConstruction> accountDeltaSetTriple = new DeltaSetTriple<AccountConstruction>(zeroAccountMap.get(rat),
                    plusAccountMap.get(rat), minusAccountMap.get(rat));

            Map<QName, DeltaSetTriple<ValueConstruction>> attributeValueDeltaMap = computeAttributeValueDeltaMap(accountDeltaSetTriple);
//			System.out.println("BBB1: "+accountDeltaSetTriple.dump());
//			System.out.println("BBB2: "+attributeValueDeltaMap);

            if (zeroAccountMap.containsKey(rat)) {
                System.out.println(">>>> SC\n" + context.debugDump(2));
                System.out.println("acc\n" + Arrays.toString(context.getAccountContexts().toArray()));
                context.getAccountSyncContext(rat).setAssigned(true);
                // The account existed before the change and should still exist
                processAccountKeep(context, rat, accountDeltaSetTriple, attributeValueDeltaMap, result);
                continue;

            } else if (plusAccountMap.containsKey(rat) && minusAccountMap.containsKey(rat)) {
                context.getAccountSyncContext(rat).setAssigned(true);
                // Account was removed and added in the same operation, therefore keep its original state
                // TODO<commit_msg>removed sysouts from assignment processor<commit_after>/*
 * Copyright (c) 2011 Evolveum
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.opensource.org/licenses/cddl1 or
 * CDDLv1.0.txt file in the source code distribution.
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 *
 * Portions Copyrighted 2011 [name of copyright owner]
 */

package com.evolveum.midpoint.model.synchronizer;

import com.evolveum.midpoint.common.refinery.ResourceAccountType;
import com.evolveum.midpoint.common.valueconstruction.ValueConstruction;
import com.evolveum.midpoint.common.valueconstruction.ValueConstructionFactory;
import com.evolveum.midpoint.model.AccountSyncContext;
import com.evolveum.midpoint.model.PolicyDecision;
import com.evolveum.midpoint.model.SyncContext;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.SchemaRegistry;
import com.evolveum.midpoint.schema.delta.DeltaSetTriple;
import com.evolveum.midpoint.schema.delta.PropertyDelta;
import com.evolveum.midpoint.schema.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.schema.exception.ObjectNotFoundException;
import com.evolveum.midpoint.schema.exception.SchemaException;
import com.evolveum.midpoint.schema.processor.Property;
import com.evolveum.midpoint.schema.processor.PropertyValue;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.DebugUtil;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_1.AccountSynchronizationSettingsType;
import com.evolveum.midpoint.xml.ns._public.common.common_1.AssignmentPolicyEnforcementType;
import com.evolveum.midpoint.xml.ns._public.common.common_1.AssignmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_1.ObjectType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.xml.namespace.QName;
import java.util.*;
import java.util.Map.Entry;

/**
 * @author semancik
 */
@Component
public class AssignmentProcessor {

    @Autowired(required = true)
    private RepositoryService repositoryService;

    @Autowired(required = true)
    private ObjectResolver objectResolver;

    @Autowired(required = true)
    private SchemaRegistry schemaRegistry;

    @Autowired(required = true)
    private ValueConstructionFactory valueConstructionFactory;

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

    public void processAssignments(SyncContext context, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {

        AccountSynchronizationSettingsType accountSynchronizationSettings = context.getAccountSynchronizationSettings();
        if (accountSynchronizationSettings != null) {
            AssignmentPolicyEnforcementType assignmentPolicyEnforcement = accountSynchronizationSettings.getAssignmentPolicyEnforcement();
            if (assignmentPolicyEnforcement == AssignmentPolicyEnforcementType.NONE) {
                // No assignment processing
                LOGGER.trace("Assignment enforcement policy set to NONE, skipping assignment processing");

                // But mark all accounts as assigned, so they will be synchronized as expected
                for (AccountSyncContext accCtx : context.getAccountContexts()) {
                    accCtx.setAssigned(true);
                }

                return;
            }
        }

        Collection<PropertyValue<AssignmentType>> assignmentsOld = new HashSet<PropertyValue<AssignmentType>>();
        if (context.getUserTypeOld() != null) {
            List<AssignmentType> assignments = context.getUserTypeOld().getAssignment();
            for (AssignmentType assignment : assignments) {
                assignmentsOld.add(new PropertyValue<AssignmentType>(assignment));
            }
        }

        PropertyDelta assignmentDelta = context.getAssignmentDelta();

        LOGGER.trace("Assignment delta {}", assignmentDelta.dump());

        // TODO: preprocess assignment delta. It is is replace, then we need to convert it to: delete all existing assignments, add all new assignments
        Collection<PropertyValue<AssignmentType>> changedAssignments = assignmentDelta.getValues(AssignmentType.class);

        AssignmentEvaluator assignmentEvaluator = new AssignmentEvaluator();
        assignmentEvaluator.setRepository(repositoryService);
        assignmentEvaluator.setUser(context.getUserNew());
        assignmentEvaluator.setObjectResolver(objectResolver);
        assignmentEvaluator.setSchemaRegistry(schemaRegistry);
        assignmentEvaluator.setValueConstructionFactory(valueConstructionFactory);

        Map<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>> zeroAccountMap = new HashMap<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>>();
        Map<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>> plusAccountMap = new HashMap<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>>();
        Map<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>> minusAccountMap = new HashMap<ResourceAccountType, Collection<PropertyValue<AccountConstruction>>>();

        LOGGER.trace("Old assignments {}", DebugUtil.prettyPrint(assignmentsOld));
        LOGGER.trace("Changed assignments {}", DebugUtil.prettyPrint(changedAssignments));

        ObjectType source = context.getUserTypeOld();
        if (source == null) {
            source = context.getUserNew().getOrParseObjectType();
        }

        Collection<AssignmentType> newAssignments = new HashSet<AssignmentType>();
        Collection<PropertyValue<AssignmentType>> allAssignments = MiscUtil.union(assignmentsOld, changedAssignments);
        for (PropertyValue<AssignmentType> propertyValue : allAssignments) {
            AssignmentType assignmentType = propertyValue.getValue();

            LOGGER.trace("Processing assignment {}", DebugUtil.prettyPrint(assignmentType));

            Assignment evaluatedAssignment = assignmentEvaluator.evaluate(assignmentType, source, result);

            if (assignmentsOld.contains(assignmentType)) {
                // TODO: remember old state
            }

            context.rememberResources(evaluatedAssignment.getResources(result));

            // Sort assignments to sets: unchanged (zero), added (plus), removed (minus)
            if (changedAssignments.contains(assignmentType)) {
                // There was some change

                if (assignmentDelta.isValueToAdd(assignmentType)) {
                    collectToAccountMap(plusAccountMap, evaluatedAssignment, result);
                }
                if (assignmentDelta.isValueToDelete(assignmentType)) {
                    collectToAccountMap(minusAccountMap, evaluatedAssignment, result);
                }

            } else {
                // No change in assignment
                collectToAccountMap(zeroAccountMap, evaluatedAssignment, result);
                newAssignments.add(assignmentType);
            }
        }

        if (LOGGER.isTraceEnabled()) {
            // Dump the maps
            LOGGER.trace("Account maps:\nZERO:\n{}\nPLUS:\n{}\nMINUS:\n{}\n", new Object[]{dumpAccountMap(zeroAccountMap),
                    dumpAccountMap(plusAccountMap), dumpAccountMap(minusAccountMap)});
        }

        Collection<ResourceAccountType> allAccountTypes = MiscUtil.union(zeroAccountMap.keySet(), plusAccountMap.keySet(), minusAccountMap.keySet());
        for (ResourceAccountType rat : allAccountTypes) {

            if (rat.getResourceOid() == null) {
                throw new IllegalStateException("Resource OID null in ResourceAccountType during outbound processing");
            }
            if (rat.getAccountType() == null) {
                throw new IllegalStateException("Account type is null in ResourceAccountType during outbound processing");
            }

            DeltaSetTriple<AccountConstruction> accountDeltaSetTriple = new DeltaSetTriple<AccountConstruction>(zeroAccountMap.get(rat),
                    plusAccountMap.get(rat), minusAccountMap.get(rat));

            Map<QName, DeltaSetTriple<ValueConstruction>> attributeValueDeltaMap = computeAttributeValueDeltaMap(accountDeltaSetTriple);
//			System.out.println("BBB1: "+accountDeltaSetTriple.dump());
//			System.out.println("BBB2: "+attributeValueDeltaMap);

            if (zeroAccountMap.containsKey(rat)) {
                context.getAccountSyncContext(rat).setAssigned(true);
                // The account existed before the change and should still exist
                processAccountKeep(context, rat, accountDeltaSetTriple, attributeValueDeltaMap, result);
                continue;

            } else if (plusAccountMap.containsKey(rat) && minusAccountMap.containsKey(rat)) {
                context.getAccountSyncContext(rat).setAssigned(true);
                // Account was removed and added in the same operation, therefore keep its original state
                // TODO
{'fraction_non_alphanumeric': 0.08802131365918639, 'fraction_numerical': 0.0017932165180858694, 'mean_word_length': 3.688686043718472, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3321561', 'n_tokens_mistral': 4892, 'n_tokens_neox': 4772, 'n_words': 1002}