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 2022/09/01 12:04:10 UTC

[syncope] branch master updated: [SYNCOPE-1694] Refactor ImplementationManager to allow either per-class and per-instance caches (#372)

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 5119be8c25 [SYNCOPE-1694] Refactor ImplementationManager to allow either per-class and per-instance caches (#372)
5119be8c25 is described below

commit 5119be8c2506d5aaa0965dd085a01756fbd3c91a
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Thu Sep 1 14:04:04 2022 +0200

    [SYNCOPE-1694] Refactor ImplementationManager to allow either per-class and per-instance caches (#372)
---
 .../common/lib/types/ConnectorCapability.java      |   1 +
 .../syncope/core/logic/AbstractAnyLogic.java       |  13 +-
 .../init/ClassPathScanImplementationLookup.java    |   4 +-
 ...idator.java => PlainAttrValidationManager.java} |   6 +-
 ...Validator.java => PlainAttrValueValidator.java} |   6 +-
 .../core/persistence/api/entity/AnyUtils.java      |   3 +-
 .../core/persistence/api/entity/PlainAttr.java     |   6 +-
 .../jpa/MyJPAJSONPersistenceContext.java           |   7 +-
 .../jpa/PGJPAJSONPersistenceContext.java           |  11 +-
 .../persistence/jpa/dao/MyJPAJSONAnySearchDAO.java |  17 +-
 .../persistence/jpa/dao/PGJPAJSONAnySearchDAO.java |  19 +-
 .../entity/JPAJSONAttributableValidator.java       |   2 +-
 .../src/test/resources/domains/MasterContent.xml   |   4 +-
 .../core/persistence/jpa/PersistenceContext.java   |  26 ++-
 .../attrvalue/validation/AbstractValidator.java    |  17 +-
 .../attrvalue/validation/AlwaysTrueValidator.java  |   3 +-
 .../jpa/attrvalue/validation/BasicValidator.java   |   5 +-
 .../jpa/attrvalue/validation/BinaryValidator.java  |   3 +-
 .../DefaultPlainAttrValidationManager.java         |  60 +++++
 .../validation/EmailAddressValidator.java          |   3 +-
 .../jpa/attrvalue/validation/URLValidator.java     |   3 +-
 .../persistence/jpa/dao/AbstractAnySearchDAO.java  |  12 +-
 .../core/persistence/jpa/dao/JPAAnyMatchDAO.java   |  22 +-
 .../core/persistence/jpa/dao/JPAAnySearchDAO.java  |  17 +-
 .../core/persistence/jpa/dao/JPAUserDAO.java       |  71 ++++--
 .../persistence/jpa/entity/AbstractPlainAttr.java  |   9 +-
 .../core/persistence/jpa/entity/JPAAnyUtils.java   |  10 +-
 .../persistence/jpa/entity/JPAPlainSchema.java     |  28 ---
 ...trValidator.java => JPAPlainAttrValidator.java} |   2 +-
 .../jpa/validation/entity/PlainAttrCheck.java      |   2 +-
 .../core/persistence/jpa/inner/PlainAttrTest.java  |  24 +-
 .../core/persistence/jpa/outer/AnySearchTest.java  |   8 +-
 .../core/persistence/jpa/outer/GroupTest.java      |  12 +-
 .../core/persistence/jpa/outer/RoleTest.java       |   6 +-
 .../core/persistence/jpa/outer/UserTest.java       |  14 +-
 .../src/test/resources/domains/MasterContent.xml   |   4 +-
 .../provisioning/java/ConnectorFacadeProxy.java    |   2 +-
 .../provisioning/java/ProvisioningContext.java     |  19 +-
 .../java/data/AbstractAnyDataBinder.java           |   9 +-
 .../java/data/AnyObjectDataBinderImpl.java         |   7 +-
 .../java/data/GroupDataBinderImpl.java             |   7 +-
 .../java/data/ImplementationDataBinderImpl.java    |   4 +-
 .../java/data/ResourceDataBinderImpl.java          |  10 +-
 .../provisioning/java/data/UserDataBinderImpl.java |   5 +-
 .../notification/DefaultNotificationManager.java   |   9 +-
 .../AbstractPropagationTaskExecutor.java           |  20 +-
 .../java/propagation/AzurePropagationActions.java  |   3 +
 .../propagation/DBPasswordPropagationActions.java  |   3 +
 .../propagation/DefaultPropagationManager.java     |   3 -
 .../propagation/GoogleAppsPropagationActions.java  |   3 +
 .../LDAPMembershipPropagationActions.java          |   3 +
 .../LDAPPasswordPropagationActions.java            |   3 +
 .../PriorityPropagationTaskExecutor.java           |   5 +-
 .../pushpull/AbstractProvisioningJobDelegate.java  |  23 ++
 .../provisioning/java/pushpull/InboundMatcher.java |  54 +++--
 .../java/pushpull/OutboundMatcher.java             |  46 ++--
 .../java/pushpull/PullJobDelegate.java             |  71 +++---
 .../java/pushpull/PushJobDelegate.java             |  45 ++--
 .../java/pushpull/SinglePullJobDelegate.java       |  29 +--
 .../java/pushpull/SinglePushJobDelegate.java       |  37 +--
 .../pushpull/stream/StreamPullJobDelegate.java     |  27 +--
 .../pushpull/stream/StreamPushJobDelegate.java     |  25 +-
 .../core/provisioning/java/utils/MappingUtils.java |   9 +-
 .../java/DefaultMappingManagerTest.java            |   6 +-
 .../syncope/core/spring/ImplementationManager.java | 250 --------------------
 .../implementation/ImplementationManager.java      | 252 +++++++++++++++++++++
 .../core/spring/implementation/InstanceScope.java  |  23 +-
 .../implementation/SyncopeImplementation.java}     |  15 +-
 .../spring/security/DefaultPasswordGenerator.java  |  38 +++-
 .../core/spring/ImplementationManagerTest.java     |  10 +-
 .../jpa/ElasticsearchPersistenceContext.java       |   3 +
 .../jpa/dao/ElasticsearchAnySearchDAO.java         |  14 +-
 .../core/reference/DateToDateItemTransformer.java  |   3 +
 .../core/reference/DateToLongItemTransformer.java  |   3 +
 74 files changed, 919 insertions(+), 639 deletions(-)

diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
index 1abf35f88a..70c9f4170a 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
@@ -26,6 +26,7 @@ public enum ConnectorCapability {
     AUTHENTICATE,
     CREATE,
     UPDATE,
+    UPDATE_DELTA,
     DELETE,
     SEARCH,
     SYNC;
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
index c7a82a623b..1a21f400f6 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
@@ -20,6 +20,8 @@ package org.apache.syncope.core.logic;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.request.AnyCR;
@@ -52,6 +54,8 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, C extends AnyCR, U exte
 
     protected final TemplateUtils templateUtils;
 
+    protected final Map<String, LogicActions> perContextActions = new ConcurrentHashMap<>();
+
     public AbstractAnyLogic(
             final RealmDAO realmDAO,
             final AnyTypeDAO anyTypeDAO,
@@ -63,17 +67,20 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, C extends AnyCR, U exte
     }
 
     protected List<LogicActions> getActions(final Realm realm) {
-        List<LogicActions> actions = new ArrayList<>();
+        List<LogicActions> result = new ArrayList<>();
 
         realm.getActions().forEach(impl -> {
             try {
-                actions.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.warn("While building {}", impl, e);
             }
         });
 
-        return actions;
+        return result;
     }
 
     @SuppressWarnings("unchecked")
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 a673daea32..a9e3194667 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
@@ -36,7 +36,7 @@ import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
 import org.apache.syncope.core.logic.audit.AuditAppender;
 import org.apache.syncope.core.logic.audit.JdbcAuditAppender;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
 import org.apache.syncope.core.persistence.api.dao.AccountRule;
 import org.apache.syncope.core.persistence.api.dao.AccountRuleConfClass;
 import org.apache.syncope.core.persistence.api.dao.PasswordRule;
@@ -225,7 +225,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
                     classNames.get(IdMImplementationType.PUSH_ACTIONS).add(bd.getBeanClassName());
                 }
 
