<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.syncope.core.scheduling;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javassist.NotFoundException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationalAttributes;
import org.identityconnectors.framework.common.objects.SyncDelta;
import org.identityconnectors.framework.common.objects.SyncResultsHandler;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.syncope.client.mod.UserMod;
import org.syncope.client.search.AttributeCond;
import org.syncope.client.search.NodeCond;
import org.syncope.client.search.SyncopeUserCond;
import org.syncope.client.to.UserTO;
import org.syncope.core.init.ConnInstanceLoader;
import org.syncope.core.persistence.beans.Entitlement;
import org.syncope.core.persistence.beans.ExternalResource;
import org.syncope.core.persistence.beans.PropagationTask;
import org.syncope.core.persistence.beans.SchemaMapping;
import org.syncope.core.persistence.beans.SyncPolicy;
import org.syncope.core.persistence.beans.SyncTask;
import org.syncope.core.persistence.beans.TaskExec;
import org.syncope.core.persistence.beans.user.SyncopeUser;
import org.syncope.core.persistence.beans.user.UAttrValue;
import org.syncope.core.persistence.dao.EntitlementDAO;
import org.syncope.core.persistence.dao.ResourceDAO;
import org.syncope.core.persistence.dao.UserDAO;
import org.syncope.core.persistence.dao.UserSearchDAO;
import org.syncope.core.propagation.ConnectorFacadeProxy;
import org.syncope.core.propagation.PropagationException;
import org.syncope.core.propagation.PropagationManager;
import org.syncope.core.rest.controller.InvalidSearchConditionException;
import org.syncope.core.rest.controller.UnauthorizedRoleException;
import org.syncope.core.rest.data.UserDataBinder;
import org.syncope.core.scheduling.SyncResult.Operation;
import org.syncope.core.util.ConnObjectUtil;
import org.syncope.core.util.EntitlementUtil;
import org.syncope.core.util.SchemaMappingUtil;
import org.syncope.core.workflow.UserWorkflowAdapter;
import org.syncope.core.workflow.WorkflowResult;
import org.syncope.types.ConflictResolutionAction;
import org.syncope.types.SyncPolicySpec;
import org.syncope.types.TraceLevel;

/**
 * Job for executing synchronization tasks.
 *
 * @see org.syncope.core.scheduling.Job
 * @see SyncTask
 */
public class SyncJob extends AbstractTaskJob {

    /**
     * ConnInstance loader.
     */
    @Autowired
    private ConnInstanceLoader connInstanceLoader;

    /**
     * Resource DAO.
     */
    @Autowired
    private ResourceDAO resourceDAO;

    /**
     * User DAO.
     */
    @Autowired
    private UserDAO userDAO;

    /**
     * User DAO.
     */
    @Autowired
    private UserSearchDAO userSearchDAO;

    /**
     * Entitlement DAO.
     */
    @Autowired
    private EntitlementDAO entitlementDAO;

    /**
     * User workflow adapter.
     */
    @Autowired
    private UserWorkflowAdapter wfAdapter;

    /**
     * Propagation Manager.
     */
    @Autowired
    private PropagationManager propagationManager;

    /**
     * User data binder.
     */
    @Autowired
    private UserDataBinder userDataBinder;

    /**
     * SyncJob actions.
     */
    private SyncJobActions actions;

    /**
     * ConnectorObject util.
     */
    @Autowired
    private ConnObjectUtil connObjectUtil;

    public void setActions(final SyncJobActions actions) {
        this.actions = actions;
    }

