You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/02/14 09:33:40 UTC

[syncope] branch master updated: [SYNCOPE-1535] Provision Sorter Implementation (#166)

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 bdd1023  [SYNCOPE-1535] Provision Sorter Implementation (#166)
bdd1023 is described below

commit bdd102378669407fca7885e0b0e39c1bf3b7205e
Author: DimaAy <di...@tirasa.net>
AuthorDate: Fri Feb 14 09:32:26 2020 +0100

    [SYNCOPE-1535] Provision Sorter Implementation (#166)
    
    * [SYNCOPE-1535] Provision Sorter Implementation
    
    * [SYNCOPE-1535] resolve sort
    
    * solve checkStyle
---
 .../commons/IdMImplementationInfoProvider.java     |   8 +-
 .../wizards/resources/ResourceDetailsPanel.java    |  16 +++
 .../wizards/resources/ResourceDetailsPanel.html    |   4 +
 .../resources/ResourceDetailsPanel.properties      |   1 +
 .../ResourceDetailsPanel_fr_CA.properties          |   9 +-
 .../resources/ResourceDetailsPanel_it.properties   |   1 +
 .../resources/ResourceDetailsPanel_ja.properties   |   1 +
 .../ResourceDetailsPanel_pt_BR.properties          |   1 +
 .../resources/ResourceDetailsPanel_ru.properties   |   1 +
 .../implementations/MyProvisionSorter.groovy       |  33 +++++
 .../apache/syncope/common/lib/to/ResourceTO.java   |  10 ++
 .../common/lib/types/IdMImplementationType.java    |   5 +-
 .../init/ClassPathScanImplementationLookup.java    |   5 +
 .../api/entity/resource/ExternalResource.java      |   4 +
 .../jpa/entity/resource/JPAExternalResource.java   |  15 ++
 .../core/provisioning/api/ProvisionSorter.java     |  26 ++++
 .../provisioning/java/DefaultProvisionSorter.java  |  45 ++++++
 .../java/data/ImplementationDataBinderImpl.java    |   5 +
 .../java/data/ResourceDataBinderImpl.java          |  15 ++
 .../java/pushpull/PullJobDelegate.java             | 155 +++++++++++----------
 .../fit/core/reference/ITImplementationLookup.java |   8 ++
 .../view/ResourceExplorerTopComponent.java         |  10 +-
 22 files changed, 296 insertions(+), 82 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
index 1ceacaf..174c813 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
@@ -68,7 +68,7 @@ public class IdMImplementationInfoProvider extends IdRepoImplementationInfoProvi
 
     @Override
     public String getGroovyTemplateClassName(final String implementationType) {
-        String templateClassName = null;
+        String templateClassName;
 
         switch (implementationType) {
             case IdMImplementationType.ITEM_TRANSFORMER:
@@ -99,6 +99,10 @@ public class IdMImplementationInfoProvider extends IdRepoImplementationInfoProvi
                 templateClassName = "MyPushCorrelationRule";
                 break;
 
+            case IdMImplementationType.PROVISION_SORTER:
+                templateClassName = "MyProvisionSorter";                
+                break;
+                
             default:
                 templateClassName = super.getGroovyTemplateClassName(implementationType);
         }
@@ -108,7 +112,7 @@ public class IdMImplementationInfoProvider extends IdRepoImplementationInfoProvi
 
     @Override
     public Class<?> getClass(final String implementationType, final String name) {
-        Class<?> clazz = null;
+        Class<?> clazz;
         switch (implementationType) {
             case IdMImplementationType.PULL_CORRELATION_RULE:
                 clazz = lookup.getPullCorrelationRuleConfs().get(name);
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
index f326f76..2023c99 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
@@ -56,6 +56,17 @@ public class ResourceDetailsPanel extends WizardStep {
         }
     };
 
+    private final IModel<List<String>> provisionSorters = new LoadableDetachableModel<List<String>>() {
+
+        private static final long serialVersionUID = 4659376149825914247L;
+
+        @Override
+        protected List<String> load() {
+            return ImplementationRestClient.list(IdMImplementationType.PROVISION_SORTER).stream().
+                    map(EntityTO::getKey).sorted().collect(Collectors.toList());
+        }
+    };
+
     public ResourceDetailsPanel(final ResourceTO resourceTO, final boolean createFlag) {
         super();
         setOutputMarkupId(true);
@@ -103,6 +114,11 @@ public class ResourceDetailsPanel extends WizardStep {
                 setChoices(Arrays.stream(TraceLevel.values()).collect(Collectors.toList())).setNullValid(false));
 
         container.add(new AjaxDropDownChoicePanel<>(
+                "provisionSorter", "provisionSorter",
+                new PropertyModel<>(resourceTO, "provisionSorter"), false).
+                setChoices(provisionSorters));
+
+        container.add(new AjaxDropDownChoicePanel<>(
                 "updateTraceLevel",
                 new ResourceModel("updateTraceLevel", "updateTraceLevel").getObject(),
                 new PropertyModel<>(resourceTO, "updateTraceLevel"),
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.html
index c730e84..6dd792c 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.html
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.html
@@ -44,6 +44,10 @@ under the License.
       </div>
 
       <div class="form-group">
+        <span wicket:id="provisionSorter">[provisionSorter]</span>
+      </div>
+
+      <div class="form-group">
         <span wicket:id="createTraceLevel">[createTraceLevel]</span>
       </div>
 
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.properties
index 5e588e5..004dd02 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.properties
@@ -20,6 +20,7 @@ enforceMandatoryCondition=Enforce mandatory condition
 propagationPriority=Propagation priority
 randomPwdIfNotProvided=Generate random passwords when missing
 propagationActions=Propagation Actions
+provisionSorter=Provision Sorter
 createTraceLevel=Propagation: create trace level
 updateTraceLevel=Propagation: update trace level
 deleteTraceLevel=Propagation: delete trace level
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_fr_CA.properties
index cd93a42..587a5f2 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_fr_CA.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_fr_CA.properties
@@ -17,10 +17,11 @@
 name=Nom de la ressource
 connector=Raccordement
 enforceMandatoryCondition=Application de la condition obligatoire
-propagationPriority=Priorit� de propagation
-randomPwdIfNotProvided=G�n�rer des mots de passe al�atoires si manquants.
+propagationPriority=Priorit\u00e9 de propagation
+randomPwdIfNotProvided=G\u00e9n\u00e9rer des mots de passe al\u00e9atoires si manquants.
 propagationActions=Actions de propagation
-createTraceLevel=Propagation : cr�ation d'un niveau de trace
-updateTraceLevel=Propagation : mise � jour du niveau de trace
+provisionSorter=Trieur de provision
+createTraceLevel=Propagation : cr\u00e9ation d'un niveau de trace
+updateTraceLevel=Propagation : mise \u00e0 jour du niveau de trace
 deleteTraceLevel=Propagation : effacer le niveau de trace
 provisioningTraceLevel=Push/pull le niveau de trace
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_it.properties
index 33422f1..3d69ca0 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_it.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_it.properties
@@ -20,6 +20,7 @@ enforceMandatoryCondition=Abilita mandatory condition
 propagationPriority=Priorit\u00e0 in propagazione
 randomPwdIfNotProvided=Genera password casuali se mancanti
 propagationActions=Azioni di Propagazione
+provisionSorter=Provision Sorter
 createTraceLevel=Propagazione: tracciamento creazione
 updateTraceLevel=Propagazione: tracciamento aggiornamento
 deleteTraceLevel=Propagazione: tracciamento rimozione
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ja.properties
index 7636f94..477b0e8 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ja.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ja.properties
@@ -20,6 +20,7 @@ enforceMandatoryCondition=\u5fc5\u9808\u6761\u4ef6\u306b\u5f93\u3046
 propagationPriority=\u4f1d\u64ad\u512a\u5148\u5ea6
 randomPwdIfNotProvided=\u898b\u3064\u304b\u3089\u306a\u3044\u5834\u5408\u306f\u30e9\u30f3\u30c0\u30e0\u306a\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u751f\u6210
 propagationActions=\u4f1d\u64ad\u30a2\u30af\u30b7\u30e7\u30f3
+provisionSorter=\u30bd\u30fc\u30bf\u30fc\u306e\u30d7\u30ed\u30d3\u30b8\u30e7\u30cb\u30f3\u30b0
 createTraceLevel=\u4f1d\u64ad: \u4f5c\u6210\u30c8\u30ec\u30fc\u30b9\u30ec\u30d9\u30eb
 updateTraceLevel=\u4f1d\u64ad: \u66f4\u65b0\u30c8\u30ec\u30fc\u30b9\u30ec\u30d9\u30eb
 deleteTraceLevel=\u4f1d\u64ad: \u524a\u9664\u30c8\u30ec\u30fc\u30b9\u30ec\u30d9\u30eb
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_pt_BR.properties
index d2c7119..79ac8ed 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_pt_BR.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_pt_BR.properties
@@ -20,6 +20,7 @@ enforceMandatoryCondition=Aplicar condi\u00e7\u00e3o obrigat\u00f3ria
 propagationPriority=Prioridade de propaga\u00e7\u00e3o
 randomPwdIfNotProvided=Gerar senhas aleat\u00f3rias quando n\u00e3o houver
 propagationActions=A\u00e7\u00f5es de Propaga\u00e7\u00e3o
+provisionSorter=Classificador de Provis\u00f5es
 createTraceLevel=Propagation: create trace level
 updateTraceLevel=Propagation: update trace level
 deleteTraceLevel=Propagation: delete trace level
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ru.properties
index 2340913..c2ef730 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ru.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel_ru.properties
@@ -21,6 +21,7 @@ enforceMandatoryCondition=\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c
 propagationPriority=\u041f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439
 randomPwdIfNotProvided=\u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c, \u0435\u0441\u043b\u0438 \u043e\u043d \u043d\u0435 \u0437\u0430\u0434\u0430\u043d
 propagationActions=\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f
+provisionSorter=\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a \u043f\u0440\u043e\u0432\u0438\u0437\u0438\u0438
 createTraceLevel=\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439: \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e
 updateTraceLevel=\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439: \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044e
 deleteTraceLevel=\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439: \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044e
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyProvisionSorter.groovy b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyProvisionSorter.groovy
new file mode 100644
index 0000000..c59a91c
--- /dev/null
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/implementations/MyProvisionSorter.groovy
@@ -0,0 +1,33 @@
+
+/*
+ * 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.
+ */
+import groovy.transform.CompileStatic
+import org.apache.syncope.core.persistence.api.ProvisionSorter
+import org.apache.syncope.core.persistence.api.entity.resource.Provision
+
+
+@CompileStatic
+class MyProvisionSorter implements ProvisionSorter {
+ 
+  @Override
+  int compare(Provision provision1, Provision provision2) {
+    return 0;
+  }
+  
+}
\ No newline at end of file
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
index 8581d54..20a8628 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
@@ -80,6 +80,8 @@ public class ResourceTO implements EntityTO {
 
     private String pushPolicy;
 
+    private String provisionSorter;
+
     private final List<ConnConfProperty> confOverride = new ArrayList<>();
 
     private boolean overrideCapabilities = false;
@@ -195,6 +197,14 @@ public class ResourceTO implements EntityTO {
         this.pushPolicy = pushPolicy;
     }
 
+    public String getProvisionSorter() {
+        return provisionSorter;
+    }
+
+    public void setProvisionSorter(final String provisionSorter) {
+        this.provisionSorter = provisionSorter;
+    }
+
     @JsonIgnore
     public Optional<ProvisionTO> getProvision(final String anyType) {
         return provisions.stream().filter(
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java
index f8cc797..a3aff0a 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java
@@ -37,6 +37,8 @@ public final class IdMImplementationType {
 
     public static final String PUSH_CORRELATION_RULE = "PUSH_CORRELATION_RULE";
 
+    public static final String PROVISION_SORTER = "PROVISION_SORTER";
+
     private static final Map<String, String> VALUES = Map.ofEntries(
             Pair.of(ITEM_TRANSFORMER, "org.apache.syncope.core.spring.security.JWTSSOProvider"),
             Pair.of(RECON_FILTER_BUILDER, "org.apache.syncope.core.persistence.api.dao.Reportlet"),
@@ -44,7 +46,8 @@ public final class IdMImplementationType {
             Pair.of(PULL_ACTIONS, "org.apache.syncope.core.persistence.api.dao.PasswordRule"),
             Pair.of(PUSH_ACTIONS, "org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate"),
             Pair.of(PULL_CORRELATION_RULE, "org.apache.syncope.core.provisioning.api.LogicActions"),
-            Pair.of(PUSH_CORRELATION_RULE, "org.apache.syncope.core.persistence.api.attrvalue.validation.Validator"));
+            Pair.of(PUSH_CORRELATION_RULE, "org.apache.syncope.core.persistence.api.attrvalue.validation.Validator"),
+            Pair.of(PROVISION_SORTER, "org.apache.syncope.core.provisioning.api.ProvisionSorter"));
 
     public static Map<String, String> values() {
         return VALUES;
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index 446a7db..5baf2e5 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -53,6 +53,7 @@ import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
 import org.apache.syncope.core.persistence.api.dao.PullCorrelationRuleConfClass;
 import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
 import org.apache.syncope.core.persistence.api.dao.PushCorrelationRuleConfClass;
+import org.apache.syncope.core.provisioning.api.ProvisionSorter;
 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
 import org.apache.syncope.core.provisioning.java.data.JEXLItemTransformerImpl;
@@ -238,6 +239,10 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
                     classNames.get(IdRepoImplementationType.AUDIT_APPENDER).add(clazz.getName());
                     auditAppenderClasses.add(clazz);
                 }
+
+                if (ProvisionSorter.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                    classNames.get(IdMImplementationType.PROVISION_SORTER).add(bd.getBeanClassName());
+                }
             } catch (Throwable t) {
                 LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
             }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
index 0d91ed0..1bb44d9 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
@@ -63,6 +63,10 @@ public interface ExternalResource extends ProvidedKeyEntity {
     void setPullPolicy(PullPolicy pullPolicy);
 
     PushPolicy getPushPolicy();
+    
+    Implementation getProvisionSorter();
+
+    void setProvisionSorter(Implementation provisionSorter);
 
     void setPushPolicy(PushPolicy pushPolicy);
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
index e61568e..ef97aa6 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
@@ -136,6 +136,9 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
     @ManyToOne(fetch = FetchType.EAGER)
     private JPAPushPolicy pushPolicy;
 
+    @ManyToOne
+    private JPAImplementation provisionSorter;
+
     /**
      * Configuration properties that are overridden from the connector instance.
      */
@@ -330,6 +333,18 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
     }
 
     @Override
+    public Implementation getProvisionSorter() {
+        return provisionSorter;
+    }
+
+    @Override
+    public void setProvisionSorter(final Implementation provisionSorter) {
+        checkType(provisionSorter, JPAImplementation.class);
+        checkImplementationType(provisionSorter, IdMImplementationType.PROVISION_SORTER);
+        this.provisionSorter = (JPAImplementation) provisionSorter;
+    }
+
+    @Override
     public Set<ConnConfProperty> getConfOverride() {
         Set<ConnConfProperty> confOverride = new HashSet<>();
         if (!StringUtils.isBlank(jsonConf)) {
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/ProvisionSorter.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/ProvisionSorter.java
new file mode 100644
index 0000000..ad9d3a1
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/ProvisionSorter.java
@@ -0,0 +1,26 @@
+/*
+ * 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.api;
+
+import java.util.Comparator;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+
+public interface ProvisionSorter extends Comparator<Provision> {
+
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultProvisionSorter.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultProvisionSorter.java
new file mode 100644
index 0000000..0acd40d
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultProvisionSorter.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.ProvisionSorter;
+
+public class DefaultProvisionSorter implements ProvisionSorter {
+
+    @Override
+    public int compare(final Provision provision1, final Provision provision2) {
+        if (provision1.getAnyType().getKind() == AnyTypeKind.USER) {
+            return -1;
+        }
+        if (provision2.getAnyType().getKind() == AnyTypeKind.USER) {
+            return 1;
+        }
+        if (provision1.getAnyType().getKind() == AnyTypeKind.GROUP) {
+            return -1;
+        }
+        if (provision2.getAnyType().getKind() == AnyTypeKind.GROUP) {
+            return 1;
+        }
+        return ObjectUtils.compare(provision1.getAnyType().getKey(), provision2.getAnyType().getKey());
+    }
+
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
index 1b530a3..0cfe2ff 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
@@ -42,6 +42,7 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
 import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
+import org.apache.syncope.core.provisioning.api.ProvisionSorter;
 import org.apache.syncope.core.provisioning.api.notification.RecipientsProvider;
 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
@@ -144,6 +145,10 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                     base = RecipientsProvider.class;
                     break;
 
+                case IdMImplementationType.PROVISION_SORTER:
+                    base = ProvisionSorter.class;
+                    break;
+
                 default:
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index 96dad29..1df550e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -358,6 +358,18 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         resource.setPushPolicy(resourceTO.getPushPolicy() == null
                 ? null : (PushPolicy) policyDAO.find(resourceTO.getPushPolicy()));
 
+        if (resourceTO.getProvisionSorter() == null) {
+            resource.setProvisionSorter(null);
+        } else {
+            Implementation provisionSorter = implementationDAO.find(resourceTO.getProvisionSorter());
+            if (provisionSorter == null) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...",
+                        resourceTO.getProvisionSorter());
+            } else {
+                resource.setProvisionSorter(provisionSorter);
+            }
+        }
+
         resource.setConfOverride(new HashSet<>(resourceTO.getConfOverride()));
 
         resource.setOverrideCapabilities(resourceTO.isOverrideCapabilities());
@@ -675,6 +687,9 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         resourceTO.setPushPolicy(resource.getPushPolicy() == null
                 ? null : resource.getPushPolicy().getKey());
 
+        resourceTO.setProvisionSorter(resource.getProvisionSorter() == null
+                ? null : resource.getProvisionSorter().getKey());
+
         resourceTO.getConfOverride().addAll(resource.getConfOverride());
         Collections.sort(resourceTO.getConfOverride());
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
index 74c935e..ded6b13 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
@@ -49,6 +50,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.ProvisionSorter;
 import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPullResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPullResultHandler;
@@ -62,6 +64,7 @@ import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.OperationOptions;
 import org.identityconnectors.framework.common.objects.SyncToken;
 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
+import org.apache.syncope.core.provisioning.java.DefaultProvisionSorter;
 import org.apache.syncope.core.spring.ImplementationManager;
 
 public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> implements SyncopePullExecutor {
@@ -270,89 +273,99 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             }
         }
 
+        ProvisionSorter provisionSorter = new DefaultProvisionSorter();
+        if (pullTask.getResource().getProvisionSorter() != null) {
+            try {
+                provisionSorter = ImplementationManager.build(pullTask.getResource().getProvisionSorter());
+            } catch (Exception e) {
+                LOG.error("While building {}", pullTask.getResource().getProvisionSorter(), e);
+            }
+        }
         // ...then provisions for any types
         SyncopePullResultHandler handler;
         GroupPullResultHandler ghandler = buildGroupHandler();
-        for (Provision provision : pullTask.getResource().getProvisions()) {
-            if (provision.getMapping() != null) {
-                status.set("Pulling " + provision.getObjectClass().getObjectClassValue());
+        for (Provision provision : pullTask.getResource().getProvisions().stream().
+                filter(provision -> provision.getMapping() != null).sorted(provisionSorter).
+                collect(Collectors.toList())) {
+
+            status.set("Pulling " + provision.getObjectClass().getObjectClassValue());
 
-                switch (provision.getAnyType().getKind()) {
-                    case USER:
-                        handler = buildUserHandler();
+            switch (provision.getAnyType().getKind()) {
+                case USER:
+                    handler = buildUserHandler();
+                    break;
+
+                case GROUP:
+                    handler = ghandler;
+                    break;
+
+                case ANY_OBJECT:
+                default:
+                    handler = buildAnyObjectHandler();
+            }
+            handler.setProfile(profile);
+            handler.setPullExecutor(this);
+
+            try {
+                Set<String> moreAttrsToGet = new HashSet<>();
+                actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
+
+                Stream<? extends Item> mapItems = Stream.concat(
+                        MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
+                        virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
+
+                OperationOptions options = MappingUtils.buildOperationOptions(
+                        mapItems, moreAttrsToGet.toArray(new String[0]));
+
+                switch (pullTask.getPullMode()) {
+                    case INCREMENTAL:
+                        if (!dryRun) {
+                            latestSyncTokens.put(provision.getObjectClass(), provision.getSyncToken());
+                        }
+
+                        connector.sync(
+                                provision.getObjectClass(),
+                                provision.getSyncToken(),
+                                handler,
+                                options);
+
+                        if (!dryRun) {
+                            provision.setSyncToken(latestSyncTokens.get(provision.getObjectClass()));
+                            resourceDAO.save(provision.getResource());
+                        }
                         break;
 
-                    case GROUP:
-                        handler = ghandler;
+                    case FILTERED_RECONCILIATION:
+                        connector.filteredReconciliation(
+                                provision.getObjectClass(),
+                                ImplementationManager.build(pullTask.getReconFilterBuilder()),
+                                handler,
+                                options);
                         break;
 
-                    case ANY_OBJECT:
+                    case FULL_RECONCILIATION:
                     default:
-                        handler = buildAnyObjectHandler();
+                        connector.fullReconciliation(
+                                provision.getObjectClass(),
+                                handler,
+                                options);
+                        break;
                 }
-                handler.setProfile(profile);
-                handler.setPullExecutor(this);
-
-                try {
-                    Set<String> moreAttrsToGet = new HashSet<>();
-                    actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
-
-                    Stream<? extends Item> mapItems = Stream.concat(
-                            MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
-                            virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
-
-                    OperationOptions options = MappingUtils.buildOperationOptions(
-                            mapItems, moreAttrsToGet.toArray(new String[0]));
-
-                    switch (pullTask.getPullMode()) {
-                        case INCREMENTAL:
-                            if (!dryRun) {
-                                latestSyncTokens.put(provision.getObjectClass(), provision.getSyncToken());
-                            }
-
-                            connector.sync(
-                                    provision.getObjectClass(),
-                                    provision.getSyncToken(),
-                                    handler,
-                                    options);
-
-                            if (!dryRun) {
-                                provision.setSyncToken(latestSyncTokens.get(provision.getObjectClass()));
-                                resourceDAO.save(provision.getResource());
-                            }
-                            break;
-
-                        case FILTERED_RECONCILIATION:
-                            connector.filteredReconciliation(
-                                    provision.getObjectClass(),
-                                    ImplementationManager.build(pullTask.getReconFilterBuilder()),
-                                    handler,
-                                    options);
-                            break;
-
-                        case FULL_RECONCILIATION:
-                        default:
-                            connector.fullReconciliation(
-                                    provision.getObjectClass(),
-                                    handler,
-                                    options);
-                            break;
-                    }
-
-                    if (provision.getUidOnCreate() != null) {
-                        AnyUtils anyUtils = anyUtilsFactory.getInstance(provision.getAnyType().getKind());
-                        profile.getResults().stream().
-                                filter(result -> result.getUidValue() != null && result.getKey() != null
-                                && result.getOperation() == ResourceOperation.CREATE
-                                && result.getAnyType().equals(provision.getAnyType().getKey())).
-                                forEach(result -> {
-                                    anyUtils.addAttr(result.getKey(), provision.getUidOnCreate(), result.getUidValue());
-                                });
-                    }
-                } catch (Throwable t) {
-                    throw new JobExecutionException("While pulling from connector", t);
+
+                if (provision.getUidOnCreate() != null) {
+                    AnyUtils anyUtils = anyUtilsFactory.getInstance(provision.getAnyType().getKind());
+                    profile.getResults().stream().
+                            filter(result -> result.getUidValue() != null && result.getKey() != null
+                            && result.getOperation() == ResourceOperation.CREATE
+                            && result.getAnyType().equals(provision.getAnyType().getKey())).
+                            forEach(result -> {
+                                anyUtils.addAttr(result.getKey(), provision.getUidOnCreate(), result.getUidValue());
+                            });
                 }
+            } catch (Throwable t) {
+                throw new JobExecutionException("While pulling from connector", t);
             }
+
         }
         try {
             setGroupOwners(ghandler);
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index d75efc8..40834af 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -62,6 +62,7 @@ import org.apache.syncope.core.persistence.jpa.attrvalue.validation.BinaryValida
 import org.apache.syncope.core.persistence.jpa.attrvalue.validation.EmailAddressValidator;
 import org.apache.syncope.core.persistence.jpa.dao.DefaultPullCorrelationRule;
 import org.apache.syncope.core.persistence.jpa.dao.DefaultPushCorrelationRule;
+import org.apache.syncope.core.provisioning.java.DefaultProvisionSorter;
 import org.apache.syncope.core.provisioning.java.propagation.AzurePropagationActions;
 import org.apache.syncope.core.provisioning.java.propagation.DBPasswordPropagationActions;
 import org.apache.syncope.core.provisioning.java.propagation.GoogleAppsPropagationActions;
@@ -152,6 +153,9 @@ public class ITImplementationLookup implements ImplementationLookup {
     private static final Set<Class<?>> AUDITAPPENDER_CLASSES = new HashSet<>(
             List.of(TestFileAuditAppender.class, TestFileRewriteAuditAppender.class));
 
+    private static final Set<Class<?>> PROVISION_SORTER_CLASSES = new HashSet<>(
+            List.of(DefaultProvisionSorter.class));
+
     private static final Map<String, Set<String>> CLASS_NAMES = new HashMap<String, Set<String>>() {
 
         private static final long serialVersionUID = 3109256773218160485L;
@@ -232,6 +236,10 @@ public class ITImplementationLookup implements ImplementationLookup {
             classNames = ITImplementationLookup.AUDITAPPENDER_CLASSES.stream().
                     map(Class::getName).collect(Collectors.toSet());
             put(IdRepoImplementationType.AUDIT_APPENDER, classNames);
+
+            classNames = ITImplementationLookup.PROVISION_SORTER_CLASSES.stream().
+                    map(Class::getName).collect(Collectors.toSet());
+            put(IdMImplementationType.PROVISION_SORTER, classNames);
         }
     };
 
diff --git a/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java b/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java
index 3a25faf..9e4096d 100644
--- a/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java
+++ b/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java
@@ -183,7 +183,7 @@ public final class ResourceExplorerTopComponent extends TopComponent {
                     getLastSelectedPathComponent();
             DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) selectedNode.getParent();
             String parentNodeName = Optional.ofNullable(parentNode)
-                .map(node -> String.valueOf(node.getUserObject())).orElse(null);
+                    .map(node -> String.valueOf(node.getUserObject())).orElse(null);
             if (selectedNode.isLeaf() && StringUtils.isNotBlank(parentNodeName)) {
                 String leafNodeName = (String) selectedNode.getUserObject();
                 DefaultMutableTreeNode grandParentNode = (DefaultMutableTreeNode) parentNode.getParent();
@@ -298,7 +298,7 @@ public final class ResourceExplorerTopComponent extends TopComponent {
     private void addMailTemplates() {
         List<MailTemplateTO> mailTemplateList = mailTemplateManagerService.list();
         mailTemplateList
-            .forEach(mailTemplate -> this.mailTemplates.add(new DefaultMutableTreeNode(mailTemplate.getKey())));
+                .forEach(mailTemplate -> this.mailTemplates.add(new DefaultMutableTreeNode(mailTemplate.getKey())));
         treeModel.reload();
     }
 
@@ -446,6 +446,10 @@ public final class ResourceExplorerTopComponent extends TopComponent {
                                 templateClassName = "MyRecipientsProvider";
                                 break;
 
+                            case IdMImplementationType.PROVISION_SORTER:
+                                templateClassName = "MyProvisionSorter";
+                                break;
+
                             default:
                         }
                         newNode.setBody(IOUtils.toString(getClass().getResourceAsStream(
@@ -719,8 +723,6 @@ public final class ResourceExplorerTopComponent extends TopComponent {
                 componentClosed();
                 componentOpened();
             }
-
         };
     }
-
 }