-                if (Validator.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                if (PlainAttrValueValidator.class.isAssignableFrom(clazz) && !isAbstractClazz) {
                     classNames.get(IdRepoImplementationType.VALIDATOR).add(bd.getBeanClassName());
                 }
 
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValidationManager.java
similarity index 88%
copy from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValidationManager.java
index 5b49b4ccf8..f686162dae 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValidationManager.java
@@ -21,9 +21,7 @@ package org.apache.syncope.core.persistence.api.attrvalue.validation;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
-public interface Validator {
+public interface PlainAttrValidationManager {
 
-    void setSchema(PlainSchema schema);
-
-    void validate(String value, PlainAttrValue attrValue);
+    void validate(PlainSchema schema, String value, PlainAttrValue attrValue);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValueValidator.java
similarity index 88%
rename from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
rename to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValueValidator.java
index 5b49b4ccf8..44d1f21161 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValueValidator.java
@@ -21,9 +21,7 @@ package org.apache.syncope.core.persistence.api.attrvalue.validation;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
-public interface Validator {
+public interface PlainAttrValueValidator {
 
-    void setSchema(PlainSchema schema);
-
-    void validate(String value, PlainAttrValue attrValue);
+    void validate(PlainSchema schema, String value, PlainAttrValue attrValue);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
index 4c65c764f5..0ceb427afe 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
@@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.request.AnyCR;
 import org.apache.syncope.common.lib.request.AnyUR;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 
 public interface AnyUtils {
@@ -58,5 +59,5 @@ public interface AnyUtils {
 
     Set<ExternalResource> getAllResources(Any<?> any);
 
-    void addAttr(String key, PlainSchema schema, String value);
+    void addAttr(PlainAttrValidationManager validator, String key, PlainSchema schema, String value);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java
index 2e44dcea67..15376c3016 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.api.entity;
 
 import java.util.List;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 
 public interface PlainAttr<A extends Any<?>> extends Entity {
 
@@ -30,9 +31,9 @@ public interface PlainAttr<A extends Any<?>> extends Entity {
 
     void setSchema(PlainSchema schema);
 
-    void add(String value, AnyUtils anyUtils);
+    void add(PlainAttrValidationManager validator, String value, AnyUtils anyUtils);
 
-    void add(String value, PlainAttrValue attrValue);
+    void add(PlainAttrValidationManager validator, String value, PlainAttrValue attrValue);
 
     PlainAttrUniqueValue getUniqueValue();
 
@@ -41,5 +42,4 @@ public interface PlainAttr<A extends Any<?>> extends Entity {
     List<? extends PlainAttrValue> getValues();
 
     List<String> getValuesAsStrings();
-
 }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java
index ab01aac0f2..b638bb1051 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.jpa;
 
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
@@ -66,7 +67,8 @@ public class MyJPAJSONPersistenceContext extends JPAJSONPersistenceContext {
             final @Lazy AnyObjectDAO anyObjectDAO,
             final @Lazy PlainSchemaDAO schemaDAO,
             final @Lazy EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         return new MyJPAJSONAnySearchDAO(
                 realmDAO,
@@ -76,7 +78,8 @@ public class MyJPAJSONPersistenceContext extends JPAJSONPersistenceContext {
                 anyObjectDAO,
                 schemaDAO,
                 entityFactory,
-                anyUtilsFactory);
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean(name = "myJPAJSONAuditConfDAO")
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java
index 88e294914f..551b2c859d 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.jpa;
 
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
@@ -64,9 +65,10 @@ public class PGJPAJSONPersistenceContext extends JPAJSONPersistenceContext {
             final @Lazy UserDAO userDAO,
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO,
-            final @Lazy PlainSchemaDAO plainSchemaDAO,
+            final @Lazy PlainSchemaDAO schemaDAO,
             final @Lazy EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         return new PGJPAJSONAnySearchDAO(
                 realmDAO,
@@ -74,9 +76,10 @@ public class PGJPAJSONPersistenceContext extends JPAJSONPersistenceContext {
                 userDAO,
                 groupDAO,
                 anyObjectDAO,
-                plainSchemaDAO,
+                schemaDAO,
                 entityFactory,
-                anyUtilsFactory);
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean(name = "pgJPAJSONAuditConfDAO")
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
index 9d29051adf..a76fe02804 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -54,9 +55,19 @@ public class MyJPAJSONAnySearchDAO extends JPAAnySearchDAO {
             final AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
-
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, schemaDAO, entityFactory, anyUtilsFactory);
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
+
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
     }
 
     @Override
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
index 4c73c531a3..c9a209d822 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
@@ -33,6 +33,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -88,11 +89,21 @@ public class PGJPAJSONAnySearchDAO extends JPAAnySearchDAO {
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
-            final PlainSchemaDAO plainSchemaDAO,
+            final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
-
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, plainSchemaDAO, entityFactory, anyUtilsFactory);
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
+
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
     }
 
     @Override
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
index aa72fd46ce..0d93175844 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
@@ -29,7 +29,7 @@ public class JPAJSONAttributableValidator extends AbstractValidator<JPAJSONAttri
     public boolean isValid(final JSONAttributable<?> entity, final ConstraintValidatorContext context) {
         context.disableDefaultConstraintViolation();
 
-        PlainAttrValidator attrValidator = new PlainAttrValidator();
+        JPAPlainAttrValidator attrValidator = new JPAPlainAttrValidator();
         PlainAttrValueValidator attrValueValidator = new PlainAttrValueValidator();
 
         AtomicReference<Boolean> isValid = new AtomicReference<>(Boolean.TRUE);
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index c3799e7347..b959f52e72 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -545,7 +545,7 @@ under the License.
                 connectorName="net.tirasa.connid.bundles.ldap.LdapConnector"
                 version="${connid.ldap.version}" 
                 jsonConf='[{"schema":{"name":"host","type":"java.lang.String","required":true,"order":1,"confidential":false,"defaultValues":[]},"values":["localhost"],"overridable":false},{"schema":{"name":"port","type":"int","required":false,"order":2,"confidential":false,"defaultValues":[389]},"values":[1389],"overridable":false},{"schema":{"name":"ssl","type":"boolean","required":false,"order":3,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"s [...]
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH"]'/>
   
   <ConnInstance id="a28abd9b-9f4a-4ef6-a7a8-d19ad2a8f29d" displayName="H2-test2"
                 adminRealm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
@@ -570,7 +570,7 @@ under the License.
                 connectorName="net.tirasa.connid.bundles.db.scriptedsql.ScriptedSQLConnector"
                 displayName="Scripted SQL" version="${connid.database.version}"
                 jsonConf='[{&quot;schema&quot;:{&quot;name&quot;:&quot;updateScriptFileName&quot;,&quot;displayName&quot;:&quot;updateScriptFileName&quot;,&quot;helpMessage&quot;:&quot;updateScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/UpdateScript.groovy&quot;]},{&quot;schem [...]
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH","SYNC"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH","SYNC"]'/>
   
   <ConnInstance id="44c02549-19c3-483c-8025-4919c3283c37" bundlename="net.tirasa.connid.bundles.rest"
                 adminRealm_id="0679e069-7355-4b20-bd11-a5a0a5453c7c"
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
index b2e7b2cc5b..cf95524230 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
@@ -26,6 +26,7 @@ import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.keymaster.client.api.DomainOps;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.DomainRegistry;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -79,6 +80,7 @@ import org.apache.syncope.core.persistence.api.entity.am.ClientAppUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.jpa.attrvalue.validation.DefaultPlainAttrValidationManager;
 import org.apache.syncope.core.persistence.jpa.content.KeymasterConfParamLoader;
 import org.apache.syncope.core.persistence.jpa.content.XMLContentExporter;
 import org.apache.syncope.core.persistence.jpa.content.XMLContentLoader;
@@ -174,6 +176,12 @@ public class PersistenceContext {
         return new LocalValidatorFactoryBean();
     }
 
+    @ConditionalOnMissingBean
+    @Bean
+    public PlainAttrValidationManager plainAttrValidationManager() {
+        return new DefaultPlainAttrValidationManager();
+    }
+
     @ConditionalOnMissingBean
     @Bean
     public CommonEntityManagerFactoryConf commonEMFConf(final PersistenceProperties persistenceProperties) {
@@ -321,9 +329,17 @@ public class PersistenceContext {
             final @Lazy AnyObjectDAO anyObjectDAO,
             final RealmDAO realmDAO,
             final PlainSchemaDAO plainSchemaDAO,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
-        return new JPAAnyMatchDAO(userDAO, groupDAO, anyObjectDAO, realmDAO, plainSchemaDAO, anyUtilsFactory);
+        return new JPAAnyMatchDAO(
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                realmDAO,
+                plainSchemaDAO,
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean
@@ -355,7 +371,8 @@ public class PersistenceContext {
             final @Lazy AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         return new JPAAnySearchDAO(
                 realmDAO,
@@ -365,7 +382,8 @@ public class PersistenceContext {
                 anyObjectDAO,
                 schemaDAO,
                 entityFactory,
-                anyUtilsFactory);
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java
index 8c149db99a..4bcecbb353 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java
@@ -19,30 +19,23 @@
 package org.apache.syncope.core.persistence.jpa.attrvalue.validation;
 
 import java.io.Serializable;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public abstract class AbstractValidator implements Validator, Serializable {
+public abstract class AbstractValidator implements PlainAttrValueValidator, Serializable {
 
     private static final long serialVersionUID = -5439345166669502493L;
 
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractValidator.class);
 
-    protected PlainSchema schema;
-
-    @Override
-    public void setSchema(final PlainSchema schema) {
-        this.schema = schema;
-    }
-
     @Override
-    public void validate(final String value, final PlainAttrValue attrValue) {
+    public void validate(final PlainSchema schema, final String value, final PlainAttrValue attrValue) {
         attrValue.parseValue(schema, value);
-        doValidate(attrValue);
+        doValidate(schema, attrValue);
     }
 
-    protected abstract void doValidate(PlainAttrValue attrValue);
+    protected abstract void doValidate(PlainSchema schema, PlainAttrValue attrValue);
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java
index 65d63edc4f..887eefc3df 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java
@@ -20,13 +20,14 @@ package org.apache.syncope.core.persistence.jpa.attrvalue.validation;
 
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class AlwaysTrueValidator extends AbstractValidator {
 
     private static final long serialVersionUID = 872107345555773183L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         Boolean value = attrValue.getValue();
         if (!value) {
             throw new InvalidPlainAttrValueException("This attribute must be set to \"true\"");
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java
index 2e62e16b58..13840dabbe 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java
@@ -22,13 +22,14 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class BasicValidator extends AbstractValidator {
 
     private static final long serialVersionUID = -2606728447694223607L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         if (AttrSchemaType.Enum == schema.getType()) {
             final String[] enumeration = schema.getEnumerationValues().split(SyncopeConstants.ENUM_VALUES_SEPARATOR);
             final String value = attrValue.getStringValue();
@@ -42,7 +43,7 @@ public class BasicValidator extends AbstractValidator {
 
             if (!found) {
                 throw new InvalidPlainAttrValueException(
-                    '\'' + value + "' is not one of: " + schema.getEnumerationValues());
+                        '\'' + value + "' is not one of: " + schema.getEnumerationValues());
             }
         }
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java
index 0df8bb2bf3..bc7d2fdc00 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.tika.Tika;
 
 public class BinaryValidator extends AbstractValidator {
@@ -38,7 +39,7 @@ public class BinaryValidator extends AbstractValidator {
     }
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         // check Binary schemas MIME Type mismatches
         if (attrValue.getBinaryValue() != null) {
             byte[] binaryValue = attrValue.getBinaryValue();
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/DefaultPlainAttrValidationManager.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/DefaultPlainAttrValidationManager.java
new file mode 100644
index 0000000000..7cc027488e
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/DefaultPlainAttrValidationManager.java
@@ -0,0 +1,60 @@
+/*
+ * 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.persistence.jpa.attrvalue.validation;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.spring.ImplementationManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultPlainAttrValidationManager implements PlainAttrValidationManager {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(DefaultPlainAttrValidationManager.class);
+
+    protected static final PlainAttrValueValidator BASIC_VALIDATOR = new BasicValidator();
+
+    protected final Map<String, PlainAttrValueValidator> perContextValidators = new ConcurrentHashMap<>();
+
+    @Override
+    public void validate(final PlainSchema schema, final String value, final PlainAttrValue attrValue) {
+        PlainAttrValueValidator validator = null;
+
+        if (schema.getValidator() != null) {
+            try {
+                validator = ImplementationManager.build(
+                        schema.getValidator(),
+                        () -> perContextValidators.get(schema.getValidator().getKey()),
+                        instance -> perContextValidators.put(schema.getValidator().getKey(), instance));
+            } catch (Exception e) {
+                LOG.error("While building {}", schema.getValidator(), e);
+            }
+        }
+
+        if (validator == null) {
+            validator = BASIC_VALIDATOR;
+        }
+
+        validator.validate(schema, value, attrValue);
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java
index ebc9e9d044..3fa84390fa 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java
@@ -22,13 +22,14 @@ import java.util.regex.Matcher;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class EmailAddressValidator extends AbstractValidator {
 
     private static final long serialVersionUID = 792457177290331518L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         Matcher matcher = Entity.EMAIL_PATTERN.matcher(attrValue.<CharSequence>getValue());
         if (!matcher.matches()) {
             throw new InvalidPlainAttrValueException("\"" + attrValue.getValue() + "\" is not a valid email address");
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java
index 5646b0b1e2..acb8f6b9b0 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java
@@ -22,13 +22,14 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class URLValidator extends AbstractValidator {
 
     private static final long serialVersionUID = 792457177290331518L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         try {
             new URL(attrValue.getStringValue());
         } catch (MalformedURLException e) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
index 53863ab241..0443b45761 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
@@ -35,6 +35,7 @@ import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
@@ -60,7 +61,6 @@ import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
-import org.apache.syncope.core.persistence.jpa.entity.JPAPlainSchema;
 import org.springframework.util.CollectionUtils;
 
 public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO {
@@ -126,6 +126,8 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
 
     protected final AnyUtilsFactory anyUtilsFactory;
 
+    protected final PlainAttrValidationManager validator;
+
     public AbstractAnySearchDAO(
             final RealmDAO realmDAO,
             final DynRealmDAO dynRealmDAO,
@@ -134,7 +136,8 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
             final AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO plainSchemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         this.realmDAO = realmDAO;
         this.dynRealmDAO = dynRealmDAO;
@@ -144,6 +147,7 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
         this.plainSchemaDAO = plainSchemaDAO;
         this.entityFactory = entityFactory;
         this.anyUtilsFactory = anyUtilsFactory;
+        this.validator = validator;
     }
 
     protected abstract int doCount(
@@ -210,7 +214,7 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
                     && cond.getType() != AttrCond.Type.ISNULL
                     && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
-                ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
+                validator.validate(schema, cond.getExpression(), attrValue);
             }
         } catch (ValidationException e) {
             throw new IllegalArgumentException("Could not validate expression " + cond.getExpression());
@@ -272,7 +276,7 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
                 && computed.getType() != AttrCond.Type.ISNOTNULL) {
 
             try {
-                ((JPAPlainSchema) schema).validator().validate(computed.getExpression(), attrValue);
+                validator.validate(schema, computed.getExpression(), attrValue);
             } catch (ValidationException e) {
                 throw new IllegalArgumentException("Could not validate expression " + computed.getExpression());
             }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
index add9784f7d..d086fc8ad0 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
@@ -35,6 +35,7 @@ import org.apache.commons.lang3.ClassUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -81,13 +82,16 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
 
     protected final AnyUtilsFactory anyUtilsFactory;
 
+    protected final PlainAttrValidationManager validator;
+
     public JPAAnyMatchDAO(
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
             final RealmDAO realmDAO,
             final PlainSchemaDAO plainSchemaDAO,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         this.userDAO = userDAO;
         this.groupDAO = groupDAO;
@@ -95,6 +99,7 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
         this.realmDAO = realmDAO;
         this.plainSchemaDAO = plainSchemaDAO;
         this.anyUtilsFactory = anyUtilsFactory;
+        this.validator = validator;
     }
 
     /**
@@ -171,8 +176,9 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
                 }
 
                 if (match == null) {
-                    Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
-                    match = anyCond.map(value -> matches(any, value, not)).orElseGet(() -> cond.getLeaf(AttrCond.class).
+                    match = cond.getLeaf(AnyCond.class).
+                            map(value -> matches(any, value, not)).
+                            orElseGet(() -> cond.getLeaf(AttrCond.class).
                             map(leaf -> matches(any, leaf, not)).
                             orElse(null));
                 }
@@ -197,12 +203,12 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
         return false;
     }
 
-    protected static boolean matches(final Any<?> any, final AnyTypeCond cond, final boolean not) {
+    protected boolean matches(final Any<?> any, final AnyTypeCond cond, final boolean not) {
         boolean equals = any.getType().getKey().equals(cond.getAnyTypeKey());
         return not ? !equals : equals;
     }
 
-    protected static boolean matches(
+    protected boolean matches(
             final GroupableRelatable<?, ?, ?, ?, ?> any, final RelationshipTypeCond cond, final boolean not) {
 
         boolean found = any.getRelationships().stream().
@@ -284,7 +290,7 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected static boolean matches(
+    protected boolean matches(
             final List<? extends PlainAttrValue> anyAttrValues,
             final PlainAttrValue attrValue,
             final PlainSchema schema,
@@ -374,7 +380,7 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
                             && cond.getType() != AttrCond.Type.ISNULL
                             && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
-                        ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
+                        validator.validate(schema, cond.getExpression(), attrValue);
                     }
                 } catch (ValidationException e) {
                     LOG.error("Could not validate expression '" + cond.getExpression() + '\'', e);
@@ -466,7 +472,7 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
                         && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
                     try {
-                        ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
+                        validator.validate(schema, cond.getExpression(), attrValue);
                     } catch (ValidationException e) {
                         LOG.error("Could not validate expression '" + cond.getExpression() + '\'', e);
                         return false;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index c87e434b4d..a1bca5e545 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -35,6 +35,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -79,9 +80,19 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
             final AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO plainSchemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
-
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, plainSchemaDAO, entityFactory, anyUtilsFactory);
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
+
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                plainSchemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
     }
 
     protected String buildAdminRealmsFilter(
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index aca6eee89d..d31abd15d6 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import javax.persistence.NoResultException;
 import javax.persistence.PersistenceException;
@@ -38,11 +39,13 @@ import org.apache.syncope.common.lib.types.EntityViolationType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.AccountRule;
 import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.FIQLQueryDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
@@ -93,6 +96,10 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
 
     protected final SecurityProperties securityProperties;
 
+    protected final Map<String, AccountRule> perContextAccountRules = new ConcurrentHashMap<>();
+
+    protected final Map<String, PasswordRule> perContextPasswordRules = new ConcurrentHashMap<>();
+
     public JPAUserDAO(
             final AnyUtilsFactory anyUtilsFactory,
             final PlainSchemaDAO plainSchemaDAO,
@@ -321,6 +328,42 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         return policies;
     }
 
+    protected List<AccountRule> getAccountRules(final AccountPolicy policy) {
+        List<AccountRule> result = new ArrayList<>();
+
+        for (Implementation impl : policy.getRules()) {
+            try {
+                ImplementationManager.buildAccountRule(
+                        impl,
+                        () -> perContextAccountRules.get(impl.getKey()),
+                        instance -> perContextAccountRules.put(impl.getKey(), instance)).
+                        ifPresent(result::add);
+            } catch (Exception e) {
+                LOG.warn("While building {}", impl, e);
+            }
+        }
+
+        return result;
+    }
+
+    protected List<PasswordRule> getPasswordRules(final PasswordPolicy policy) {
+        List<PasswordRule> result = new ArrayList<>();
+
+        for (Implementation impl : policy.getRules()) {
+            try {
+                ImplementationManager.buildPasswordRule(
+                        impl,
+                        () -> perContextPasswordRules.get(impl.getKey()),
+                        instance -> perContextPasswordRules.put(impl.getKey(), instance)).
+                        ifPresent(result::add);
+            } catch (Exception e) {
+                LOG.warn("While building {}", impl, e);
+            }
+        }
+
+        return result;
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Pair<Boolean, Boolean> enforcePolicies(final User user) {
@@ -336,15 +379,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
                     throw new PasswordPolicyException("Password mandatory");
                 }
 
-                for (Implementation impl : policy.getRules()) {
-                    ImplementationManager.buildPasswordRule(impl).ifPresent(rule -> {
-                        rule.enforce(user);
+                getPasswordRules(policy).forEach(rule -> {
+                    rule.enforce(user);
 
-                        user.getLinkedAccounts().stream().
-                                filter(account -> account.getPassword() != null).
-                                forEach(rule::enforce);
-                    });
-                }
+                    user.getLinkedAccounts().stream().
+                            filter(account -> account.getPassword() != null).
+                            forEach(rule::enforce);
+                });
 
                 boolean matching = false;
                 if (policy.getHistoryLength() > 0) {
@@ -419,15 +460,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
                         });
             } else {
                 for (AccountPolicy policy : accountPolicies) {
-                    for (Implementation impl : policy.getRules()) {
-                        ImplementationManager.buildAccountRule(impl).ifPresent(rule -> {
-                            rule.enforce(user);
+                    getAccountRules(policy).forEach(rule -> {
+                        rule.enforce(user);
 
-                            user.getLinkedAccounts().stream().
-                                    filter(account -> account.getUsername() != null).
-                                    forEach(rule::enforce);
-                        });
-                    }
+                        user.getLinkedAccounts().stream().
+                                filter(account -> account.getUsername() != null).
+                                forEach(rule::enforce);
+                    });
 
                     suspend |= user.getFailedLogins() != null && policy.getMaxAuthenticationAttempts() > 0
                             && user.getFailedLogins() > policy.getMaxAuthenticationAttempts() && !user.isSuspended();
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java
index b3dfd3d213..0dc8ea0967 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java
@@ -26,6 +26,7 @@ import javax.persistence.FetchType;
 import javax.persistence.ManyToOne;
 import javax.persistence.MappedSuperclass;
 import javax.validation.constraints.NotNull;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
@@ -65,11 +66,11 @@ public abstract class AbstractPlainAttr<O extends Any<?>> extends AbstractGenera
     }
 
     @Override
-    public void add(final String value, final PlainAttrValue attrValue) {
+    public void add(final PlainAttrValidationManager validator, final String value, final PlainAttrValue attrValue) {
         checkNonNullSchema();
 
         attrValue.setAttr(this);
-        getSchema().validator().validate(value, attrValue);
+        validator.validate(getSchema(), value, attrValue);
 
         if (getSchema().isUniqueConstraint()) {
             setUniqueValue((PlainAttrUniqueValue) attrValue);
@@ -82,7 +83,7 @@ public abstract class AbstractPlainAttr<O extends Any<?>> extends AbstractGenera
     }
 
     @Override
-    public void add(final String value, final AnyUtils anyUtils) {
+    public void add(final PlainAttrValidationManager validator, final String value, final AnyUtils anyUtils) {
         checkNonNullSchema();
 
         PlainAttrValue attrValue;
@@ -93,7 +94,7 @@ public abstract class AbstractPlainAttr<O extends Any<?>> extends AbstractGenera
             attrValue = anyUtils.newPlainAttrValue();
         }
 
-        add(value, attrValue);
+        add(validator, value, attrValue);
     }
 
     @Override
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
index ae37005b57..786709523b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
@@ -41,6 +41,7 @@ import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -420,7 +421,12 @@ public class JPAAnyUtils implements AnyUtils {
 
     @Transactional
     @Override
-    public void addAttr(final String key, final PlainSchema schema, final String value) {
+    public void addAttr(
+            final PlainAttrValidationManager validator,
+            final String key,
+            final PlainSchema schema,
+            final String value) {
+
         Any any = dao().find(key);
 
         Set<AnyTypeClass> typeOwnClasses = new HashSet<>();
@@ -439,7 +445,7 @@ public class JPAAnyUtils implements AnyUtils {
             any.add(attr);
 
             try {
-                attr.add(value, this);
+                attr.add(validator, value, this);
                 dao().save(any);
             } catch (InvalidPlainAttrValueException e) {
                 LOG.error("Invalid value for attribute {} and {}: {}", schema.getKey(), any, value, e);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java
index e795177db0..3299bd6612 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java
@@ -27,18 +27,14 @@ import javax.persistence.Lob;
 import javax.persistence.OneToOne;
 import javax.persistence.PrimaryKeyJoinColumn;
 import javax.persistence.Table;
-import javax.persistence.Transient;
 import javax.validation.constraints.NotNull;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
-import org.apache.syncope.core.persistence.jpa.attrvalue.validation.BasicValidator;
 import org.apache.syncope.core.persistence.jpa.validation.entity.PlainSchemaCheck;
-import org.apache.syncope.core.spring.ImplementationManager;
 
 @Entity
 @Table(name = JPAPlainSchema.TABLE)
@@ -90,9 +86,6 @@ public class JPAPlainSchema extends AbstractSchema implements PlainSchema {
     @OneToOne
     private JPAImplementation validator;
 
-    @Transient
-    private Validator validatorImpl;
-
     @Override
     public AnyTypeClass getAnyTypeClass() {
         return anyTypeClass;
@@ -154,27 +147,6 @@ public class JPAPlainSchema extends AbstractSchema implements PlainSchema {
         this.readonly = readonly;
     }
 
-    public Validator validator() {
-        if (validatorImpl != null) {
-            return validatorImpl;
-        }
-
-        if (getValidator() != null) {
-            try {
-                validatorImpl = ImplementationManager.build(getValidator());
-            } catch (Exception e) {
-                LOG.error("While building {}", getValidator(), e);
-            }
-        }
-
-        if (validatorImpl == null) {
-            validatorImpl = new BasicValidator();
-        }
-        validatorImpl.setSchema(this);
-
-        return validatorImpl;
-    }
-
     @Override
     public Implementation getValidator() {
         return validator;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAPlainAttrValidator.java
similarity index 96%
rename from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrValidator.java
rename to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAPlainAttrValidator.java
index 89ee1f8bda..1a66833a0f 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAPlainAttrValidator.java
@@ -22,7 +22,7 @@ import javax.validation.ConstraintValidatorContext;
 import org.apache.syncope.common.lib.types.EntityViolationType;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 
-public class PlainAttrValidator extends AbstractValidator<PlainAttrCheck, PlainAttr<?>> {
+public class JPAPlainAttrValidator extends AbstractValidator<PlainAttrCheck, PlainAttr<?>> {
 
     @Override
     public boolean isValid(final PlainAttr<?> attr, final ConstraintValidatorContext context) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
index 4ae6eb5740..5fe4554400 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
@@ -28,7 +28,7 @@ import javax.validation.Payload;
 
 @Target({ ElementType.TYPE })
 @Retention(RetentionPolicy.RUNTIME)
-@Constraint(validatedBy = PlainAttrValidator.class)
+@Constraint(validatedBy = JPAPlainAttrValidator.class)
 @Documented
 public @interface PlainAttrCheck {
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
index b2c4ceff53..8c68cdc608 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
@@ -36,6 +36,7 @@ import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.EntityViolationType;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -67,6 +68,9 @@ public class PlainAttrTest extends AbstractTest {
     @Autowired
     private AnyTypeClassDAO anyTypeClassDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Tag("plainAttrTable")
     @Test
     public void findByKey() {
@@ -98,15 +102,15 @@ public class PlainAttrTest extends AbstractTest {
 
         Exception thrown = null;
         try {
-            attr.add("john.doe@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
-            attr.add("mario.rossi@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attr.add(validator, "john.doe@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attr.add(validator, "mario.rossi@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
         assertNull(thrown);
 
         try {
-            attr.add("http://www.apache.org", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attr.add(validator, "http://www.apache.org", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
@@ -130,13 +134,13 @@ public class PlainAttrTest extends AbstractTest {
 
         Exception thrown = null;
         try {
-            attribute.add("A", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attribute.add(validator, "A", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
         assertNotNull(thrown);
 
-        attribute.add("M", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attribute.add(validator, "M", anyUtilsFactory.getInstance(AnyTypeKind.USER));
 
         InvalidEntityException iee = null;
         try {
@@ -225,7 +229,7 @@ public class PlainAttrTest extends AbstractTest {
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(obscureSchema);
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         userDAO.save(user);
@@ -255,7 +259,7 @@ public class PlainAttrTest extends AbstractTest {
 
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setSchema(obscureWithKeyAsSysprop);
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
 
         assertEquals(Encryptor.getInstance(obscureSchema.getSecretKey()).
                 encode("testvalue", obscureSchema.getCipherAlgorithm()), attr.getValues().get(0).getStringValue());
@@ -274,8 +278,8 @@ public class PlainAttrTest extends AbstractTest {
 
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setSchema(obscureWithDecodeConversionPattern);
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
-        
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+
         assertEquals(Encryptor.getInstance(obscureWithDecodeConversionPattern.getSecretKey()).
                 encode("testvalue", obscureWithDecodeConversionPattern.getCipherAlgorithm()),
                 attr.getValues().get(0).getStringValue());
@@ -302,7 +306,7 @@ public class PlainAttrTest extends AbstractTest {
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(photoSchema);
-        attr.add(photoB64Value, anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, photoB64Value, anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         userDAO.save(user);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
index a3ee8a9a49..67fd767e93 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
@@ -32,6 +32,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -76,6 +77,9 @@ public class AnySearchTest extends AbstractTest {
     @Autowired
     private PlainSchemaDAO plainSchemaDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void searchByDynMembership() {
         // 1. create role with dynamic membership
@@ -204,14 +208,14 @@ public class AnySearchTest extends AbstractTest {
         GPlainAttr title = entityFactory.newEntity(GPlainAttr.class);
         title.setOwner(group);
         title.setSchema(plainSchemaDAO.find("title"));
-        title.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        title.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(title);
 
         // unique
         GPlainAttr originalName = entityFactory.newEntity(GPlainAttr.class);
         originalName.setOwner(group);
         originalName.setSchema(plainSchemaDAO.find("originalName"));
-        originalName.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        originalName.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(originalName);
 
         groupDAO.save(group);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
index c9c1c7d159..c45a8329d3 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
@@ -35,6 +35,7 @@ import javax.persistence.Query;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -84,6 +85,9 @@ public class GroupTest extends AbstractTest {
     @Autowired
     private AnyTypeClassDAO anyTypeClassDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void saveWithTwoOwners() {
         assertThrows(InvalidEntityException.class, () -> {
@@ -205,7 +209,7 @@ public class GroupTest extends AbstractTest {
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("cool"));
-        attr.add("true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         user = userDAO.save(user);
@@ -304,7 +308,7 @@ public class GroupTest extends AbstractTest {
         APlainAttr attr = entityFactory.newEntity(APlainAttr.class);
         attr.setOwner(anyObject);
         attr.setSchema(plainSchemaDAO.find("model"));
-        attr.add("Canon MFC8030", anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
+        attr.add(validator, "Canon MFC8030", anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
         anyObject.add(attr);
 
         anyObject = anyObjectDAO.save(anyObject);
@@ -382,14 +386,14 @@ public class GroupTest extends AbstractTest {
         GPlainAttr title = entityFactory.newEntity(GPlainAttr.class);
         title.setOwner(group);
         title.setSchema(plainSchemaDAO.find("title"));
-        title.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        title.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(title);
 
         // unique
         GPlainAttr originalName = entityFactory.newEntity(GPlainAttr.class);
         originalName.setOwner(group);
         originalName.setSchema(plainSchemaDAO.find("originalName"));
-        originalName.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        originalName.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(originalName);
 
         groupDAO.save(group);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
index 402860d27b..b857573862 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
@@ -32,6 +32,7 @@ import java.util.Set;
 import javax.persistence.Query;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -71,6 +72,9 @@ public class RoleTest extends AbstractTest {
     @Autowired
     private DelegationDAO delegationDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     /**
      * Static copy of {@link org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO} method with same signature:
      * required for avoiding creating new transaction - good for general use case but bad for the way how
@@ -106,7 +110,7 @@ public class RoleTest extends AbstractTest {
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("cool"));
-        attr.add("true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         user = userDAO.save(user);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
index 8f5c75a8f5..46c35c87c1 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
@@ -32,6 +32,7 @@ import java.util.UUID;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
 import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
@@ -96,6 +97,9 @@ public class UserTest extends AbstractTest {
     @Autowired
     private RoleDAO roleDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void delete() {
         List<UMembership> memberships = groupDAO.findUMemberships(groupDAO.findByName("managingDirector"));
@@ -169,7 +173,7 @@ public class UserTest extends AbstractTest {
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         // add 'obscure' to user (via 'artDirector' membership): does not work because 'obscure' is from 'other'
@@ -183,7 +187,7 @@ public class UserTest extends AbstractTest {
         attr.setOwner(user);
         attr.setMembership(membership);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         try {
@@ -204,7 +208,7 @@ public class UserTest extends AbstractTest {
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         // add 'obscure' (via 'additional' membership): that group defines type extension with classes 'other' and 'csv'
@@ -217,7 +221,7 @@ public class UserTest extends AbstractTest {
         attr.setOwner(user);
         attr.setMembership(membership);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         userDAO.save(user);
@@ -260,7 +264,7 @@ public class UserTest extends AbstractTest {
         attr.setAccount(account);
         account.add(attr);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue", anyUtils);
+        attr.add(validator, "testvalue", anyUtils);
 
         user = userDAO.save(user);
         entityManager().flush();
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 0d41fc8f28..0acb0a151c 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -632,7 +632,7 @@ under the License.
                 connectorName="net.tirasa.connid.bundles.ldap.LdapConnector"
                 version="${connid.ldap.version}" 
                 jsonConf='[{"schema":{"name":"host","type":"java.lang.String","required":true,"order":1,"confidential":false,"defaultValues":[]},"values":["localhost"],"overridable":false},{"schema":{"name":"port","type":"int","required":false,"order":2,"confidential":false,"defaultValues":[389]},"values":[1389],"overridable":false},{"schema":{"name":"ssl","type":"boolean","required":false,"order":3,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"s [...]
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH"]'/>
   
   <ConnInstance id="a28abd9b-9f4a-4ef6-a7a8-d19ad2a8f29d" displayName="H2-test2"
                 adminRealm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
@@ -657,7 +657,7 @@ under the License.
                 connectorName="net.tirasa.connid.bundles.db.scriptedsql.ScriptedSQLConnector"
                 displayName="Scripted SQL" version="${connid.database.version}"
                 jsonConf='[{&quot;schema&quot;:{&quot;name&quot;:&quot;updateScriptFileName&quot;,&quot;displayName&quot;:&quot;updateScriptFileName&quot;,&quot;helpMessage&quot;:&quot;updateScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/UpdateScript.groovy&quot;]},{&quot;schem [...]
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH","SYNC"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH","SYNC"]'/>
   
   <ConnInstance id="44c02549-19c3-483c-8025-4919c3283c37" bundlename="net.tirasa.connid.bundles.rest"
                 adminRealm_id="0679e069-7355-4b20-bd11-a5a0a5453c7c"
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
index d03331d955..68cc8a56bc 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
@@ -236,7 +236,7 @@ public class ConnectorFacadeProxy implements Connector {
 
         Set<AttributeDelta> result = null;
 
-        if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) {
+        if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE_DELTA)) {
             propagationAttempted.set(true);
 
             Future<Set<AttributeDelta>> future = 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
index 36a44dfbbc..5554f89d87 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
@@ -34,6 +34,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.LogOutputStream;
 import org.apache.syncope.core.persistence.api.DomainHolder;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -624,7 +625,8 @@ public class ProvisioningContext {
             final NotificationManager notificationManager,
             final AuditManager auditManager,
             final TaskDataBinder taskDataBinder,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         return new PriorityPropagationTaskExecutor(
                 connectorManager,
@@ -642,6 +644,7 @@ public class ProvisioningContext {
                 taskUtilsFactory,
                 entityFactory,
                 outboundMatcher,
+                validator,
                 propagationTaskExecutorAsyncExecutor);
     }
 
@@ -837,7 +840,8 @@ public class ProvisioningContext {
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         return new AnyObjectDataBinderImpl(
                 anyTypeDAO,
@@ -857,7 +861,8 @@ public class ProvisioningContext {
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
     }
 
     @ConditionalOnMissingBean
@@ -995,7 +1000,8 @@ public class ProvisioningContext {
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         return new GroupDataBinderImpl(
                 anyTypeDAO,
@@ -1016,7 +1022,8 @@ public class ProvisioningContext {
                 mappingManager,
                 intAttrNameParser,
                 outboundMatcher,
-                searchCondVisitor);
+                searchCondVisitor,
+                validator);
     }
 
     @ConditionalOnMissingBean
@@ -1237,6 +1244,7 @@ public class ProvisioningContext {
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
             final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator,
             final RoleDAO roleDAO,
             final SecurityQuestionDAO securityQuestionDAO,
             final ApplicationDAO applicationDAO,
@@ -1263,6 +1271,7 @@ public class ProvisioningContext {
                 mappingManager,
                 intAttrNameParser,
                 outboundMatcher,
+                validator,
                 roleDAO,
                 securityQuestionDAO,
                 applicationDAO,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index 65d7100068..b728ec7759 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -47,6 +47,7 @@ import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AllowedSchemas;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
@@ -135,6 +136,8 @@ abstract class AbstractAnyDataBinder {
 
     protected final OutboundMatcher outboundMatcher;
 
+    protected final PlainAttrValidationManager validator;
+
     protected AbstractAnyDataBinder(
             final AnyTypeDAO anyTypeDAO,
             final RealmDAO realmDAO,
@@ -153,7 +156,8 @@ abstract class AbstractAnyDataBinder {
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         this.anyTypeDAO = anyTypeDAO;
         this.realmDAO = realmDAO;
@@ -173,6 +177,7 @@ abstract class AbstractAnyDataBinder {
         this.mappingManager = mappingManager;
         this.intAttrNameParser = intAttrNameParser;
         this.outboundMatcher = outboundMatcher;
+        this.validator = validator;
     }
 
     protected void setRealm(final Any<?> any, final AnyUR anyUR) {
@@ -260,7 +265,7 @@ abstract class AbstractAnyDataBinder {
                 LOG.debug("Null value for {}, ignoring", schema.getKey());
             } else {
                 try {
-                    attr.add(value, anyUtils);
+                    attr.add(validator, value, anyUtils);
                 } catch (InvalidPlainAttrValueException e) {
                     String valueToPrint = value.length() > 40
                             ? value.substring(0, 20) + "..."
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index 11f9b1d433..643f2e7c19 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -39,6 +39,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -93,7 +94,8 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         super(anyTypeDAO,
                 realmDAO,
@@ -112,7 +114,8 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
     }
 
     @Transactional(readOnly = true)
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index d33fd4e00f..b49e90a43b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -35,6 +35,7 @@ import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -96,7 +97,8 @@ public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupD
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
             final OutboundMatcher outboundMatcher,
-            final SearchCondVisitor searchCondVisitor) {
+            final SearchCondVisitor searchCondVisitor,
+            final PlainAttrValidationManager validator) {
 
         super(anyTypeDAO,
                 realmDAO,
@@ -115,7 +117,8 @@ public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupD
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
 
         this.searchCondVisitor = searchCondVisitor;
     }
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 d5b6554407..c7dad2ecec 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
@@ -28,7 +28,7 @@ import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
 import org.apache.syncope.core.persistence.api.dao.AccountRule;
 import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
@@ -138,7 +138,7 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                     break;
 
                 case IdRepoImplementationType.VALIDATOR:
-                    base = Validator.class;
+                    base = PlainAttrValueValidator.class;
                     break;
 
                 case IdRepoImplementationType.RECIPIENTS_PROVIDER:
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 c6010e891d..b86f460a3a 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
@@ -352,10 +352,10 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         resource.setProvisioningTraceLevel(resourceTO.getProvisioningTraceLevel());
 
         resource.setPasswordPolicy(resourceTO.getPasswordPolicy() == null
-                ? null : (PasswordPolicy) policyDAO.find(resourceTO.getPasswordPolicy()));
+                ? null : policyDAO.<PasswordPolicy>find(resourceTO.getPasswordPolicy()));
 
         resource.setAccountPolicy(resourceTO.getAccountPolicy() == null
-                ? null : (AccountPolicy) policyDAO.find(resourceTO.getAccountPolicy()));
+                ? null : policyDAO.<AccountPolicy>find(resourceTO.getAccountPolicy()));
 
         if (resource.getPropagationPolicy() != null
                 && !resource.getPropagationPolicy().getKey().equals(resourceTO.getPropagationPolicy())) {
@@ -363,13 +363,13 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
             propagationTaskExecutor.expireRetryTemplate(resource.getKey());
         }
         resource.setPropagationPolicy(resourceTO.getPropagationPolicy() == null
-                ? null : (PropagationPolicy) policyDAO.find(resourceTO.getPropagationPolicy()));
+                ? null : policyDAO.<PropagationPolicy>find(resourceTO.getPropagationPolicy()));
 
         resource.setPullPolicy(resourceTO.getPullPolicy() == null
-                ? null : (PullPolicy) policyDAO.find(resourceTO.getPullPolicy()));
+                ? null : policyDAO.<PullPolicy>find(resourceTO.getPullPolicy()));
 
         resource.setPushPolicy(resourceTO.getPushPolicy() == null
-                ? null : (PushPolicy) policyDAO.find(resourceTO.getPushPolicy()));
+                ? null : policyDAO.<PushPolicy>find(resourceTO.getPushPolicy()));
 
         if (resourceTO.getProvisionSorter() == null) {
             resource.setProvisionSorter(null);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 719c650cfb..15be775af5 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -48,6 +48,7 @@ import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
@@ -131,6 +132,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
             final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator,
             final RoleDAO roleDAO,
             final SecurityQuestionDAO securityQuestionDAO,
             final ApplicationDAO applicationDAO,
@@ -156,7 +158,8 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
 
         this.roleDAO = roleDAO;
         this.securityQuestionDAO = securityQuestionDAO;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
index 5705deb22a..f16b5d0a8a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
@@ -121,6 +121,8 @@ public class DefaultNotificationManager implements NotificationManager {
 
     protected final SearchCondVisitor searchCondVisitor;
 
+    protected Optional<RecipientsProvider> perContextRecipientsProvider = Optional.empty();
+
     public DefaultNotificationManager(
             final DerSchemaDAO derSchemaDAO,
             final VirSchemaDAO virSchemaDAO,
@@ -216,8 +218,11 @@ public class DefaultNotificationManager implements NotificationManager {
 
         if (notification.getRecipientsProvider() != null) {
             try {
-                RecipientsProvider recipientsProvider =
-                        ImplementationManager.build(notification.getRecipientsProvider());
+                RecipientsProvider recipientsProvider = ImplementationManager.build(
+                        notification.getRecipientsProvider(),
+                        () -> perContextRecipientsProvider.orElse(null),
+                        instance -> perContextRecipientsProvider = Optional.of(instance));
+
                 recipientEmails.addAll(recipientsProvider.provideRecipients(notification));
             } catch (Exception e) {
                 LOG.error("While building {}", notification.getRecipientsProvider(), e);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 2db1e6ece5..f4fcd7ab8e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -39,6 +40,7 @@ import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -127,6 +129,10 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
     protected final OutboundMatcher outboundMatcher;
 
+    protected final PlainAttrValidationManager validator;
+
+    protected final Map<String, PropagationActions> perContextActions = new ConcurrentHashMap<>();
+
     public AbstractPropagationTaskExecutor(
             final ConnectorManager connectorManager,
             final ConnObjectUtils connObjectUtils,
@@ -142,7 +148,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             final AnyUtilsFactory anyUtilsFactory,
             final TaskUtilsFactory taskUtilsFactory,
             final EntityFactory entityFactory,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         this.connectorManager = connectorManager;
         this.connObjectUtils = connObjectUtils;
@@ -159,6 +166,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         this.taskUtilsFactory = taskUtilsFactory;
         this.entityFactory = entityFactory;
         this.outboundMatcher = outboundMatcher;
+        this.validator = validator;
     }
 
     @Override
@@ -171,7 +179,10 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
         resource.getPropagationActions().forEach(impl -> {
             try {
-                result.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.error("While building {}", impl, e);
             }
@@ -193,7 +204,10 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         taskInfo.getResource().getProvision(taskInfo.getAnyType()).
                 filter(provision -> provision.getUidOnCreate() != null).
                 ifPresent(provision -> anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind()).addAttr(
-                taskInfo.getEntityKey(), plainSchemaDAO.find(provision.getUidOnCreate()), result.getUidValue()));
+                validator,
+                taskInfo.getEntityKey(),
+                plainSchemaDAO.find(provision.getUidOnCreate()),
+                result.getUidValue()));
 
         return result;
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java
index 31fec8e831..c90a0e9c2f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java
@@ -24,6 +24,8 @@ import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationData;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+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.AttributeUtil;
 import org.identityconnectors.framework.common.objects.Name;
@@ -37,6 +39,7 @@ import org.springframework.transaction.annotation.Transactional;
  *
  * It ensures to send the configured e-mail address as {@code __NAME__}.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class AzurePropagationActions implements PropagationActions {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AzurePropagationActions.class);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
index a0e154f31c..e36a73ee45 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
@@ -30,6 +30,8 @@ import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.common.security.GuardedString;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
@@ -43,6 +45,7 @@ import org.springframework.transaction.annotation.Transactional;
  * added a password. The CipherAlgorithm associated with the password must match the password
  * cipher algorithm property of the DB Connector.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class DBPasswordPropagationActions implements PropagationActions {
 
     protected static final String CLEARTEXT = "CLEARTEXT";
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
index 39ede6a0b1..6bb431bc78 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
@@ -18,9 +18,6 @@
  */
 package org.apache.syncope.core.provisioning.java.propagation;
 
-import static org.apache.syncope.core.provisioning.api.propagation.PropagationManager.MANDATORY_MISSING_ATTR_NAME;
-import static org.apache.syncope.core.provisioning.api.propagation.PropagationManager.MANDATORY_NULL_OR_EMPTY_ATTR_NAME;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java
index 846994d8bb..d1a869fdbe 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java
@@ -25,6 +25,8 @@ import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationData;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+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.AttributeUtil;
 import org.identityconnectors.framework.common.objects.Name;
@@ -38,6 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
  *
  * It ensures to send the configured e-mail address as {@code __NAME__}.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class GoogleAppsPropagationActions implements PropagationActions {
 
     protected static final Logger LOG = LoggerFactory.getLogger(GoogleAppsPropagationActions.class);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
index bb590cc393..9cb4215b3f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
@@ -43,6 +43,8 @@ import org.apache.syncope.core.provisioning.api.DerAttrHandler;
 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
 import org.identityconnectors.framework.common.objects.AttributeDeltaBuilder;
 import org.identityconnectors.framework.common.objects.AttributeDeltaUtil;
@@ -58,6 +60,7 @@ import org.springframework.transaction.annotation.Transactional;
  *
  * @see org.apache.syncope.core.provisioning.java.pushpull.LDAPMembershipPullActions
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class LDAPMembershipPropagationActions implements PropagationActions {
 
     protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPropagationActions.class);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java
index 76ab4355d4..eb3cee8806 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java
@@ -30,6 +30,8 @@ import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.common.security.GuardedString;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
@@ -43,6 +45,7 @@ import org.springframework.transaction.annotation.Transactional;
  * added a password. The CipherAlgorithm associated with the password must match the password
  * hash algorithm property of the LDAP Connector.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class LDAPPasswordPropagationActions implements PropagationActions {
 
     private static final String CLEARTEXT = "CLEARTEXT";
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
index 5daf4bf5f3..5fb86aa96d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
@@ -28,6 +28,7 @@ import java.util.concurrent.ExecutorCompletionService;
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.types.ExecStatus;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -102,6 +103,7 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
             final TaskUtilsFactory taskUtilsFactory,
             final EntityFactory entityFactory,
             final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator,
             final ThreadPoolTaskExecutor taskExecutor) {
 
         super(connectorManager,
@@ -118,7 +120,8 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
                 anyUtilsFactory,
                 taskUtilsFactory,
                 entityFactory,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
         this.taskExecutor = taskExecutor;
     }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
index fe113c6558..c429d09c12 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.to.Mapping;
 import org.apache.syncope.common.lib.to.Provision;
@@ -36,8 +37,10 @@ import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.ConnectorManager;
+import org.apache.syncope.core.provisioning.api.ProvisionSorter;
 import org.apache.syncope.core.provisioning.java.job.AbstractSchedTaskJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
+import org.apache.syncope.core.spring.ImplementationManager;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -105,6 +108,26 @@ public abstract class AbstractProvisioningJobDelegate<T extends ProvisioningTask
     @Autowired
     protected PolicyDAO policyDAO;
 
+    protected Optional<ProvisionSorter> perContextProvisionSorter = Optional.empty();
+
+    protected ProvisionSorter getProvisionSorter(final T task) {
+        if (task.getResource().getProvisionSorter() != null) {
+            try {
+                return ImplementationManager.build(
+                        task.getResource().getProvisionSorter(),
+                        () -> perContextProvisionSorter.orElse(null),
+                        instance -> perContextProvisionSorter = Optional.of(instance));
+            } catch (Exception e) {
+                LOG.error("While building {}", task.getResource().getProvisionSorter(), e);
+            }
+        }
+
+        if (perContextProvisionSorter.isEmpty()) {
+            perContextProvisionSorter = Optional.of(new DefaultProvisionSorter());
+        }
+        return perContextProvisionSorter.get();
+    }
+
     /**
      * Create a textual report of the provisioning operation, based on the trace level.
      *
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
index 8ca7faeb21..dd5af11ef2 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
@@ -21,8 +21,10 @@ package org.apache.syncope.core.provisioning.java.pushpull;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.Item;
@@ -106,6 +108,8 @@ public class InboundMatcher {
 
     protected final AnyUtilsFactory anyUtilsFactory;
 
+    protected final Map<String, PullCorrelationRule> perContextPullCorrelationRules = new ConcurrentHashMap<>();
+
     public InboundMatcher(
             final UserDAO userDAO,
             final AnyObjectDAO anyObjectDAO,
@@ -130,13 +134,6 @@ public class InboundMatcher {
         this.anyUtilsFactory = anyUtilsFactory;
     }
 
-    protected List<Implementation> getTransformers(final Item item) {
-        return item.getTransformers().stream().
-                map(implementationDAO::find).
-                filter(Objects::nonNull).
-                collect(Collectors.toList());
-    }
-
     public Optional<PullMatch> match(
             final AnyType anyType,
             final String nameValue,
@@ -212,6 +209,13 @@ public class InboundMatcher {
         return result;
     }
 
+    protected List<Implementation> getTransformers(final Item item) {
+        return item.getTransformers().stream().
+                map(implementationDAO::find).
+                filter(Objects::nonNull).
+                collect(Collectors.toList());
+    }
+
     public List<PullMatch> matchByConnObjectKeyValue(
             final Item connObjectKeyItem,
             final String connObjectKeyValue,
@@ -353,6 +357,27 @@ public class InboundMatcher {
         return result;
     }
 
+    protected Optional<PullCorrelationRule> rule(final ExternalResource resource, final Provision provision) {
+        Optional<? extends PullCorrelationRuleEntity> correlationRule = resource.getPullPolicy() == null
+                ? Optional.empty()
+                : resource.getPullPolicy().getCorrelationRule(provision.getAnyType());
+
+        Optional<PullCorrelationRule> rule = Optional.empty();
+        if (correlationRule.isPresent()) {
+            Implementation impl = correlationRule.get().getImplementation();
+            try {
+                rule = ImplementationManager.buildPullCorrelationRule(
+                        impl,
+                        () -> perContextPullCorrelationRules.get(impl.getKey()),
+                        instance -> perContextPullCorrelationRules.put(impl.getKey(), instance));
+            } catch (Exception e) {
+                LOG.error("While building {}", impl, e);
+            }
+        }
+
+        return rule;
+    }
+
     /**
      * Finds internal entities based on external attributes and mapping.
      *
@@ -368,18 +393,7 @@ public class InboundMatcher {
             final Provision provision,
             final AnyTypeKind anyTypeKind) {
 
-        Optional<? extends PullCorrelationRuleEntity> correlationRule = resource.getPullPolicy() == null
-                ? Optional.empty()
-                : resource.getPullPolicy().getCorrelationRule(provision.getAnyType());
-
-        Optional<PullCorrelationRule> rule = Optional.empty();
-        if (correlationRule.isPresent()) {
-            try {
-                rule = ImplementationManager.buildPullCorrelationRule(correlationRule.get().getImplementation());
-            } catch (Exception e) {
-                LOG.error("While building {}", correlationRule.get().getImplementation(), e);
-            }
-        }
+        Optional<PullCorrelationRule> rule = rule(resource, provision);
 
         List<PullMatch> result = List.of();
         try {
@@ -470,7 +484,7 @@ public class InboundMatcher {
 
             case "name":
                 if (orgUnit.isIgnoreCaseMatch()) {
-                    final String realmName = connObjectKey;
+                    String realmName = connObjectKey;
                     result.addAll(realmDAO.findAll().stream().
                             filter(r -> r.getName().equalsIgnoreCase(realmName)).collect(Collectors.toList()));
                 } else {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
index 89da2ee27c..2c78a12d15 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
@@ -22,8 +22,10 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.syncope.common.lib.to.Item;
@@ -35,6 +37,7 @@ import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
 import org.apache.syncope.core.provisioning.api.Connector;
@@ -69,6 +72,10 @@ public class OutboundMatcher {
 
     protected final VirAttrHandler virAttrHandler;
 
+    protected final Map<String, PropagationActions> perContextActions = new ConcurrentHashMap<>();
+
+    protected final Map<String, PushCorrelationRule> perContextPushCorrelationRules = new ConcurrentHashMap<>();
+
     public OutboundMatcher(
             final MappingManager mappingManager,
             final UserDAO userDAO,
@@ -90,10 +97,14 @@ public class OutboundMatcher {
 
         Optional<PushCorrelationRule> rule = Optional.empty();
         if (correlationRule.isPresent()) {
+            Implementation impl = correlationRule.get().getImplementation();
             try {
-                rule = ImplementationManager.buildPushCorrelationRule(correlationRule.get().getImplementation());
+                rule = ImplementationManager.buildPushCorrelationRule(
+                        impl,
+                        () -> perContextPushCorrelationRules.get(impl.getKey()),
+                        instance -> perContextPushCorrelationRules.put(impl.getKey(), instance));
             } catch (Exception e) {
-                LOG.error("While building {}", correlationRule.get().getImplementation(), e);
+                LOG.error("While building {}", impl, e);
             }
         }
 
@@ -160,6 +171,23 @@ public class OutboundMatcher {
         return result;
     }
 
+    protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
+        List<PropagationActions> result = new ArrayList<>();
+
+        resource.getPropagationActions().forEach(impl -> {
+            try {
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
+            } catch (Exception e) {
+                LOG.error("While building {}", impl, e);
+            }
+        });
+
+        return result;
+    }
+
     @Transactional(readOnly = true)
     public List<ConnectorObject> match(
             final Connector connector,
@@ -169,19 +197,11 @@ public class OutboundMatcher {
             final Optional<String[]> moreAttrsToGet,
             final Item... linkingItems) {
 
-        Set<String> matgFromPropagationActions = new HashSet<>();
-        resource.getPropagationActions().forEach(impl -> {
-            try {
-                matgFromPropagationActions.addAll(
-                        ImplementationManager.<PropagationActions>build(impl).
-                                moreAttrsToGet(Optional.empty(), provision));
-            } catch (Exception e) {
-                LOG.error("While building {}", impl, e);
-            }
-        });
+        Stream<String> matgFromPropagationActions = getPropagationActions(resource).stream().
+                flatMap(a -> a.moreAttrsToGet(Optional.empty(), provision).stream());
         Optional<String[]> effectiveMATG = Optional.of(Stream.concat(
                 moreAttrsToGet.stream().flatMap(Stream::of),
-                matgFromPropagationActions.stream()).toArray(String[]::new));
+                matgFromPropagationActions).toArray(String[]::new));
 
         Optional<PushCorrelationRule> rule = rule(resource, provision);
 
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 8dd27604a2..f077b2c499 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.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.StringUtils;
@@ -34,6 +35,7 @@ import org.apache.syncope.common.lib.to.OrgUnit;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -42,6 +44,7 @@ import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
@@ -87,11 +90,18 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
     @Autowired
     protected AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    protected PlainAttrValidationManager validator;
+
     protected final Map<String, SyncToken> latestSyncTokens = new HashMap<>();
 
+    protected ProvisioningProfile<PullTask, PullActions> profile;
+
     protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
 
-    protected ProvisioningProfile<PullTask, PullActions> profile;
+    protected final Map<String, PullActions> perContextActions = new ConcurrentHashMap<>();
+
+    protected Optional<ReconFilterBuilder> perContextReconFilterBuilder = Optional.empty();
 
     @Override
     public void setLatestSyncToken(final String objectClass, final SyncToken latestSyncToken) {
@@ -165,22 +175,28 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         });
     }
 
-    protected List<PullActions> buildPullActions(final PullTask pullTask) {
-        List<PullActions> actions = new ArrayList<>();
-        pullTask.getActions().forEach(impl -> {
+    protected List<PullActions> getPullActions(final List<? extends Implementation> impls) {
+        List<PullActions> result = new ArrayList<>();
+
+        impls.forEach(impl -> {
             try {
-                actions.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.warn("While building {}", impl, e);
             }
         });
-        return actions;
-    }
 
-    protected ReconFilterBuilder buildReconFilterBuilder(final PullTask pullTask)
-            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        return result;
+    }
 
-        return ImplementationManager.build(pullTask.getReconFilterBuilder());
+    protected ReconFilterBuilder getReconFilterBuilder(final PullTask pullTask) throws ClassNotFoundException {
+        return ImplementationManager.build(
+                pullTask.getReconFilterBuilder(),
+                () -> perContextReconFilterBuilder.orElse(null),
+                instance -> perContextReconFilterBuilder = Optional.of(instance));
     }
 
     protected RealmPullResultHandler buildRealmHandler() {
@@ -217,10 +233,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
         LOG.debug("Executing pull on {}", pullTask.getResource());
 
-        List<PullActions> actions = buildPullActions(pullTask);
-
         profile = new ProvisioningProfile<>(connector, pullTask);
-        profile.getActions().addAll(actions);
+        profile.getActions().addAll(getPullActions(pullTask.getActions()));
         profile.setDryRun(dryRun);
         profile.setConflictResolutionAction(pullTask.getResource().getPullPolicy() == null
                 ? ConflictResolutionAction.IGNORE
@@ -230,7 +244,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         latestSyncTokens.clear();
 
         if (!profile.isDryRun()) {
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
         }
@@ -244,7 +258,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             OrgUnit orgUnit = pullTask.getResource().getOrgUnit();
 
             Set<String> moreAttrsToGet = new HashSet<>();
-            actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, orgUnit)));
+            profile.getActions().forEach(a -> moreAttrsToGet.addAll(a.moreAttrsToGet(profile, orgUnit)));
             OperationOptions options = MappingUtils.buildOperationOptions(
                     MappingUtils.getPullItems(orgUnit.getItems().stream()), moreAttrsToGet.toArray(String[]::new));
 
@@ -274,9 +288,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                         break;
 
                     case FILTERED_RECONCILIATION:
-                        connector.filteredReconciliation(
-                                new ObjectClass(orgUnit.getObjectClass()),
-                                buildReconFilterBuilder(pullTask),
+                        connector.filteredReconciliation(new ObjectClass(orgUnit.getObjectClass()),
+                                getReconFilterBuilder(pullTask),
                                 handler,
                                 options);
                         break;
@@ -295,14 +308,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         }
 
         // ...then provisions for any types
-        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);
-            }
-        }
+        ProvisionSorter provisionSorter = getProvisionSorter(pullTask);
 
         GroupPullResultHandler ghandler = buildGroupHandler();
         for (Provision provision : pullTask.getResource().getProvisions().stream().
@@ -332,7 +338,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
             try {
                 Set<String> moreAttrsToGet = new HashSet<>();
-                actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
+                profile.getActions().forEach(a -> moreAttrsToGet.addAll(a.moreAttrsToGet(profile, provision)));
                 Stream<Item> mapItems = Stream.concat(
                         MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
                         virSchemaDAO.find(pullTask.getResource().getKey(), anyType.getKey()).stream().
@@ -362,9 +368,8 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                         break;
 
                     case FILTERED_RECONCILIATION:
-                        connector.filteredReconciliation(
-                                new ObjectClass(provision.getObjectClass()),
-                                buildReconFilterBuilder(pullTask),
+                        connector.filteredReconciliation(new ObjectClass(provision.getObjectClass()),
+                                getReconFilterBuilder(pullTask),
                                 handler,
                                 options);
                         break;
@@ -385,7 +390,9 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                             && result.getOperation() == ResourceOperation.CREATE
                             && result.getAnyType().equals(provision.getAnyType())).
                             forEach(result -> anyUtils.addAttr(
-                            result.getKey(), plainSchemaDAO.find(provision.getUidOnCreate()), result.getUidValue()));
+                            validator,
+                            result.getKey(),
+                            plainSchemaDAO.find(provision.getUidOnCreate()), result.getUidValue()));
                 }
             } catch (Throwable t) {
                 throw new JobExecutionException("While pulling from connector", t);
@@ -398,7 +405,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         }
 
         if (!profile.isDryRun()) {
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index b6dcf6932d..18305af5bd 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.MutablePair;
@@ -38,6 +39,7 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
@@ -83,6 +85,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
     protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
 
+    protected final Map<String, PushActions> perContextActions = new ConcurrentHashMap<>();
+
     protected void reportHandled(final String anyType, final String key) {
         MutablePair<Integer, String> pair = handled.get(anyType);
         if (pair == null) {
@@ -151,6 +155,23 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
                 createBean(DefaultGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
     }
 
+    protected List<PushActions> getPushActions(final List<? extends Implementation> impls) {
+        List<PushActions> result = new ArrayList<>();
+
+        impls.forEach(impl -> {
+            try {
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
+            } catch (Exception e) {
+                LOG.warn("While building {}", impl, e);
+            }
+        });
+
+        return result;
+    }
+
     @Override
     protected String doExecuteProvisioning(
             final PushTask pushTask,
@@ -161,17 +182,8 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
         LOG.debug("Executing push on {}", pushTask.getResource());
 
-        List<PushActions> actions = new ArrayList<>();
-        pushTask.getActions().forEach(impl -> {
-            try {
-                actions.add(ImplementationManager.build(impl));
-            } catch (Exception e) {
-                LOG.warn("While building {}", impl, e);
-            }
-        });
-
         profile = new ProvisioningProfile<>(connector, pushTask);
-        profile.getActions().addAll(actions);
+        profile.getActions().addAll(getPushActions(pushTask.getActions()));
         profile.setDryRun(dryRun);
         profile.setConflictResolutionAction(pushTask.getResource().getPushPolicy() == null
                 ? ConflictResolutionAction.IGNORE
@@ -179,7 +191,7 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         profile.setExecutor(executor);
 
         if (!profile.isDryRun()) {
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
         }
@@ -208,14 +220,7 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         }
 
         // ...then provisions for any types
-        ProvisionSorter provisionSorter = new DefaultProvisionSorter();
-        if (pushTask.getResource().getProvisionSorter() != null) {
-            try {
-                provisionSorter = ImplementationManager.build(pushTask.getResource().getProvisionSorter());
-            } catch (Exception e) {
-                LOG.error("While building {}", pushTask.getResource().getProvisionSorter(), e);
-            }
-        }
+        ProvisionSorter provisionSorter = getProvisionSorter(pushTask);
 
         for (Provision provision : pushTask.getResource().getProvisions().stream().
                 filter(provision -> provision.getMapping() != null).sorted(provisionSorter).
@@ -269,7 +274,7 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         }
 
         if (!profile.isDryRun() && !interrupt) {
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
index 390dcbcd8d..a71163ec3d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -18,10 +18,11 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.Provision;
@@ -29,7 +30,6 @@ import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.PullMode;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
@@ -37,7 +37,6 @@ import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.task.AnyTemplatePullTask;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
@@ -50,7 +49,6 @@ import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandle
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecutor;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -75,20 +73,6 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
 
         LOG.debug("Executing pull on {}", resource);
 
-        List<PullActions> actions = new ArrayList<>();
-        pullTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PULL_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    actions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         try {
             PullTask pullTask = entityFactory.newEntity(PullTask.class);
             pullTask.setResource(resource);
@@ -125,10 +109,11 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
             profile = new ProvisioningProfile<>(connector, pullTask);
             profile.setDryRun(false);
             profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
-            profile.getActions().addAll(actions);
+            profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream().
+                    map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
             profile.setExecutor(executor);
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
 
@@ -154,7 +139,7 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
 
             // execute filtered pull
             Set<String> matg = new HashSet<>(moreAttrsToGet);
-            actions.forEach(action -> matg.addAll(action.moreAttrsToGet(profile, provision)));
+            profile.getActions().forEach(a -> matg.addAll(a.moreAttrsToGet(profile, provision)));
 
             Stream<Item> mapItems = Stream.concat(
                     MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
@@ -173,7 +158,7 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
                 LOG.error("While setting group owners", e);
             }
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
index 6c8685ec59..9e67fce043 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
@@ -18,20 +18,19 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.provisioning.api.Connector;
@@ -40,7 +39,6 @@ import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -49,7 +47,7 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
     @Autowired
     protected ImplementationDAO implementationDAO;
 
-    protected List<PushActions> before(
+    protected void before(
             final ExternalResource resource,
             final Connector connector,
             final PushTaskTO pushTaskTO,
@@ -57,20 +55,6 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
 
         LOG.debug("Executing push on {}", resource);
 
-        List<PushActions> actions = new ArrayList<>();
-        pushTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PUSH_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    actions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         PushTask pushTask = entityFactory.newEntity(PushTask.class);
         pushTask.setResource(resource);
         pushTask.setMatchingRule(pushTaskTO.getMatchingRule() == null
@@ -84,14 +68,13 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
 
         profile = new ProvisioningProfile<>(connector, pushTask);
         profile.setExecutor(executor);
-        profile.getActions().addAll(actions);
+        profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream().
+                map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
         profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
 
-        for (PushActions action : actions) {
+        for (PushActions action : profile.getActions()) {
             action.beforeAll(profile);
         }
-
-        return actions;
     }
 
     @Override
@@ -104,7 +87,7 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
             final String executor) throws JobExecutionException {
 
         try {
-            List<PushActions> actions = before(resource, connector, pushTaskTO, executor);
+            before(resource, connector, pushTaskTO, executor);
 
             AnyType anyType = anyTypeDAO.find(provision.getAnyType());
 
@@ -126,7 +109,7 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
 
             doHandle(List.of(any), handler, resource);
 
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
@@ -148,14 +131,14 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
             final String executor) throws JobExecutionException {
 
         try {
-            List<PushActions> actions = before(resource, connector, pushTaskTO, executor);
+            before(resource, connector, pushTaskTO, executor);
 
             UserPushResultHandler handler = buildUserHandler();
             handler.setProfile(profile);
 
             handler.handle(account, provision);
 
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
index b7cb57ac50..4d39209061 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
@@ -18,10 +18,11 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull.stream;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.Mapping;
@@ -52,7 +53,6 @@ import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandle
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
 import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.quartz.JobExecutionException;
@@ -168,20 +168,6 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr
 
         LOG.debug("Executing stream pull");
 
-        List<PullActions> actions = new ArrayList<>();
-        pullTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PULL_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    actions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         try {
             ExternalResource resource =
                     externalResource(anyType, keyColumn, columns, conflictResolutionAction, pullCorrelationRule);
@@ -202,9 +188,10 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr
             profile = new ProvisioningProfile<>(connector, pullTask);
             profile.setDryRun(false);
             profile.setConflictResolutionAction(conflictResolutionAction);
-            profile.getActions().addAll(actions);
+            profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream().
+                    map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
 
@@ -228,7 +215,7 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr
 
             // execute filtered pull
             Set<String> moreAttrsToGet = new HashSet<>();
-            actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
+            profile.getActions().forEach(a -> moreAttrsToGet.addAll(a.moreAttrsToGet(profile, provision)));
 
             Stream<Item> mapItems = Stream.concat(
                     MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
@@ -246,7 +233,7 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr
                 LOG.error("While setting group owners", e);
             }
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
index 14bf80104f..7c9c765f85 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
@@ -18,8 +18,9 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull.stream;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.Mapping;
 import org.apache.syncope.common.lib.to.Provision;
@@ -44,7 +45,6 @@ import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPushExecutor;
 import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -128,20 +128,6 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
 
         LOG.debug("Executing stream push as {}", executor);
 
-        List<PushActions> pushActions = new ArrayList<>();
-        pushTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PUSH_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    pushActions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         try {
             ExternalResource resource = externalResource(anyType, columns, propagationActions);
 
@@ -156,10 +142,11 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
 
             profile = new ProvisioningProfile<>(connector, pushTask);
             profile.setExecutor(executor);
-            profile.getActions().addAll(pushActions);
+            profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream().
+                    map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
             profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
 
-            for (PushActions action : pushActions) {
+            for (PushActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
 
@@ -181,7 +168,7 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
 
             doHandle(anys, handler, resource);
 
-            for (PushActions action : pushActions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
index bf48dc0a2a..46d49cbfdd 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
@@ -21,8 +21,10 @@ package org.apache.syncope.core.provisioning.java.utils;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -49,6 +51,8 @@ public final class MappingUtils {
 
     private static final Logger LOG = LoggerFactory.getLogger(MappingUtils.class);
 
+    private static final Map<String, ItemTransformer> PER_CONTEXT_ITEM_TRANSFORMERS = new ConcurrentHashMap<>();
+
     public static Optional<Item> getConnObjectKeyItem(final Provision provision) {
         Mapping mapping = null;
         if (provision != null) {
@@ -91,7 +95,10 @@ public final class MappingUtils {
         // Then other custom transformers
         transformers.forEach(impl -> {
             try {
-                result.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> PER_CONTEXT_ITEM_TRANSFORMERS.get(impl.getKey()),
+                        instance -> PER_CONTEXT_ITEM_TRANSFORMERS.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.error("While building {}", impl, e);
             }
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java
index 86f038a4b4..e15ed0c937 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java
@@ -30,6 +30,7 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -84,6 +85,9 @@ public class DefaultMappingManagerTest extends AbstractTest {
     @Autowired
     private EntityFactory entityFactory;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void prepareAttrsForUser() {
         User bellini = userDAO.findByUsername("bellini");
@@ -251,7 +255,7 @@ public class DefaultMappingManagerTest extends AbstractTest {
         UPlainAttr cool = entityFactory.newEntity(UPlainAttr.class);
         cool.setOwner(user);
         cool.setSchema(plainSchemaDAO.find("cool"));
-        cool.add("true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        cool.add(validator, "true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(cool);
 
         user = userDAO.save(user);
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/ImplementationManager.java b/core/spring/src/main/java/org/apache/syncope/core/spring/ImplementationManager.java
deleted file mode 100644
index 7b69fee180..0000000000
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/ImplementationManager.java
+++ /dev/null
@@ -1,250 +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.spring;
-
-import groovy.lang.GroovyClassLoader;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import org.apache.syncope.common.lib.policy.AccountRuleConf;
-import org.apache.syncope.common.lib.policy.PasswordRuleConf;
-import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
-import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
-import org.apache.syncope.common.lib.report.ReportletConf;
-import org.apache.syncope.core.persistence.api.ImplementationLookup;
-import org.apache.syncope.core.persistence.api.dao.AccountRule;
-import org.apache.syncope.core.persistence.api.dao.PasswordRule;
-import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
-import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
-import org.apache.syncope.core.persistence.api.dao.Reportlet;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
-import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.support.AbstractBeanDefinition;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-
-public final class ImplementationManager {
-
-    private static final Logger LOG = LoggerFactory.getLogger(ImplementationManager.class);
-
-    private static final GroovyClassLoader GROOVY_CLASSLOADER = new GroovyClassLoader();
-
-    private static final Map<String, Class<?>> CLASS_CACHE = Collections.synchronizedMap(new HashMap<>());
-
-    public static Optional<Reportlet> buildReportlet(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<Reportlet>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                ReportletConf reportletConf = POJOHelper.deserialize(impl.getBody(), ReportletConf.class);
-                Class<? extends Reportlet> reportletClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getReportletClass(reportletConf.getClass());
-
-                Reportlet reportlet = buildJavaWithConf(reportletClass);
-                if (reportlet == null) {
-                    LOG.warn("Could not find matching reportlet for {}", reportletConf.getClass());
-                } else {
-                    reportlet.setConf(reportletConf);
-                }
-
-                return Optional.ofNullable(reportlet);
-        }
-    }
-
-    public static Optional<AccountRule> buildAccountRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<AccountRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                AccountRuleConf ruleConf = POJOHelper.deserialize(impl.getBody(), AccountRuleConf.class);
-                Class<? extends AccountRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getAccountRuleClass(ruleConf.getClass());
-
-                AccountRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching account rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static Optional<PasswordRule> buildPasswordRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<PasswordRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                PasswordRuleConf ruleConf = POJOHelper.deserialize(impl.getBody(), PasswordRuleConf.class);
-                Class<? extends PasswordRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getPasswordRuleClass(ruleConf.getClass());
-
-                PasswordRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching password rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static Optional<PullCorrelationRule> buildPullCorrelationRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<PullCorrelationRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                PullCorrelationRuleConf ruleConf =
-                        POJOHelper.deserialize(impl.getBody(), PullCorrelationRuleConf.class);
-                Class<? extends PullCorrelationRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getPullCorrelationRuleClass(ruleConf.getClass());
-
-                PullCorrelationRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching pull correlation rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static Optional<PushCorrelationRule> buildPushCorrelationRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<PushCorrelationRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                PushCorrelationRuleConf ruleConf =
-                        POJOHelper.deserialize(impl.getBody(), PushCorrelationRuleConf.class);
-                Class<? extends PushCorrelationRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getPushCorrelationRuleClass(ruleConf.getClass());
-
-                PushCorrelationRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching push correlation rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static <T> T build(final Implementation impl)
-            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return ImplementationManager.<T>buildGroovy(impl);
-
-            case JAVA:
-            default:
-                return ImplementationManager.<T>buildJava(impl);
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T buildGroovy(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        Class<?> clazz;
-        if (CLASS_CACHE.containsKey(impl.getKey())) {
-            clazz = CLASS_CACHE.get(impl.getKey());
-        } else {
-            clazz = GROOVY_CLASSLOADER.parseClass(impl.getBody());
-            CLASS_CACHE.put(impl.getKey(), clazz);
-        }
-
-        return (T) ApplicationContextProvider.getBeanFactory().
-                createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T buildJava(final Implementation impl)
-            throws ClassNotFoundException {
-
-        Class<?> clazz;
-        if (CLASS_CACHE.containsKey(impl.getKey())) {
-            clazz = CLASS_CACHE.get(impl.getKey());
-        } else {
-            clazz = Class.forName(impl.getBody());
-            CLASS_CACHE.put(impl.getKey(), clazz);
-        }
-
-        return (T) ApplicationContextProvider.getBeanFactory().
-                createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T buildJavaWithConf(final Class<T> clazz) {
-        if (clazz != null) {
-            String domainableBeanNameWithConf = AuthContextUtils.getDomain() + clazz.getName();
-            DefaultListableBeanFactory beanFactory = ApplicationContextProvider.getBeanFactory();
-
-            if (beanFactory.containsSingleton(domainableBeanNameWithConf)) {
-                return (T) beanFactory.getSingleton(domainableBeanNameWithConf);
-            }
-
-            synchronized (beanFactory.getSingletonMutex()) {
-                if (beanFactory.containsSingleton(domainableBeanNameWithConf)) {
-                    return (T) beanFactory.getSingleton(domainableBeanNameWithConf);
-                } else {
-                    T bean = (T) beanFactory.createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-                    beanFactory.registerSingleton(domainableBeanNameWithConf, bean);
-                    return bean;
-                }
-            }
-        }
-        return null;
-    }
-
-    public static Class<?> purge(final String implementation) {
-        return CLASS_CACHE.remove(implementation);
-    }
-
-    private ImplementationManager() {
-        // private constructor for static utility class
-    }
-}
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
new file mode 100644
index 0000000000..9ca5410ce5
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
@@ -0,0 +1,252 @@
+/*
+ * 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.spring;
+
+import groovy.lang.GroovyClassLoader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.policy.AccountRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
+import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.dao.AccountRule;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
+import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.Reportlet;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+
+public final class ImplementationManager {
+
+    private static final GroovyClassLoader GROOVY_CLASSLOADER = new GroovyClassLoader();
+
+    private static final Map<String, Class<?>> CLASS_CACHE = Collections.synchronizedMap(new HashMap<>());
+
+    public static Optional<Reportlet> buildReportlet(final Implementation impl) throws ClassNotFoundException {
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl));
+
+            case JAVA:
+            default:
+                ReportletConf conf = POJOHelper.deserialize(impl.getBody(), ReportletConf.class);
+                Class<? extends Reportlet> clazz = ApplicationContextProvider.getApplicationContext().
+                        getBean(ImplementationLookup.class).getReportletClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                Reportlet reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
+                        createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+                reportlet.setConf(conf);
+                return Optional.of(reportlet);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<AccountRule> buildAccountRule(
+            final Implementation impl,
+            final Supplier<AccountRule> cacheGetter,
+            final Consumer<AccountRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                AccountRuleConf conf = POJOHelper.deserialize(impl.getBody(), AccountRuleConf.class);
+                Class<AccountRule> clazz = (Class<AccountRule>) ApplicationContextProvider.getApplicationContext().
+                        getBean(ImplementationLookup.class).getAccountRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                AccountRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<PasswordRule> buildPasswordRule(
+            final Implementation impl,
+            final Supplier<PasswordRule> cacheGetter,
+            final Consumer<PasswordRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                PasswordRuleConf conf = POJOHelper.deserialize(impl.getBody(), PasswordRuleConf.class);
+                Class<PasswordRule> clazz = (Class<PasswordRule>) ApplicationContextProvider.getApplicationContext().
+                        getBean(ImplementationLookup.class).getPasswordRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                PasswordRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<PullCorrelationRule> buildPullCorrelationRule(
+            final Implementation impl,
+            final Supplier<PullCorrelationRule> cacheGetter,
+            final Consumer<PullCorrelationRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                PullCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PullCorrelationRuleConf.class);
+                Class<PullCorrelationRule> clazz =
+                        (Class<PullCorrelationRule>) ApplicationContextProvider.getApplicationContext().
+                                getBean(ImplementationLookup.class).getPullCorrelationRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                PullCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<PushCorrelationRule> buildPushCorrelationRule(
+            final Implementation impl,
+            final Supplier<PushCorrelationRule> cacheGetter,
+            final Consumer<PushCorrelationRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                PushCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PushCorrelationRuleConf.class);
+                Class<PushCorrelationRule> clazz =
+                        (Class<PushCorrelationRule>) ApplicationContextProvider.getApplicationContext().
+                                getBean(ImplementationLookup.class).getPushCorrelationRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                PushCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> Pair<Class<T>, Boolean> getClass(final Implementation impl) throws ClassNotFoundException {
+        if (CLASS_CACHE.containsKey(impl.getKey())) {
+            return Pair.of((Class<T>) CLASS_CACHE.get(impl.getKey()), true);
+        }
+
+        Class<?> clazz;
+        switch (impl.getEngine()) {
+            case GROOVY:
+                clazz = GROOVY_CLASSLOADER.parseClass(impl.getBody());
+                break;
+
+            case JAVA:
+            default:
+                clazz = Class.forName(impl.getBody());
+        }
+
+        CLASS_CACHE.put(impl.getKey(), clazz);
+        return Pair.of((Class<T>) clazz, false);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T build(final Implementation impl) throws ClassNotFoundException {
+        return (T) ApplicationContextProvider.getBeanFactory().
+                createBean(getClass(impl).getLeft(), AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T build(
+            final Class<T> clazz,
+            final boolean classCached,
+            final Supplier<T> cacheGetter,
+            final Consumer<T> cachePutter) {
+
+        boolean perContext = Optional.ofNullable(clazz.getAnnotation(SyncopeImplementation.class)).
+                map(ann -> ann.scope() == InstanceScope.PER_CONTEXT).
+                orElse(true);
+        T instance = null;
+        if (perContext && classCached) {
+            instance = cacheGetter.get();
+        }
+        if (instance == null) {
+            instance = (T) ApplicationContextProvider.getBeanFactory().
+                    createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+
+            if (perContext) {
+                cachePutter.accept(instance);
+            }
+        }
+
+        return instance;
+    }
+
+    public static <T> T build(final Implementation impl, final Supplier<T> cacheGetter, final Consumer<T> cachePutter)
+            throws ClassNotFoundException {
+
+        Pair<Class<T>, Boolean> clazz = getClass(impl);
+
+        return build(clazz.getLeft(), clazz.getRight(), cacheGetter, cachePutter);
+    }
+
+    public static Class<?> purge(final String implementation) {
+        return CLASS_CACHE.remove(implementation);
+    }
+
+    private ImplementationManager() {
+        // private constructor for static utility class
+    }
+}
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/InstanceScope.java
similarity index 65%
copy from common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
copy to core/spring/src/main/java/org/apache/syncope/core/spring/implementation/InstanceScope.java
index 1abf35f88a..fb76a83303 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/InstanceScope.java
@@ -16,18 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.core.spring.implementation;
 
-/**
- * Enum of all possible capabilities that a connector instance can expose.
- */
-public enum ConnectorCapability {
-
-    AUTHENTICATE,
-    CREATE,
-    UPDATE,
-    DELETE,
-    SEARCH,
-    SYNC;
+public enum InstanceScope {
+    /**
+     * The declaring Implementation will be instantiated every time it is getting invoked.
+     */
+    PER_CALL,
+    /**
+     * The declaring Implementation will be instantiated once, by the time of first invocation; such instance will
+     * not be destroyed until the Spring Context gets refreshed or shut down.
+     */
+    PER_CONTEXT
 
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/SyncopeImplementation.java
similarity index 69%
copy from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
copy to core/spring/src/main/java/org/apache/syncope/core/spring/implementation/SyncopeImplementation.java
index 4ae6eb5740..30e8ee750c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/SyncopeImplementation.java
@@ -16,25 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.persistence.jpa.validation.entity;
+package org.apache.syncope.core.spring.implementation;
 
-import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import javax.validation.Constraint;
-import javax.validation.Payload;
 
 @Target({ ElementType.TYPE })
 @Retention(RetentionPolicy.RUNTIME)
-@Constraint(validatedBy = PlainAttrValidator.class)
-@Documented
-public @interface PlainAttrCheck {
+public @interface SyncopeImplementation {
 
-    String message() default "{org.apache.syncope.core.persistence.validation.attr}";
-
-    Class<?>[] groups() default {};
-
-    Class<? extends Payload>[] payload() default {};
+    InstanceScope scope() default InstanceScope.PER_CONTEXT;
 }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
index 3499bea17e..daa33514ef 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
@@ -20,9 +20,13 @@ package org.apache.syncope.core.spring.security;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.spring.policy.DefaultPasswordRule;
@@ -48,6 +52,8 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
 
     protected static final int MIN_LENGTH_IF_ZERO = 8;
 
+    protected final Map<String, PasswordRule> perContextPasswordRules = new ConcurrentHashMap<>();
+
     @Transactional(readOnly = true)
     @Override
     public String generate(final ExternalResource resource) {
@@ -60,21 +66,31 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
         return generate(policies);
     }
 
-    @Override
-    public String generate(final List<PasswordPolicy> policies) {
-        List<DefaultPasswordRuleConf> ruleConfs = new ArrayList<>();
+    protected List<PasswordRule> getPasswordRules(final PasswordPolicy policy) {
+        List<PasswordRule> result = new ArrayList<>();
 
-        policies.stream().forEach(policy -> policy.getRules().forEach(impl -> {
+        for (Implementation impl : policy.getRules()) {
             try {
-                ImplementationManager.buildPasswordRule(impl).ifPresent(rule -> {
-                    if (rule.getConf() instanceof DefaultPasswordRuleConf) {
-                        ruleConfs.add((DefaultPasswordRuleConf) rule.getConf());
-                    }
-                });
+                ImplementationManager.buildPasswordRule(
+                        impl,
+                        () -> perContextPasswordRules.get(impl.getKey()),
+                        instance -> perContextPasswordRules.put(impl.getKey(), instance)).
+                        ifPresent(result::add);
             } catch (Exception e) {
-                LOG.error("Invalid {}, ignoring...", impl, e);
+                LOG.warn("While building {}", impl, e);
             }
-        }));
+        }
+
+        return result;
+    }
+
+    @Override
+    public String generate(final List<PasswordPolicy> policies) {
+        List<DefaultPasswordRuleConf> ruleConfs = new ArrayList<>();
+
+        policies.stream().forEach(policy -> getPasswordRules(policy).stream().
+                filter(rule -> rule.getConf() instanceof DefaultPasswordRuleConf).
+                forEach(rule -> ruleConfs.add((DefaultPasswordRuleConf) rule.getConf())));
 
         return generate(merge(ruleConfs));
     }
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java b/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
index 7fe3fe99c5..3f3f0f8796 100644
--- a/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
@@ -51,8 +51,8 @@ public class ImplementationManagerTest {
         String body = POJOHelper.serialize(createBaseDefaultPasswordRuleConf());
 
         assertTimeoutPreemptively(Duration.ofSeconds(30), () -> {
-            TestImplementation implementation = new TestImplementation();
-            implementation.setBody(body);
+            TestImplementation impl = new TestImplementation();
+            impl.setBody(body);
             ReentrantLock lock = new ReentrantLock();
             lock.lock();
             AtomicInteger runningThreads = new AtomicInteger(0);
@@ -66,7 +66,11 @@ public class ImplementationManagerTest {
                             Thread.yield();
                         }
                         try {
-                            ImplementationManager.buildPasswordRule(implementation).orElseThrow();
+                            ImplementationManager.buildPasswordRule(
+                                    impl,
+                                    () -> null,
+                                    instance -> {
+                                    }).orElseThrow();
                         } catch (Exception e) {
                             errorMessages.add(e.getLocalizedMessage());
                             errorCount.incrementAndGet();
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
index 8fba443005..757fb34cee 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.jpa;
 
 import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
@@ -56,6 +57,7 @@ public class ElasticsearchPersistenceContext {
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
             final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator,
             final ElasticsearchClient client,
             final @Lazy ElasticsearchUtils elasticsearchUtils) {
 
@@ -68,6 +70,7 @@ public class ElasticsearchPersistenceContext {
                 schemaDAO,
                 entityFactory,
                 anyUtilsFactory,
+                validator,
                 client,
                 elasticsearchUtils);
     }
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
index d203491980..d70d75ef30 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
@@ -48,6 +48,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -116,10 +117,21 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
             final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator,
             final ElasticsearchClient client,
             final ElasticsearchUtils elasticsearchUtils) {
 
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, schemaDAO, entityFactory, anyUtilsFactory);
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
+
         this.client = client;
         this.elasticsearchUtils = elasticsearchUtils;
     }
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java
index 894e03c36e..67ddf4d2df 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java
@@ -25,7 +25,10 @@ import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class DateToDateItemTransformer implements ItemTransformer {
 
     @Override
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java
index 00a9152a34..03e135e703 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java
@@ -25,7 +25,10 @@ import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class DateToLongItemTransformer implements ItemTransformer {
 
     @Override