    /**
     * Find users based on mapped uid value (or previous uid value, if updated).
     *
     * @param delta sync delta
     * @return list of matching users
     */
    private List<Long> findExistingUsers(final SyncDelta delta) {
        final SyncTask syncTask = (SyncTask) this.task;

        final String uid =
                delta.getPreviousUid() == null ? delta.getUid().getUidValue() : delta.getPreviousUid().getUidValue();

        // ---------------------------------
        // Get sync policy specification
        // ---------------------------------
        final SyncPolicy policy = syncTask.getResource().getSyncPolicy();
        final SyncPolicySpec policySpec = policy != null ? (SyncPolicySpec) policy.getSpecification() : null;
        // ---------------------------------

        final List<Long> result = new ArrayList<Long>();

        if (policySpec != null && !policySpec.getAlternativeSearchAttrs().isEmpty()) {

            // search external attribute name/value 
            // about each specified name
            final ConnectorObject object = delta.getObject();

            final Map<String, Attribute> extValues = new HashMap<String, Attribute>();

            for (SchemaMapping mapping : syncTask.getResource().getMappings()) {
                extValues.put(
                        SchemaMappingUtil.getIntAttrName(mapping),
                        object.getAttributeByName(SchemaMappingUtil.getExtAttrName(mapping)));
            }

            // search user by attributes specified into the policy
            NodeCond searchCondition = null;

            for (String schema : policySpec.getAlternativeSearchAttrs()) {
                Attribute value = extValues.get(schema);

                AttributeCond.Type type;
                String expression = null;

                if (value == null || value.getValue() == null || value.getValue().isEmpty()) {
                    type = AttributeCond.Type.ISNULL;
                } else {
                    type = AttributeCond.Type.EQ;
                    expression = value.getValue().size() > 1
                            ? value.getValue().toString() : value.getValue().get(0).toString();
                }

                NodeCond nodeCond;

                // just Username or SyncopeUserId can be selected to be used
                if ("id".equalsIgnoreCase(schema) || "username".equalsIgnoreCase(schema)) {

                    final SyncopeUserCond cond = new SyncopeUserCond();
                    cond.setSchema(schema);
                    cond.setType(type);
                    cond.setExpression(expression);

                    nodeCond = NodeCond.getLeafCond(cond);

                } else {
                    final AttributeCond cond = new AttributeCond();
                    cond.setSchema(schema);
                    cond.setType(type);
                    cond.setExpression(expression);

                    nodeCond = NodeCond.getLeafCond(cond);
                }

                searchCondition = searchCondition != null ? NodeCond.getAndCond(searchCondition, nodeCond) : nodeCond;
            }

            List<SyncopeUser> users =
                    userSearchDAO.search(EntitlementUtil.getRoleIds(entitlementDAO.findAll()), searchCondition);
            for (SyncopeUser user : users) {
                result.add(user.getId());
            }
        } else {
            final SyncopeUser found;
            List<SyncopeUser> users;

            final SchemaMapping accountIdMap = syncTask.getResource().getAccountIdMapping();

            switch (accountIdMap.getIntMappingType()) {
                case Username:
                    found = userDAO.find(uid);
                    if (found != null) {
                        result.add(found.getId());
                    }
                    break;

                case SyncopeUserId:
                    found = userDAO.find(Long.parseLong(uid));
                    if (found != null) {
                        result.add(found.getId());
                    }
                    break;

                case UserSchema:
                    final UAttrValue value = new UAttrValue();
                    value.setStringValue(uid);
                    users = userDAO.findByAttrValue(accountIdMap.getIntAttrName(), value);
                    for (SyncopeUser user : users) {
                        result.add(user.getId());
                    }
                    break;

                case UserDerivedSchema:
                    try {
                        users = userDAO.findByDerAttrValue(accountIdMap.getIntAttrName(), uid);
                        for (SyncopeUser user : users) {
                            result.add(user.getId());
                        }
                    } catch (InvalidSearchConditionException e) {
                        LOG.error("Could not search for matching users", e);
                    }
                    break;

                default:
                    LOG.error("Invalid accountId type '{}'", accountIdMap.getIntMappingType());
            }
        }

        return result;
    }

