This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 49e013436f [SYNCOPE-1705] Encapsulating each object processing into an 
inner transaction (#383)
49e013436f is described below

commit 49e013436f13856093548672105d79e5d6edb9a7
Author: Francesco Chicchiriccò <ilgro...@users.noreply.github.com>
AuthorDate: Thu Oct 27 12:51:49 2022 +0200

    [SYNCOPE-1705] Encapsulating each object processing into an inner 
transaction (#383)
---
 .../persistence/api/dao/ExternalResourceDAO.java   |   4 +-
 .../core/persistence/api/dao/RemediationDAO.java   |   1 -
 .../api/pushpull/ProvisioningActions.java          |   4 +-
 .../provisioning/java/job/SetUMembershipsJob.java  | 129 ---------------------
 .../propagation/DefaultPropagationReporter.java    |   4 +-
 .../java/pushpull/AbstractPullResultHandler.java   |  64 ++--------
 .../pushpull/DefaultUserPullResultHandler.java     |   6 +-
 .../java/pushpull/LDAPMembershipPullActions.java   |  81 ++++++++++---
 .../java/pushpull/SchedulingPullActions.java       |   2 +
 .../pushpull/LDAPMembershipPullActionsTest.java    |  46 ++------
 .../apache/syncope/fit/core/PullTaskITCase.java    |   4 +-
 11 files changed, 99 insertions(+), 246 deletions(-)

diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ExternalResourceDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ExternalResourceDAO.java
index 8fc338e8c3..bc3ec6e880 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ExternalResourceDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/ExternalResourceDAO.java
@@ -37,9 +37,9 @@ public interface ExternalResourceDAO extends 
DAO<ExternalResource> {
 
     boolean anyItemHaving(Implementation transformer);
 
-    List<ExternalResource> findByProvisionSorter(Implementation 
propagationActions);
+    List<ExternalResource> findByProvisionSorter(Implementation 
provisionSorter);
 
-    List<ExternalResource> findByPropagationActions(Implementation 
provisionSorter);
+    List<ExternalResource> findByPropagationActions(Implementation 
propagationActions);
 
     List<ExternalResource> findByPolicy(Policy policy);
 
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java
index 73216b30cf..5fd3d49737 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java
@@ -41,5 +41,4 @@ public interface RemediationDAO extends DAO<Remediation> {
     void delete(Remediation remediation);
 
     void delete(String key);
-
 }
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningActions.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningActions.java
index ff3a3e4df1..afbf49aac2 100644
--- 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningActions.java
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningActions.java
@@ -28,7 +28,7 @@ public interface ProvisioningActions {
      * @param profile provisioning profile
      * @throws JobExecutionException in case of generic failure
      */
-    default void beforeAll(final ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
+    default void beforeAll(ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
         // do nothing
     }
 
@@ -38,7 +38,7 @@ public interface ProvisioningActions {
      * @param profile provisioning profile
      * @throws JobExecutionException in case of generic failure
      */
-    default void afterAll(final ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
+    default void afterAll(ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
         // do nothing        
     }
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
deleted file mode 100644
index a9d9a52538..0000000000
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.apache.syncope.core.provisioning.java.job;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import org.apache.syncope.common.lib.request.MembershipUR;
-import org.apache.syncope.common.lib.request.UserUR;
-import org.apache.syncope.common.lib.types.PatchOperation;
-import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
-import org.apache.syncope.core.provisioning.api.job.JobManager;
-import org.apache.syncope.core.spring.ApplicationContextProvider;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
-import org.apache.syncope.core.spring.security.SecurityProperties;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobExecutionException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-
-/**
- * Quartz Job used for setting user memberships asynchronously, after the 
completion of
- * {@link org.apache.syncope.core.provisioning.api.pushpull.PullActions}.
- */
-public class SetUMembershipsJob extends AbstractInterruptableJob {
-
-    private static final Logger LOG = 
LoggerFactory.getLogger(SetUMembershipsJob.class);
-
-    public static final String MEMBERSHIPS_BEFORE_KEY = "membershipsBefore";
-
-    public static final String MEMBERSHIPS_AFTER_KEY = "membershipsAfter";
-
-    public static final String CONTEXT = "context";
-
-    @Autowired
-    private SecurityProperties securityProperties;
-
-    @Autowired
-    private UserProvisioningManager userProvisioningManager;
-
-    @Override
-    public void execute(final JobExecutionContext context) throws 
JobExecutionException {
-        String executor = 
Optional.ofNullable(context.getMergedJobDataMap().getString(JobManager.EXECUTOR_KEY)).
-                orElse(securityProperties.getAdminUser());
-
-        try {
-            
AuthContextUtils.callAsAdmin(context.getMergedJobDataMap().getString(JobManager.DOMAIN_KEY),
 () -> {
-
-                @SuppressWarnings("unchecked")
-                Map<String, Set<String>> membershipsBefore =
-                        (Map<String, Set<String>>) 
context.getMergedJobDataMap().get(MEMBERSHIPS_BEFORE_KEY);
-                LOG.debug("Memberships before pull (User -> Groups) {}", 
membershipsBefore);
-
-                @SuppressWarnings("unchecked")
-                Map<String, Set<String>> membershipsAfter =
-                        (Map<String, Set<String>>) 
context.getMergedJobDataMap().get(MEMBERSHIPS_AFTER_KEY);
-                LOG.debug("Memberships after pull (User -> Groups) {}", 
membershipsAfter);
-
-                List<UserUR> updateReqs = new ArrayList<>();
-
-                membershipsAfter.forEach((user, groups) -> {
-                    UserUR userUR = new UserUR();
-                    userUR.setKey(user);
-                    updateReqs.add(userUR);
-
-                    groups.forEach(group -> {
-                        Set<String> before = membershipsBefore.get(user);
-                        if (before == null || !before.contains(group)) {
-                            userUR.getMemberships().add(new 
MembershipUR.Builder(group).
-                                    operation(PatchOperation.ADD_REPLACE).
-                                    build());
-                        }
-                    });
-                });
-
-                membershipsBefore.forEach((user, groups) -> {
-                    UserUR userUR = updateReqs.stream().
-                            filter(req -> 
user.equals(req.getKey())).findFirst().
-                            orElseGet(() -> {
-                                UserUR req = new UserUR.Builder(user).build();
-                                updateReqs.add(req);
-                                return req;
-                            });
-
-                    groups.forEach(group -> {
-                        Set<String> after = membershipsAfter.get(user);
-                        if (after == null || !after.contains(group)) {
-                            userUR.getMemberships().add(new 
MembershipUR.Builder(group).
-                                    operation(PatchOperation.DELETE).
-                                    build());
-                        }
-                    });
-                });
-
-                updateReqs.stream().filter(req -> !req.isEmpty()).forEach(req 
-> {
-                    LOG.debug("About to update User {}", req);
-                    userProvisioningManager.update(
-                            req, true, executor, 
context.getMergedJobDataMap().getString(CONTEXT));
-                });
-
-                return null;
-            });
-        } catch (RuntimeException e) {
-            LOG.error("While setting memberships", e);
-            throw new JobExecutionException("While executing memberships", e);
-        } finally {
-            
ApplicationContextProvider.getBeanFactory().destroySingleton(context.getJobDetail().getKey().getName());
-        }
-    }
-}
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
index 13037ca4e6..12225b472f 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
@@ -18,10 +18,10 @@
  */
 package org.apache.syncope.core.provisioning.java.propagation;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
@@ -36,7 +36,7 @@ public class DefaultPropagationReporter implements 
PropagationReporter {
 
     protected static final Logger LOG = 
LoggerFactory.getLogger(DefaultPropagationReporter.class);
 
-    protected final List<PropagationStatus> statuses = new ArrayList<>();
+    protected final List<PropagationStatus> statuses = new 
CopyOnWriteArrayList<>();
 
     protected boolean add(final PropagationStatus status) {
         return statuses.stream().anyMatch(item -> 
item.getResource().equals(status.getResource()))
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
index 440d0cfb18..29c7dc094a 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
@@ -70,9 +70,9 @@ import 
org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.SyncDelta;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
-@Transactional(rollbackFor = Throwable.class)
 public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHandler<PullTask, PullActions>
         implements SyncopePullResultHandler {
 
@@ -128,6 +128,7 @@ public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHan
         this.executor = executor;
     }
 
+    @Transactional(rollbackFor = Throwable.class, propagation = 
Propagation.REQUIRES_NEW)
     @Override
     public boolean handle(final SyncDelta delta) {
         Provision provision = null;
@@ -265,15 +266,7 @@ public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHan
                 if (profile.getTask().isRemediation()) {
                     // set to SUCCESS to let the incremental flow go on in 
case of errors
                     resultStatus = Result.SUCCESS;
-                    createRemediation(
-                            provision.getAnyType(),
-                            null,
-                            anyCR,
-                            null,
-                            taskDAO.exists(TaskType.PULL, 
profile.getTask().getKey())
-                            ? profile.getTask() : null,
-                            result,
-                            delta);
+                    createRemediation(provision.getAnyType(), null, anyCR, 
null, result, delta);
                 } else {
                     resultStatus = Result.FAILURE;
                 }
@@ -391,13 +384,7 @@ public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHan
                         if (profile.getTask().isRemediation()) {
                             // set to SUCCESS to let the incremental flow go 
on in case of errors
                             resultStatus = Result.SUCCESS;
-                            createRemediation(
-                                    provision.getAnyType(),
-                                    anyUR,
-                                    taskDAO.exists(TaskType.PULL, 
profile.getTask().getKey())
-                                    ? profile.getTask() : null,
-                                    result,
-                                    delta);
+                            createRemediation(provision.getAnyType(), null, 
null, anyUR, result, delta);
                         } else {
                             resultStatus = Result.FAILURE;
                         }
@@ -695,14 +682,7 @@ public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHan
                             // set to SUCCESS to let the incremental flow go 
on in case of errors
                             resultStatus = Result.SUCCESS;
                             createRemediation(
-                                    provision.getAnyType(),
-                                    match.getAny().getKey(),
-                                    null,
-                                    null,
-                                    taskDAO.exists(TaskType.PULL, 
profile.getTask().getKey())
-                                    ? profile.getTask() : null,
-                                    result,
-                                    delta);
+                                    provision.getAnyType(), 
match.getAny().getKey(), null, null, result, delta);
                         }
                     }
 
@@ -989,43 +969,15 @@ public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHan
             final ProvisioningReport result) {
 
         if (ProvisioningReport.Status.FAILURE == result.getStatus() && 
profile.getTask().isRemediation()) {
-            createRemediation(
-                    result.getAnyType(),
-                    null,
-                    null,
-                    anyUR,
-                    taskDAO.exists(TaskType.PULL, profile.getTask().getKey()) 
? profile.getTask() : null,
-                    result,
-                    delta);
+            createRemediation(result.getAnyType(), null, null, anyUR, result, 
delta);
         }
     }
 
-    protected void createRemediation(
-            final String anyType,
-            final AnyCR anyCR,
-            final PullTask pullTask,
-            final ProvisioningReport result,
-            final SyncDelta delta) {
-
-        createRemediation(anyType, null, anyCR, null, pullTask, result, delta);
-    }
-
-    protected void createRemediation(
-            final String anyType,
-            final AnyUR anyUR,
-            final PullTask pullTask,
-            final ProvisioningReport result,
-            final SyncDelta delta) {
-
-        createRemediation(anyType, null, null, anyUR, pullTask, result, delta);
-    }
-
     protected void createRemediation(
             final String anyType,
             final String anyKey,
             final AnyCR anyCR,
             final AnyUR anyUR,
-            final PullTask pullTask,
             final ProvisioningReport result,
             final SyncDelta delta) {
 
@@ -1043,7 +995,9 @@ public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHan
         remediation.setError(result.getMessage());
         remediation.setInstant(OffsetDateTime.now());
         remediation.setRemoteName(delta.getObject().getName().getNameValue());
-        remediation.setPullTask(pullTask);
+        if (taskDAO.exists(TaskType.PULL, profile.getTask().getKey())) {
+            remediation.setPullTask((PullTask) taskDAO.find(TaskType.PULL, 
profile.getTask().getKey()));
+        }
 
         remediation = remediationDAO.save(remediation);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
index 9232e4b011..08164b5d91 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
@@ -422,7 +422,7 @@ public class DefaultUserPullResultHandler extends 
AbstractPullResultHandler impl
                 resultStatus = Result.FAILURE;
 
                 if (profile.getTask().isRemediation()) {
-                    createRemediation(provision.getAnyType(), req, 
profile.getTask(), report, delta);
+                    createRemediation(provision.getAnyType(), null, null, req, 
report, delta);
                 }
             }
 
@@ -541,7 +541,7 @@ public class DefaultUserPullResultHandler extends 
AbstractPullResultHandler impl
                 resultStatus = Result.FAILURE;
 
                 if (profile.getTask().isRemediation()) {
-                    createRemediation(provision.getAnyType(), userUR, 
profile.getTask(), report, delta);
+                    createRemediation(provision.getAnyType(), null, null, 
userUR, report, delta);
                 }
             }
 
@@ -617,7 +617,7 @@ public class DefaultUserPullResultHandler extends 
AbstractPullResultHandler impl
                     output = e;
 
                     if (profile.getTask().isRemediation()) {
-                        createRemediation(provision.getAnyType(), req, 
profile.getTask(), report, delta);
+                        createRemediation(provision.getAnyType(), null, null, 
req, report, delta);
                     }
                 }
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
index 3bb5a9cc86..0dad00af24 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -25,18 +26,23 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import org.apache.syncope.common.lib.request.AnyUR;
+import org.apache.syncope.common.lib.request.MembershipUR;
+import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.PullMatch;
 import org.apache.syncope.core.provisioning.api.Connector;
-import org.apache.syncope.core.provisioning.api.job.JobManager;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
-import org.apache.syncope.core.provisioning.java.job.SetUMembershipsJob;
+import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.ObjectClass;
@@ -46,6 +52,7 @@ import org.quartz.JobExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -54,7 +61,8 @@ import 
org.springframework.transaction.annotation.Transactional;
  *
  * @see 
org.apache.syncope.core.provisioning.java.propagation.LDAPMembershipPropagationActions
  */
-public class LDAPMembershipPullActions extends SchedulingPullActions {
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
+public class LDAPMembershipPullActions implements PullActions {
 
     protected static final Logger LOG = 
LoggerFactory.getLogger(LDAPMembershipPullActions.class);
 
@@ -65,7 +73,10 @@ public class LDAPMembershipPullActions extends 
SchedulingPullActions {
     protected GroupDAO groupDAO;
 
     @Autowired
-    private InboundMatcher inboundMatcher;
+    protected InboundMatcher inboundMatcher;
+
+    @Autowired
+    protected UserProvisioningManager userProvisioningManager;
 
     protected final Map<String, Set<String>> membershipsBefore = new 
HashMap<>();
 
@@ -137,7 +148,8 @@ public class LDAPMembershipPullActions extends 
SchedulingPullActions {
             final AnyUR anyUR) throws JobExecutionException {
 
         if (!(entity instanceof GroupTO)) {
-            super.beforeUpdate(profile, delta, entity, anyUR);
+            PullActions.super.beforeUpdate(profile, delta, entity, anyUR);
+            return;
         }
 
         
groupDAO.findUMemberships(groupDAO.find(entity.getKey())).forEach(uMembership 
-> {
@@ -162,13 +174,15 @@ public class LDAPMembershipPullActions extends 
SchedulingPullActions {
             final ProvisioningReport result) throws JobExecutionException {
 
         if (!(entity instanceof GroupTO)) {
-            super.after(profile, delta, entity, result);
+            PullActions.super.after(profile, delta, entity, result);
+            return;
         }
 
         Optional<Provision> provision = profile.getTask().getResource().
                 getProvisionByAnyType(AnyTypeKind.USER.name()).filter(p -> 
p.getMapping() != null);
         if (provision.isEmpty()) {
-            super.after(profile, delta, entity, result);
+            PullActions.super.after(profile, delta, entity, result);
+            return;
         }
 
         getMembAttrValues(delta, profile.getConnector()).forEach(membValue -> {
@@ -190,15 +204,52 @@ public class LDAPMembershipPullActions extends 
SchedulingPullActions {
         });
     }
 
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
     @Override
     public void afterAll(final ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
-        Map<String, Object> jobMap = new HashMap<>();
-        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_BEFORE_KEY, 
membershipsBefore);
-        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_AFTER_KEY, membershipsAfter);
-        jobMap.put(JobManager.EXECUTOR_KEY, profile.getExecutor());
-        jobMap.put(
-                SetUMembershipsJob.CONTEXT,
-                "PullTask " + profile.getTask().getKey() + " '" + 
profile.getTask().getName() + "'");
-        schedule(SetUMembershipsJob.class, jobMap);
+        List<UserUR> updateReqs = new ArrayList<>();
+
+        membershipsAfter.forEach((user, groups) -> {
+            UserUR userUR = new UserUR();
+            userUR.setKey(user);
+            updateReqs.add(userUR);
+
+            groups.stream().forEach(group -> {
+                Set<String> before = membershipsBefore.get(user);
+                if (before == null || !before.contains(group)) {
+                    userUR.getMemberships().add(new 
MembershipUR.Builder(group).
+                            operation(PatchOperation.ADD_REPLACE).
+                            build());
+                }
+            });
+        });
+
+        membershipsBefore.forEach((user, groups) -> {
+            UserUR userUR = updateReqs.stream().
+                    filter(req -> user.equals(req.getKey())).findFirst().
+                    orElseGet(() -> {
+                        UserUR req = new UserUR.Builder(user).build();
+                        updateReqs.add(req);
+                        return req;
+                    });
+
+            groups.forEach(group -> {
+                Set<String> after = membershipsAfter.get(user);
+                if (after == null || !after.contains(group)) {
+                    userUR.getMemberships().add(new 
MembershipUR.Builder(group).
+                            operation(PatchOperation.DELETE).
+                            build());
+                }
+            });
+        });
+
+        membershipsAfter.clear();
+        membershipsBefore.clear();
+
+        String context = "PullTask " + profile.getTask().getKey() + " '" + 
profile.getTask().getName() + "'";
+        updateReqs.stream().filter(req -> !req.isEmpty()).forEach(req -> {
+            LOG.debug("About to update memberships for User {}", req.getKey());
+            userProvisioningManager.update(req, true, profile.getExecutor(), 
context);
+        });
     }
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SchedulingPullActions.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SchedulingPullActions.java
index cdf4483c45..c4fdbb0863 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SchedulingPullActions.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SchedulingPullActions.java
@@ -41,6 +41,8 @@ import 
org.springframework.scheduling.quartz.SchedulerFactoryBean;
  * Superclass for pull actions that need to schedule actions to run after 
their completion.
  *
  * @see LDAPMembershipPullActions for a concrete example
+ * @deprecated From 3.0.0-M2 this class is not needed anymore and will be 
removed from 3.0.0 onwards.
+ * After SYNCOPE-1705 there is no need anymore to schedule a job to run after 
the current pull task execution
  */
 public abstract class SchedulingPullActions implements PullActions {
 
diff --git 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
index a4d6ce0030..0d3a976d6a 100644
--- 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
+++ 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
@@ -39,6 +39,7 @@ import org.apache.syncope.common.lib.request.AnyUR;
 import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.Mapping;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -68,10 +69,7 @@ import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.quartz.JobExecutionException;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 import org.springframework.test.util.ReflectionTestUtils;
 
 public class LDAPMembershipPullActionsTest extends AbstractTest {
@@ -104,10 +102,13 @@ public class LDAPMembershipPullActionsTest extends 
AbstractTest {
     private Map<String, Set<String>> membershipsAfter;
 
     @Mock
-    private ProvisioningTask<?> provisioningTask;
+    private ProvisioningTask<?> pullTask;
 
     @Mock
-    private ExternalResource externalResource;
+    private ExternalResource resource;
+
+    @Mock
+    private Provision provision;
 
     @Mock
     private Connector connector;
@@ -150,8 +151,10 @@ public class LDAPMembershipPullActionsTest extends 
AbstractTest {
         connConfProperties = new HashSet<>();
         connConfProperties.add(connConfProperty);
 
-        lenient().when(profile.getTask()).thenAnswer(ic -> provisioningTask);
-        
lenient().when(provisioningTask.getResource()).thenReturn(externalResource);
+        lenient().when(profile.getTask()).thenAnswer(ic -> pullTask);
+        lenient().when(pullTask.getResource()).thenReturn(resource);
+        
lenient().when(resource.getProvisionByAnyType(anyString())).thenReturn(Optional.of(provision));
+        lenient().when(provision.getMapping()).thenReturn(new Mapping());
         lenient().when(anyTypeDAO.findUser()).thenAnswer(ic -> {
             AnyType userAnyType = mock(AnyType.class);
             
lenient().when(userAnyType.getKey()).thenReturn(AnyTypeKind.USER.name());
@@ -192,27 +195,14 @@ public class LDAPMembershipPullActionsTest extends 
AbstractTest {
         assertEquals(1, membershipsBefore.get(user.getKey()).size());
     }
 
-    @Test
-    public void afterWithEmptyAttributes(final @Mock Attribute attribute) 
throws JobExecutionException {
-        entity = new GroupTO();
-
-        
when(connectorObj.getAttributeByName(anyString())).thenReturn(attribute);
-        
when(externalResource.getProvisionByAnyType(anyString())).thenAnswer(ic -> 
Optional.of(mock(Provision.class)));
-
-        ldapMembershipPullActions.after(profile, syncDelta, entity, result);
-
-        assertEquals(List.of(), attribute.getValue());
-    }
-
     @Test
     public void after() throws JobExecutionException {
-        entity = new UserTO();
+        entity = new GroupTO();
         String expectedUid = UUID.randomUUID().toString();
         Attribute attribute = new Uid(expectedUid);
         List<String> expected = List.of(expectedUid);
 
         
when(connectorObj.getAttributeByName(anyString())).thenReturn(attribute);
-        
when(externalResource.getProvisionByAnyType(anyString())).thenAnswer(ic -> 
Optional.empty());
         when(inboundMatcher.match(any(AnyType.class), anyString(), 
any(ExternalResource.class), any(Connector.class))).
                 thenReturn(Optional.of(new PullMatch(MatchType.ANY, user)));
 
@@ -222,18 +212,4 @@ public class LDAPMembershipPullActionsTest extends 
AbstractTest {
         verify(membershipsAfter).put(anyString(), any());
         assertEquals(expected, attribute.getValue());
     }
-
-    @Test
-    public void afterAll(
-            final @Mock Map<String, Object> jobMap,
-            final @Mock SchedulerFactoryBean schedulerFactoryBean,
-            final @Mock Scheduler scheduler) throws JobExecutionException, 
SchedulerException {
-
-        ReflectionTestUtils.setField(ldapMembershipPullActions, "scheduler", 
schedulerFactoryBean);
-        when(schedulerFactoryBean.getScheduler()).thenReturn(scheduler);
-
-        ldapMembershipPullActions.afterAll(profile);
-
-        verify(scheduler).scheduleJob(any(), any());
-    }
 }
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index 1301d14e25..b6c1571008 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -436,7 +436,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
         assertEquals("odd", 
userConnObject.getAttr("title").get().getValues().get(0));
         Attr userDn = userConnObject.getAttr(Name.NAME).get();
         updateLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD,
-                userDn.getValues().get(0), Collections.singletonMap("title", 
(String) null));
+                userDn.getValues().get(0), Collections.singletonMap("title", 
null));
 
         // SYNCOPE-317
         execProvisioningTask(
@@ -1083,7 +1083,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
     @Test
     public void issueSYNCOPE307() {
         
assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform()));
-        
+
         UserCR userCR = UserITCase.getUniqueSample("s...@apache.org");
         userCR.setUsername("test0");
         userCR.getPlainAttrs().removeIf(attr -> 
"firstname".equals(attr.getSchema()));


Reply via email to