    private SyncResult createUser(SyncDelta delta, final boolean dryRun)
            throws JobExecutionException {

        final SyncResult result = new SyncResult();
        result.setOperation(Operation.CREATE);

        UserTO userTO = connObjectUtil.getUserTO(delta.getObject(), (SyncTask) task);

        delta = actions.beforeCreate(delta, userTO);

        if (dryRun) {
            result.setUserId(0L);
            result.setUsername(userTO.getUsername());
            result.setStatus(Status.SUCCESS);
        } else {
            try {
                Boolean enabled = null;

                // --------------------------
                // Check for status synchronization ...
                // --------------------------
                if (((SyncTask) this.task).isSyncStatus()) {
                    Attribute status =
                            AttributeUtil.find(OperationalAttributes.ENABLE_NAME, delta.getObject().getAttributes());

                    if (status != null) {
                        enabled = status != null
                                && status.getValue() != null
                                && !status.getValue().isEmpty() ? (Boolean) status.getValue().get(0) : null;
                    }
                }
                // --------------------------

                WorkflowResult<Map.Entry<Long, Boolean>> created = wfAdapter.create(userTO, true, enabled);

                List<PropagationTask> tasks = propagationManager.getCreateTaskIds(created, userTO.getPassword(), null,
                        Collections.singleton(((SyncTask) this.task).getResource().getName()));
                propagationManager.execute(tasks);

                userTO = userDataBinder.getUserTO(created.getResult().getKey());

                result.setUserId(created.getResult().getKey());
                result.setUsername(userTO.getUsername());
                result.setStatus(Status.SUCCESS);
            } catch (PropagationException e) {
                LOG.error("Could not propagate user " + delta.getUid().getUidValue(), e);
            } catch (Throwable t) {
                result.setStatus(Status.FAILURE);
                result.setMessage(t.getMessage());
                LOG.error("Could not create user " + delta.getUid().getUidValue(), t);
            }
        }

        delta = actions.after(delta, userTO, result);

        return result;
    }

    private void updateUsers(
            SyncDelta delta, final List<Long> users, final boolean dryRun, final List<SyncResult> results)
            throws JobExecutionException {

        if (!((SyncTask) task).isPerformUpdate()) {
            LOG.debug("SyncTask not configured for update");
            return;
        }

        LOG.debug("About to update {}", users);

        for (Long userId : users) {
            final SyncResult result = new SyncResult();
            result.setOperation(Operation.UPDATE);

            try {
                UserTO userTO = userDataBinder.getUserTO(userId);
                try {

                    final UserMod userMod = connObjectUtil.getUserMod(userId, delta.getObject(), (SyncTask) task);
                    delta = actions.beforeUpdate(delta, userTO, userMod);

                    result.setStatus(Status.SUCCESS);
                    result.setUserId(userMod.getId());
                    result.setUsername(userMod.getUsername());

                    if (!dryRun) {
                        WorkflowResult<Map.Entry<Long, Boolean>> updated = wfAdapter.update(userMod);

                        List<PropagationTask> tasks = propagationManager.getUpdateTaskIds(
                                updated, userMod.getPassword(), null, null,
                                Collections.singleton(((SyncTask) this.task).getResource().getName()));

                        propagationManager.execute(tasks);
                        userTO = userDataBinder.getUserTO(updated.getResult().getKey());
                    }
                } catch (PropagationException e) {
                    LOG.error("Could not propagate user " + delta.getUid().getUidValue(), e);
                } catch (Throwable t) {
                    result.setStatus(Status.FAILURE);
                    result.setMessage(t.getMessage());
                    LOG.error("Could not update user " + delta.getUid().getUidValue(), t);
                }

                delta = actions.after(delta, userTO, result);
                results.add(result);
            } catch (NotFoundException e) {
                LOG.error("Could not find user {}", userId, e);
            } catch (UnauthorizedRoleException e) {
                LOG.error("Not allowed to read user {}", userId, e);<commit_msg>SYNCOPE-34 #comment missed to pass virtual attributes as parameter to create propagation task<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.syncope.core.scheduling;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javassist.NotFoundException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationalAttributes;
import org.identityconnectors.framework.common.objects.SyncDelta;
import org.identityconnectors.framework.common.objects.SyncResultsHandler;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.syncope.client.mod.UserMod;
import org.syncope.client.search.AttributeCond;
import org.syncope.client.search.NodeCond;
import org.syncope.client.search.SyncopeUserCond;
import org.syncope.client.to.UserTO;
import org.syncope.core.init.ConnInstanceLoader;
import org.syncope.core.persistence.beans.Entitlement;
import org.syncope.core.persistence.beans.ExternalResource;
import org.syncope.core.persistence.beans.PropagationTask;
import org.syncope.core.persistence.beans.SchemaMapping;
import org.syncope.core.persistence.beans.SyncPolicy;
import org.syncope.core.persistence.beans.SyncTask;
import org.syncope.core.persistence.beans.TaskExec;
import org.syncope.core.persistence.beans.user.SyncopeUser;
import org.syncope.core.persistence.beans.user.UAttrValue;
import org.syncope.core.persistence.dao.EntitlementDAO;
import org.syncope.core.persistence.dao.ResourceDAO;
import org.syncope.core.persistence.dao.UserDAO;
import org.syncope.core.persistence.dao.UserSearchDAO;
import org.syncope.core.propagation.ConnectorFacadeProxy;
import org.syncope.core.propagation.PropagationException;
import org.syncope.core.propagation.PropagationManager;
import org.syncope.core.rest.controller.InvalidSearchConditionException;
import org.syncope.core.rest.controller.UnauthorizedRoleException;
import org.syncope.core.rest.data.UserDataBinder;
import org.syncope.core.scheduling.SyncResult.Operation;
import org.syncope.core.util.ConnObjectUtil;
import org.syncope.core.util.EntitlementUtil;
import org.syncope.core.util.SchemaMappingUtil;
import org.syncope.core.workflow.UserWorkflowAdapter;
import org.syncope.core.workflow.WorkflowResult;
import org.syncope.types.ConflictResolutionAction;
import org.syncope.types.SyncPolicySpec;
import org.syncope.types.TraceLevel;

/**
 * Job for executing synchronization tasks.
 *
 * @see org.syncope.core.scheduling.Job
 * @see SyncTask
 */
public class SyncJob extends AbstractTaskJob {

    /**
     * ConnInstance loader.
     */
    @Autowired
    private ConnInstanceLoader connInstanceLoader;

    /**
     * Resource DAO.
     */
    @Autowired
    private ResourceDAO resourceDAO;

    /**
     * User DAO.
     */
    @Autowired
    private UserDAO userDAO;

    /**
     * User DAO.
     */
    @Autowired
    private UserSearchDAO userSearchDAO;

    /**
     * Entitlement DAO.
     */
    @Autowired
    private EntitlementDAO entitlementDAO;

    /**
     * User workflow adapter.
     */
    @Autowired
    private UserWorkflowAdapter wfAdapter;

    /**
     * Propagation Manager.
     */
    @Autowired
    private PropagationManager propagationManager;

    /**
     * User data binder.
     */
    @Autowired
    private UserDataBinder userDataBinder;

    /**
     * SyncJob actions.
     */
    private SyncJobActions actions;

    /**
     * ConnectorObject util.
     */
    @Autowired
    private ConnObjectUtil connObjectUtil;

    public void setActions(final SyncJobActions actions) {
        this.actions = actions;
    }

    /**
     * Find users based on mapped uid value (or previous uid value, if updated).
     *
     * @param delta sync delta
     * @return list of matching users
     */
    private List<Long> findExistingUsers(final SyncDelta delta) {
        final SyncTask syncTask = (SyncTask) this.task;

        final String uid =
                delta.getPreviousUid() == null ? delta.getUid().getUidValue() : delta.getPreviousUid().getUidValue();

        // ---------------------------------
        // Get sync policy specification
        // ---------------------------------
        final SyncPolicy policy = syncTask.getResource().getSyncPolicy();
        final SyncPolicySpec policySpec = policy != null ? (SyncPolicySpec) policy.getSpecification() : null;
        // ---------------------------------

        final List<Long> result = new ArrayList<Long>();

        if (policySpec != null && !policySpec.getAlternativeSearchAttrs().isEmpty()) {

            // search external attribute name/value 
            // about each specified name
            final ConnectorObject object = delta.getObject();

            final Map<String, Attribute> extValues = new HashMap<String, Attribute>();

            for (SchemaMapping mapping : syncTask.getResource().getMappings()) {
                extValues.put(
                        SchemaMappingUtil.getIntAttrName(mapping),
                        object.getAttributeByName(SchemaMappingUtil.getExtAttrName(mapping)));
            }

            // search user by attributes specified into the policy
            NodeCond searchCondition = null;

            for (String schema : policySpec.getAlternativeSearchAttrs()) {
                Attribute value = extValues.get(schema);

                AttributeCond.Type type;
                String expression = null;

                if (value == null || value.getValue() == null || value.getValue().isEmpty()) {
                    type = AttributeCond.Type.ISNULL;
                } else {
                    type = AttributeCond.Type.EQ;
                    expression = value.getValue().size() > 1
                            ? value.getValue().toString() : value.getValue().get(0).toString();
                }

                NodeCond nodeCond;

                // just Username or SyncopeUserId can be selected to be used
                if ("id".equalsIgnoreCase(schema) || "username".equalsIgnoreCase(schema)) {

                    final SyncopeUserCond cond = new SyncopeUserCond();
                    cond.setSchema(schema);
                    cond.setType(type);
                    cond.setExpression(expression);

                    nodeCond = NodeCond.getLeafCond(cond);

                } else {
                    final AttributeCond cond = new AttributeCond();
                    cond.setSchema(schema);
                    cond.setType(type);
                    cond.setExpression(expression);

                    nodeCond = NodeCond.getLeafCond(cond);
                }

                searchCondition = searchCondition != null ? NodeCond.getAndCond(searchCondition, nodeCond) : nodeCond;
            }

            List<SyncopeUser> users =
                    userSearchDAO.search(EntitlementUtil.getRoleIds(entitlementDAO.findAll()), searchCondition);
            for (SyncopeUser user : users) {
                result.add(user.getId());
            }
        } else {
            final SyncopeUser found;
            List<SyncopeUser> users;

            final SchemaMapping accountIdMap = syncTask.getResource().getAccountIdMapping();

            switch (accountIdMap.getIntMappingType()) {
                case Username:
                    found = userDAO.find(uid);
                    if (found != null) {
                        result.add(found.getId());
                    }
                    break;

                case SyncopeUserId:
                    found = userDAO.find(Long.parseLong(uid));
                    if (found != null) {
                        result.add(found.getId());
                    }
                    break;

                case UserSchema:
                    final UAttrValue value = new UAttrValue();
                    value.setStringValue(uid);
                    users = userDAO.findByAttrValue(accountIdMap.getIntAttrName(), value);
                    for (SyncopeUser user : users) {
                        result.add(user.getId());
                    }
                    break;

                case UserDerivedSchema:
                    try {
                        users = userDAO.findByDerAttrValue(accountIdMap.getIntAttrName(), uid);
                        for (SyncopeUser user : users) {
                            result.add(user.getId());
                        }
                    } catch (InvalidSearchConditionException e) {
                        LOG.error("Could not search for matching users", e);
                    }
                    break;

                default:
                    LOG.error("Invalid accountId type '{}'", accountIdMap.getIntMappingType());
            }
        }

        return result;
    }

    private SyncResult createUser(SyncDelta delta, final boolean dryRun)
            throws JobExecutionException {

        final SyncResult result = new SyncResult();
        result.setOperation(Operation.CREATE);

        UserTO userTO = connObjectUtil.getUserTO(delta.getObject(), (SyncTask) task);

        delta = actions.beforeCreate(delta, userTO);

        if (dryRun) {
            result.setUserId(0L);
            result.setUsername(userTO.getUsername());
            result.setStatus(Status.SUCCESS);
        } else {
            try {
                Boolean enabled = null;

                // --------------------------
                // Check for status synchronization ...
                // --------------------------
                if (((SyncTask) this.task).isSyncStatus()) {
                    Attribute status =
                            AttributeUtil.find(OperationalAttributes.ENABLE_NAME, delta.getObject().getAttributes());

                    if (status != null) {
                        enabled = status != null
                                && status.getValue() != null
                                && !status.getValue().isEmpty() ? (Boolean) status.getValue().get(0) : null;
                    }
                }
                // --------------------------

                WorkflowResult<Map.Entry<Long, Boolean>> created = wfAdapter.create(userTO, true, enabled);

                List<PropagationTask> tasks = propagationManager.getCreateTaskIds(
                        created,
                        userTO.getPassword(),
                        userTO.getVirtualAttributes(),
                        Collections.singleton(((SyncTask) this.task).getResource().getName()));

                propagationManager.execute(tasks);

                userTO = userDataBinder.getUserTO(created.getResult().getKey());

                result.setUserId(created.getResult().getKey());
                result.setUsername(userTO.getUsername());
                result.setStatus(Status.SUCCESS);
            } catch (PropagationException e) {
                LOG.error("Could not propagate user " + delta.getUid().getUidValue(), e);
            } catch (Throwable t) {
                result.setStatus(Status.FAILURE);
                result.setMessage(t.getMessage());
                LOG.error("Could not create user " + delta.getUid().getUidValue(), t);
            }
        }

        delta = actions.after(delta, userTO, result);

        return result;
    }

    private void updateUsers(
            SyncDelta delta, final List<Long> users, final boolean dryRun, final List<SyncResult> results)
            throws JobExecutionException {

        if (!((SyncTask) task).isPerformUpdate()) {
            LOG.debug("SyncTask not configured for update");
            return;
        }

        LOG.debug("About to update {}", users);

        for (Long userId : users) {
            final SyncResult result = new SyncResult();
            result.setOperation(Operation.UPDATE);

            try {
                UserTO userTO = userDataBinder.getUserTO(userId);
                try {

                    final UserMod userMod = connObjectUtil.getUserMod(userId, delta.getObject(), (SyncTask) task);
                    delta = actions.beforeUpdate(delta, userTO, userMod);

                    result.setStatus(Status.SUCCESS);
                    result.setUserId(userMod.getId());
                    result.setUsername(userMod.getUsername());

                    if (!dryRun) {
                        WorkflowResult<Map.Entry<Long, Boolean>> updated = wfAdapter.update(userMod);

                        List<PropagationTask> tasks = propagationManager.getUpdateTaskIds(
                                updated,
                                userMod.getPassword(),
                                userMod.getVirtualAttributesToBeRemoved(),
                                userMod.getVirtualAttributesToBeUpdated(),
                                Collections.singleton(((SyncTask) this.task).getResource().getName()));

                        propagationManager.execute(tasks);
                        userTO = userDataBinder.getUserTO(updated.getResult().getKey());
                    }
                } catch (PropagationException e) {
                    LOG.error("Could not propagate user " + delta.getUid().getUidValue(), e);
                } catch (Throwable t) {
                    result.setStatus(Status.FAILURE);
                    result.setMessage(t.getMessage());
                    LOG.error("Could not update user " + delta.getUid().getUidValue(), t);
                }

                delta = actions.after(delta, userTO, result);
                results.add(result);
            } catch (NotFoundException e) {
                LOG.error("Could not find user {}", userId, e);
            } catch (UnauthorizedRoleException e) {
                LOG.error("Not allowed to read user {}", userId, e);
{'fraction_non_alphanumeric': 0.10675554671176997, 'fraction_numerical': 0.0005969555268132525, 'mean_word_length': 1.9893922871022107, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5524999', 'n_tokens_mistral': 7617, 'n_tokens_neox': 7352, 'n_words': 1580}