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 13:53:25 UTC

[syncope] 02/02: [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 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 89a3251b12ec5fea5facd058cc0ab26e3b492cff
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       |  17 +-
 .../init/ClassPathScanImplementationLookup.java    |   6 +-
 ...idator.java => PlainAttrValidationManager.java} |   6 +-
 ...Validator.java => PlainAttrValueValidator.java} |   6 +-
 .../core/persistence/api/entity/AnyUtils.java      |   3 +-
 .../core/persistence/api/entity/PlainAttr.java     |   6 +-
 .../entity/JPAJSONAttributableValidator.java       |   2 +-
 .../attrvalue/validation/AbstractValidator.java    |  17 +-
 .../attrvalue/validation/AlwaysTrueValidator.java  |   3 +-
 .../jpa/attrvalue/validation/BasicValidator.java   |   5 +-
 .../jpa/attrvalue/validation/BinaryValidator.java  |   3 +-
 .../DefaultPlainAttrValidationManager.java         |  62 +++++
 .../validation/EmailAddressValidator.java          |   3 +-
 .../jpa/attrvalue/validation/URLValidator.java     |   3 +-
 .../persistence/jpa/dao/AbstractAnySearchDAO.java  |   9 +-
 .../core/persistence/jpa/dao/JPAAnyMatchDAO.java   |  21 +-
 .../persistence/jpa/dao/JPAImplementationDAO.java  |   2 +-
 .../core/persistence/jpa/dao/JPAUserDAO.java       |  73 ++++--
 .../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/ConfTest.java       |   6 +-
 .../core/persistence/jpa/inner/PlainAttrTest.java  |  24 +-
 .../core/persistence/jpa/outer/AnySearchTest.java  |   8 +-
 .../core/persistence/jpa/outer/ConfTest.java       |   6 +-
 .../core/persistence/jpa/outer/GroupTest.java      |  12 +-
 .../core/persistence/jpa/outer/RoleTest.java       |   6 +-
 .../core/persistence/jpa/outer/UserTest.java       |  16 +-
 .../java/data/AbstractAnyDataBinder.java           |   6 +-
 .../java/data/ConfigurationDataBinderImpl.java     |   8 +-
 .../java/data/ImplementationDataBinderImpl.java    |   4 +-
 .../java/data/ResourceDataBinderImpl.java          |  10 +-
 .../core/provisioning/java/job/TaskJob.java        |   2 +-
 .../java/job/report/DefaultReportJobDelegate.java  |   2 +-
 .../notification/DefaultNotificationManager.java   |  11 +-
 .../AbstractPropagationTaskExecutor.java           |  12 +-
 .../java/propagation/AzurePropagationActions.java  |   3 +
 .../propagation/DBPasswordPropagationActions.java  |   3 +
 .../propagation/GoogleAppsPropagationActions.java  |   3 +
 .../LDAPMembershipPropagationActions.java          |   3 +
 .../LDAPPasswordPropagationActions.java            |   3 +
 .../pushpull/AbstractProvisioningJobDelegate.java  |  24 ++
 .../provisioning/java/pushpull/InboundMatcher.java |  39 +++-
 .../java/pushpull/OutboundMatcher.java             |  49 ++--
 .../java/pushpull/PullJobDelegate.java             |  64 +++---
 .../java/pushpull/PushJobDelegate.java             |  55 ++---
 .../java/pushpull/SinglePullJobDelegate.java       |  29 +--
 .../java/pushpull/SinglePushJobDelegate.java       |  37 +--
 .../pushpull/stream/StreamPullJobDelegate.java     |  33 +--
 .../pushpull/stream/StreamPushJobDelegate.java     |  25 +-
 .../core/provisioning/java/utils/MappingUtils.java |  11 +-
 .../provisioning/java/MappingManagerImplTest.java  |   4 +
 .../syncope/core/spring/ImplementationManager.java | 251 ---------------------
 .../implementation/ImplementationManager.java      | 251 +++++++++++++++++++++
 .../core/spring/implementation/InstanceScope.java  |  26 +--
 .../implementation/SyncopeImplementation.java}     |  16 +-
 .../spring/security/DefaultPasswordGenerator.java  |  42 ++--
 .../core/spring/ImplementationManagerTest.java     |  13 +-
 .../core/reference/DateToDateItemTransformer.java  |   3 +
 .../core/reference/DateToLongItemTransformer.java  |   3 +
 63 files changed, 802 insertions(+), 620 deletions(-)

diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
index 41b1e4a27f..bd3b11f22f 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
@@ -29,6 +29,7 @@ public enum ConnectorCapability {
     AUTHENTICATE,
     CREATE,
     UPDATE,
+    UPDATE_DELTA,
     DELETE,
     SEARCH,
     SYNC;
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
index 5e73e52a2c..154090479d 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
+++ b/core/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.patch.AnyPatch;
@@ -37,7 +39,7 @@ import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.provisioning.api.LogicActions;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
 public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> extends AbstractResourceAssociator<TO> {
@@ -51,18 +53,23 @@ public abstract class AbstractAnyLogic<TO extends AnyTO, P extends AnyPatch> ext
     @Autowired
     private TemplateUtils templateUtils;
 
-    private List<LogicActions> getActions(final Realm realm) {
-        List<LogicActions> actions = new ArrayList<>();
+    protected final Map<String, LogicActions> perContextActions = new ConcurrentHashMap<>();
+
+    protected List<LogicActions> getActions(final Realm realm) {
+        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;
     }
 
     protected Pair<TO, List<LogicActions>> beforeCreate(final TO input) {
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index f206b15394..9053ae5357 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -34,7 +34,7 @@ import org.apache.syncope.common.lib.types.ImplementationType;
 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;
@@ -134,7 +134,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
         scanner.addIncludeFilter(new AssignableTypeFilter(PropagationActions.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(PullActions.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(PushActions.class));
-        scanner.addIncludeFilter(new AssignableTypeFilter(Validator.class));
+        scanner.addIncludeFilter(new AssignableTypeFilter(PlainAttrValueValidator.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(RecipientsProvider.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(AuditAppender.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(ProvisionSorter.class));
@@ -234,7 +234,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
                     classNames.get(ImplementationType.PUSH_ACTIONS).add(bd.getBeanClassName());
                 }
 
-                if (Validator.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                if (PlainAttrValueValidator.class.isAssignableFrom(clazz) && !isAbstractClazz) {
                     classNames.get(ImplementationType.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 90b3c96ff0..b2ea89cf49 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
@@ -23,6 +23,7 @@ import java.util.Set;
 import org.apache.syncope.common.lib.patch.AnyPatch;
 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;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 
@@ -56,5 +57,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/validation/entity/JPAJSONAttributableValidator.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
index b8d1a08ceb..8b09663d22 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/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 fc64f03f30..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 b32f91c684..2fa44704c8 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..6f7bfd2833
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/DefaultPlainAttrValidationManager.java
@@ -0,0 +1,62 @@
+/*
+ * 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.implementation.ImplementationManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Component
+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 1ee4126a33..6998ffbd22 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
@@ -36,6 +36,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;
@@ -61,7 +62,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.beans.factory.annotation.Autowired;
 
 public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO {
@@ -96,6 +96,9 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
     @Autowired
     protected AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    protected PlainAttrValidationManager validator;
+
     protected SearchCond buildEffectiveCond(
             final SearchCond cond,
             final Set<String> dynRealmKeys,
@@ -190,7 +193,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());
@@ -252,7 +255,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 a1255d6376..c2458d4449 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.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
@@ -90,6 +91,9 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
     @Autowired
     private AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     /**
      * Verify if any matches the given search condition.
      *
@@ -164,14 +168,11 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
                 }
 
                 if (match == null) {
-                    Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
-                    if (anyCond.isPresent()) {
-                        match = matches(any, anyCond.get(), not);
-                    } else {
-                        match = cond.getLeaf(AttrCond.class).
-                                map(leaf -> matches(any, leaf, not)).
-                                orElse(null);
-                    }
+                    match = cond.getLeaf(AnyCond.class).
+                            map(value -> matches(any, value, not)).
+                            orElseGet(() -> cond.getLeaf(AttrCond.class).
+                            map(leaf -> matches(any, leaf, not)).
+                            orElse(null));
                 }
 
                 if (match == null) {
@@ -371,7 +372,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);
@@ -463,7 +464,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/JPAImplementationDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAImplementationDAO.java
index 9148fbb7cf..2a690ac569 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAImplementationDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAImplementationDAO.java
@@ -24,7 +24,7 @@ import org.apache.syncope.common.lib.types.ImplementationType;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
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 ce2168bcbb..2ca5f2553b 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.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
 import javax.persistence.NoResultException;
@@ -43,8 +44,10 @@ import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 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.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
@@ -69,7 +72,7 @@ import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Propagation;
@@ -100,6 +103,10 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
     @Resource(name = "anonymousUser")
     protected String anonymousUser;
 
+    protected final Map<String, AccountRule> perContextAccountRules = new ConcurrentHashMap<>();
+
+    protected final Map<String, PasswordRule> perContextPasswordRules = new ConcurrentHashMap<>();
+
     @Override
     protected AnyUtils init() {
         return anyUtilsFactory.getInstance(AnyTypeKind.USER);
@@ -311,6 +318,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) {
@@ -326,15 +369,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(account -> rule.enforce(account));
-                    });
-                }
+                    user.getLinkedAccounts().stream().
+                            filter(account -> account.getPassword() != null).
+                            forEach(rule::enforce);
+                });
 
                 boolean matching = false;
                 if (policy.getHistoryLength() > 0) {
@@ -406,15 +447,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(account -> rule.enforce(account));
-                        });
-                    }
+                        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 7bdcc552c5..734c24dd84 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
@@ -27,6 +27,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;
@@ -67,11 +68,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);
@@ -84,7 +85,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;
@@ -95,7 +96,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 6c3e7da74e..55ea7884bd 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
@@ -37,6 +37,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;
@@ -391,7 +392,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<>();
@@ -410,7 +416,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 e26f5e93eb..88c8f42523 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.ImplementationType;
-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 2613a2290e..8565a7c2eb 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
@@ -29,7 +29,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/ConfTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ConfTest.java
index 55f0b6b511..d6887a89c5 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ConfTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ConfTest.java
@@ -27,6 +27,7 @@ import java.util.Optional;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 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.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
@@ -49,6 +50,9 @@ public class ConfTest extends AbstractTest {
     @Autowired
     private PlainSchemaDAO plainSchemaDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void read() {
         Optional<? extends CPlainAttr> conf = confDAO.find("selfRegistration.allowed");
@@ -71,7 +75,7 @@ public class ConfTest extends AbstractTest {
         } else {
             attrValue = entityFactory.newEntity(CPlainAttrValue.class);
         }
-        newAttr.add(value, attrValue);
+        newAttr.add(validator, value, attrValue);
     }
 
     @Test
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 6059bd0989..bc297d6290 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
@@ -37,6 +37,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;
@@ -68,6 +69,9 @@ public class PlainAttrTest extends AbstractTest {
     @Autowired
     private AnyTypeClassDAO anyTypeClassDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Tag("plainAttrTable")
     @Test
     public void findByKey() {
@@ -99,15 +103,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;
         }
@@ -131,13 +135,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 {
@@ -226,7 +230,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);
@@ -256,7 +260,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());
@@ -275,8 +279,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());
@@ -303,7 +307,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 0f92bca3a8..13295200b0 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
@@ -31,6 +31,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.StandardEntitlement;
+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.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
@@ -75,6 +76,9 @@ public class AnySearchTest extends AbstractTest {
     @Autowired
     private PlainSchemaDAO plainSchemaDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void searchByDynMembership() {
         // 1. create role with dynamic membership
@@ -201,14 +205,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/ConfTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ConfTest.java
index fefa7ff4b5..48c2cbf91f 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ConfTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ConfTest.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.persistence.jpa.outer;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
@@ -42,6 +43,9 @@ public class ConfTest extends AbstractTest {
     @Autowired
     private PlainSchemaDAO plainSchemaDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     private void add(final CPlainAttr newAttr, final String value) {
         PlainAttrValue attrValue;
         if (newAttr.getSchema().isUniqueConstraint()) {
@@ -50,7 +54,7 @@ public class ConfTest extends AbstractTest {
         } else {
             attrValue = entityFactory.newEntity(CPlainAttrValue.class);
         }
-        newAttr.add(value, attrValue);
+        newAttr.add(validator, value, attrValue);
     }
 
     @Test
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 646714278c..8c3b510ae5 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, () -> {
@@ -202,7 +206,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);
@@ -303,7 +307,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);
@@ -381,14 +385,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 709ef48c91..d43bfe7a77 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
@@ -33,6 +33,7 @@ import java.util.List;
 import javax.persistence.Query;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
+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;
@@ -72,6 +73,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
@@ -107,7 +111,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 7f58bfc300..4e436ead63 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
@@ -33,6 +33,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;
@@ -97,6 +98,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"));
@@ -174,7 +178,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'
@@ -188,7 +192,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 {
@@ -209,7 +213,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'
@@ -222,7 +226,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);
@@ -264,7 +268,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();
@@ -404,7 +408,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);
 
         userDAO.save(user);
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 07bb31f01b..ac105d65e3 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
@@ -45,6 +45,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;
@@ -150,6 +151,9 @@ abstract class AbstractAnyDataBinder {
     @Autowired
     protected OutboundMatcher outboundMatcher;
 
+    @Autowired
+    protected PlainAttrValidationManager validator;
+
     protected void setRealm(final Any<?> any, final AnyPatch anyPatch) {
         if (anyPatch.getRealm() != null && StringUtils.isNotBlank(anyPatch.getRealm().getValue())) {
             Realm newRealm = realmDAO.findByFullPath(anyPatch.getRealm().getValue());
@@ -234,7 +238,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/ConfigurationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConfigurationDataBinderImpl.java
index 416cc9c0ad..e3bc95bbe1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConfigurationDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConfigurationDataBinderImpl.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.core.provisioning.java.data;
 
-import org.apache.syncope.core.provisioning.api.data.ConfigurationDataBinder;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -29,6 +28,7 @@ import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 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.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
@@ -37,6 +37,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttrValue;
+import org.apache.syncope.core.provisioning.api.data.ConfigurationDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -46,6 +47,9 @@ public class ConfigurationDataBinderImpl extends AbstractAnyDataBinder implement
     @Autowired
     private ConfDAO confDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Override
     public List<AttrTO> getConfTO() {
         return confDAO.get().getPlainAttrs().stream().map(attr -> getAttrTO(attr)).collect(Collectors.toList());
@@ -99,7 +103,7 @@ public class ConfigurationDataBinderImpl extends AbstractAnyDataBinder implement
                         attrValue = entityFactory.newEntity(CPlainAttrValue.class);
                     }
 
-                    attr.add(value, attrValue);
+                    attr.add(validator, value, attrValue);
                 } catch (InvalidPlainAttrValueException e) {
                     LOG.warn("Invalid value for attribute " + schema.getKey() + ": " + value, e);
 
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 57b97e6ce3..a561be8f18 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
@@ -27,7 +27,7 @@ import org.apache.syncope.common.lib.to.ImplementationTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.ImplementationType;
-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.Reportlet;
@@ -137,7 +137,7 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                     break;
 
                 case VALIDATOR:
-                    base = Validator.class;
+                    base = PlainAttrValueValidator.class;
                     break;
 
                 case 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 dd7773d654..27662bcf77 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
@@ -349,10 +349,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())) {
@@ -360,13 +360,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/job/TaskJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
index 962edc40d3..61fb5f530f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
@@ -27,7 +27,7 @@ import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
index e4050b7ea2..d25bbdc76c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/DefaultReportJobDelegate.java
@@ -43,8 +43,8 @@ import org.apache.syncope.core.persistence.api.dao.Reportlet;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Report;
 import org.apache.syncope.core.persistence.api.entity.ReportExec;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.provisioning.api.job.report.ReportJobDelegate;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.quartz.JobExecutionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
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 99dbde36b4..eb33975302 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
@@ -77,7 +77,7 @@ import org.apache.syncope.core.provisioning.api.notification.NotificationManager
 import org.apache.syncope.core.provisioning.api.notification.RecipientsProvider;
 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -163,6 +163,8 @@ public class DefaultNotificationManager implements NotificationManager {
     @Autowired
     private SearchCondVisitor searchCondVisitor;
 
+    private Optional<RecipientsProvider> perContextRecipientsProvider = Optional.empty();
+
     @Transactional(readOnly = true)
     @Override
     public long getMaxRetries() {
@@ -218,8 +220,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 f0845383b5..78dbfe3714 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
@@ -38,6 +38,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.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
@@ -67,7 +68,7 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.identityconnectors.framework.common.exceptions.ConnectorException;
 import org.identityconnectors.framework.common.objects.Attribute;
@@ -165,6 +166,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     @Autowired
     protected OutboundMatcher outboundMatcher;
 
+    @Autowired
+    protected PlainAttrValidationManager validator;
+
     @Override
     public void expireRetryTemplate(final String resource) {
         retryTemplates.remove(resource);
@@ -223,8 +227,10 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
             task.getResource().getProvision(task.getAnyType()).ifPresent(provision -> {
                 if (provision.getUidOnCreate() != null) {
-                    anyUtilsFactory.getInstance(task.getAnyTypeKind()).
-                            addAttr(task.getEntityKey(), provision.getUidOnCreate(), result.getUidValue());
+                    anyUtilsFactory.getInstance(task.getAnyTypeKind()).addAttr(validator,
+                            task.getEntityKey(),
+                            provision.getUidOnCreate(),
+                            result.getUidValue());
                 }
             });
         } else {
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 2eeac67d67..b03e5ed42e 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
@@ -23,6 +23,8 @@ import java.util.Set;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
+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.ConnectorObject;
@@ -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 {
 
     private 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 a40d9b29cd..bd187e82ea 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.task.PropagationTask;
 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.PropagationTaskExecutor;
+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;
@@ -44,6 +46,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 {
 
     private static final String CLEARTEXT = "CLEARTEXT";
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 68f0b66519..63316a6ea4 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
@@ -24,6 +24,8 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
+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.ConnectorObject;
@@ -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 {
 
     private 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 5ff46c7e70..c382798357 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
@@ -40,6 +40,8 @@ import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.provisioning.api.DerAttrHandler;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.identityconnectors.framework.common.objects.Attribute;
+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.AttributeUtil;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
@@ -54,6 +56,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 21e5e0c5e4..3549a72504 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.task.PropagationTask;
 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.PropagationTaskExecutor;
+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;
@@ -44,6 +46,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/pushpull/AbstractProvisioningJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
index fb83490d44..7721c0e1b8 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
@@ -24,6 +24,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import javax.annotation.Resource;
+import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.TraceLevel;
@@ -38,8 +39,11 @@ 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.ConnectorFactory;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.ProvisionSorter;
+import org.apache.syncope.core.provisioning.java.DefaultProvisionSorter;
 import org.apache.syncope.core.provisioning.java.job.AbstractSchedTaskJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -110,6 +114,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.isPresent()) {
+            perContextProvisionSorter = Optional.of(new DefaultProvisionSorter());
+        }
+        return perContextProvisionSorter.get();
+    }
+
     /**
      * Create a textual report of the provisionig 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 a0d4cc8d14..1ff7827874 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
@@ -22,7 +22,9 @@ import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 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.types.AnyTypeKind;
@@ -52,6 +54,7 @@ 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.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
@@ -60,7 +63,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Item;
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeUtil;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
@@ -113,6 +116,8 @@ public class InboundMatcher {
     @Autowired
     private AnyUtilsFactory anyUtilsFactory;
 
+    private final Map<String, PullCorrelationRule> perContextPullCorrelationRules = new ConcurrentHashMap<>();
+
     public Optional<PullMatch> match(
             final AnyType anyType,
             final String nameValue,
@@ -339,27 +344,37 @@ public class InboundMatcher {
         return result;
     }
 
-    /**
-     * Finds internal entities based on external attributes and mapping.
-     *
-     * @param syncDelta change operation, including external attributes
-     * @param provision mapping
-     * @return list of matching users' / groups' / any objects' keys
-     */
-    public List<PullMatch> match(final SyncDelta syncDelta, final Provision provision) {
+    protected Optional<PullCorrelationRule> rule(final Provision provision) {
         Optional<? extends PullCorrelationRuleEntity> correlationRule = provision.getResource().getPullPolicy() == null
                 ? Optional.empty()
                 : provision.getResource().getPullPolicy().getCorrelationRule(provision.getAnyType());
 
         Optional<PullCorrelationRule> rule = Optional.empty();
         if (correlationRule.isPresent()) {
+            Implementation impl = correlationRule.get().getImplementation();
             try {
-                rule = ImplementationManager.buildPullCorrelationRule(correlationRule.get().getImplementation());
+                rule = ImplementationManager.buildPullCorrelationRule(
+                        impl,
+                        () -> perContextPullCorrelationRules.get(impl.getKey()),
+                        instance -> perContextPullCorrelationRules.put(impl.getKey(), instance));
             } catch (Exception e) {
-                LOG.error("While building {}", correlationRule.get().getImplementation(), e);
+                LOG.error("While building {}", impl, e);
             }
         }
 
+        return rule;
+    }
+
+    /**
+     * Finds internal entities based on external attributes and mapping.
+     *
+     * @param syncDelta change operation, including external attributes
+     * @param provision mapping
+     * @return list of matching users' / groups' / any objects' keys
+     */
+    public List<PullMatch> match(final SyncDelta syncDelta, final Provision provision) {
+        Optional<PullCorrelationRule> rule = rule(provision);
+
         List<PullMatch> result = Collections.emptyList();
         try {
             if (rule.isPresent()) {
@@ -442,7 +457,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 1add31108f..9851cfd92f 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
@@ -23,8 +23,10 @@ import java.util.Arrays;
 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.types.AnyTypeKind;
@@ -33,9 +35,11 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 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.Implementation;
 import org.apache.syncope.core.persistence.api.entity.LinkingMappingItem;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
@@ -45,7 +49,7 @@ import org.apache.syncope.core.provisioning.api.TimeoutException;
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.SearchResult;
@@ -77,6 +81,10 @@ public class OutboundMatcher {
     @Autowired
     private VirAttrHandler virAttrHandler;
 
+    private final Map<String, PropagationActions> perContextActions = new ConcurrentHashMap<>();
+
+    private final Map<String, PushCorrelationRule> perContextPushCorrelationRules = new ConcurrentHashMap<>();
+
     private Optional<PushCorrelationRule> rule(final Provision provision) {
         Optional<? extends PushCorrelationRuleEntity> correlationRule = provision.getResource().getPushPolicy() == null
                 ? Optional.empty()
@@ -84,10 +92,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);
             }
         }
 
@@ -149,6 +161,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,
@@ -157,19 +186,11 @@ public class OutboundMatcher {
             final Optional<String[]> moreAttrsToGet,
             final LinkingMappingItem... linkingItems) {
 
-        Set<String> matgFromPropagationActions = new HashSet<>();
-        provision.getResource().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(provision.getResource()).stream().
+                flatMap(a -> a.moreAttrsToGet(Optional.empty(), provision).stream());
         Optional<String[]> effectiveMATG = Optional.of(Stream.concat(
                 moreAttrsToGet.map(Stream::of).orElse(Stream.empty()),
-                matgFromPropagationActions.stream()).toArray(String[]::new));
+                matgFromPropagationActions).toArray(String[]::new));
 
         Optional<PushCorrelationRule> rule = rule(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 19444449e7..116d89b954 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;
@@ -32,12 +33,14 @@ import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
+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.PullMatch;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 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.resource.Item;
@@ -64,8 +67,7 @@ import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.OperationOptions;
 import org.identityconnectors.framework.common.objects.SyncToken;
 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
-import org.apache.syncope.core.provisioning.java.DefaultProvisionSorter;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.quartz.JobExecutionContext;
 
 public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> implements SyncopePullExecutor {
@@ -82,10 +84,17 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
     @Autowired
     protected AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    protected PlainAttrValidationManager validator;
+
     protected final Map<ObjectClass, SyncToken> latestSyncTokens = new HashMap<>();
 
     protected final Map<ObjectClass, MutablePair<Integer, String>> handled = new HashMap<>();
 
+    protected final Map<String, PullActions> perContextActions = new ConcurrentHashMap<>();
+
+    protected Optional<ReconFilterBuilder> perContextReconFilterBuilder = Optional.empty();
+
     protected ProvisioningProfile<PullTask, PullActions> profile;
 
     @Override
@@ -162,22 +171,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() {
@@ -213,10 +228,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
@@ -225,7 +238,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);
             }
         }
@@ -239,7 +252,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(new String[0]));
 
@@ -269,7 +282,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                     case FILTERED_RECONCILIATION:
                         connector.filteredReconciliation(
                                 orgUnit.getObjectClass(),
-                                buildReconFilterBuilder(pullTask),
+                                getReconFilterBuilder(pullTask),
                                 handler,
                                 options);
                         break;
@@ -287,14 +300,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().
@@ -322,7 +328,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<? extends Item> mapItems = Stream.concat(
                         MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
                         virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
@@ -350,7 +356,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                     case FILTERED_RECONCILIATION:
                         connector.filteredReconciliation(
                                 provision.getObjectClass(),
-                                buildReconFilterBuilder(pullTask),
+                                getReconFilterBuilder(pullTask),
                                 handler,
                                 options);
                         break;
@@ -371,7 +377,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                             && result.getOperation() == ResourceOperation.CREATE
                             && result.getAnyType().equals(provision.getAnyType().getKey())).
                             forEach(result -> anyUtils.addAttr(
-                            result.getKey(), provision.getUidOnCreate(), result.getUidValue()));
+                            validator, result.getKey(), provision.getUidOnCreate(), result.getUidValue()));
                 }
             } catch (Throwable t) {
                 throw new JobExecutionException("While pulling from connector", t);
@@ -385,7 +391,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 55eee2e478..243eaf0827 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,20 +24,19 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
-import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
-import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
-import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 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.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;
@@ -46,6 +45,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.task.PushTaskAnyFilter;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.ProvisionSorter;
@@ -56,8 +56,8 @@ import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.provisioning.api.pushpull.RealmPushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
-import org.apache.syncope.core.provisioning.java.DefaultProvisionSorter;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -84,6 +84,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) {
@@ -154,6 +156,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,
@@ -163,24 +182,15 @@ 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
                 : pushTask.getResource().getPushPolicy().getConflictResolutionAction());
 
         if (!profile.isDryRun()) {
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
         }
@@ -209,14 +219,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).
@@ -259,14 +262,14 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
                         cond,
                         page,
                         AnyDAO.DEFAULT_PAGE_SIZE,
-                        Collections.<OrderByClause>emptyList(),
+                        Collections.emptyList(),
                         provision.getAnyType().getKind());
                 doHandle(anys, handler, pushTask.getResource());
             }
         }
 
         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 9f5066efdb..6a70e79177 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,21 +18,20 @@
  */
 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.PullTaskTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
-import org.apache.syncope.common.lib.types.ImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.PullMode;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 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.Implementation;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.resource.Item;
@@ -49,7 +48,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.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -74,20 +72,6 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
 
         LOG.debug("Executing pull on {}", provision.getResource());
 
-        List<PullActions> actions = new ArrayList<>();
-        pullTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || impl.getType() != ImplementationType.PULL_ACTIONS) {
-                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(provision.getResource());
@@ -124,9 +108,10 @@ 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())));
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
 
@@ -150,7 +135,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<? extends Item> mapItems = Stream.concat(
                     MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
@@ -168,7 +153,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 e6a4520b45..0cbf65aee1 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,17 +18,16 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
-import org.apache.syncope.common.lib.types.ImplementationType;
 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.Implementation;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
@@ -39,7 +38,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;
 
@@ -48,27 +46,13 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
     @Autowired
     private ImplementationDAO implementationDAO;
 
-    private List<PushActions> before(
+    private void before(
             final Provision provision,
             final Connector connector,
             final PushTaskTO pushTaskTO) throws JobExecutionException {
 
         LOG.debug("Executing push on {}", provision.getResource());
 
-        List<PushActions> actions = new ArrayList<>();
-        pushTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || impl.getType() != ImplementationType.PUSH_ACTIONS) {
-                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(provision.getResource());
         pushTask.setMatchingRule(pushTaskTO.getMatchingRule() == null
@@ -81,14 +65,13 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
         pushTask.setSyncStatus(pushTaskTO.isSyncStatus());
 
         profile = new ProvisioningProfile<>(connector, pushTask);
-        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
@@ -99,7 +82,7 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
             final PushTaskTO pushTaskTO) throws JobExecutionException {
 
         try {
-            List<PushActions> actions = before(provision, connector, pushTaskTO);
+            before(provision, connector, pushTaskTO);
 
             SyncopePushResultHandler handler;
             switch (provision.getAnyType().getKind()) {
@@ -119,7 +102,7 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
 
             doHandle(Collections.singletonList(any), handler, provision.getResource());
 
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
@@ -139,14 +122,14 @@ public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSin
             final PushTaskTO pushTaskTO) throws JobExecutionException {
 
         try {
-            List<PushActions> actions = before(provision, connector, pushTaskTO);
+            before(provision, connector, pushTaskTO);
 
             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 e0185b36e8..711856ae0c 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.PullTaskTO;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
@@ -35,7 +36,6 @@ 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.Implementation;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
-import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
@@ -45,12 +45,12 @@ import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPullResultHandler;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
@@ -169,20 +169,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 || impl.getType() != ImplementationType.PULL_ACTIONS) {
-                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);
@@ -203,9 +189,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);
             }
 
@@ -229,9 +216,9 @@ 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<? extends Item> mapItems = Stream.concat(
+            Stream<Item> mapItems = Stream.concat(
                     MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
                     virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
 
@@ -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 18800a7792..417d7b3bce 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.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
@@ -44,7 +45,6 @@ import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPus
 import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
 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.identityconnectors.framework.common.objects.ObjectClass;
 import org.quartz.JobExecutionException;
@@ -130,20 +130,6 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
 
         LOG.debug("Executing stream push");
 
-        List<PushActions> pushActions = new ArrayList<>();
-        pushTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || impl.getType() != ImplementationType.PUSH_ACTIONS) {
-                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);
             Provision provision = resource.getProvisions().get(0);
@@ -158,10 +144,11 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
             pushTask.setSyncStatus(false);
 
             profile = new ProvisioningProfile<>(connector, pushTask);
-            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);
             }
 
@@ -183,7 +170,7 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
 
             doHandle(anys, handler, provision.getResource());
 
-            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 1f5258506b..b3d771800a 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
@@ -22,8 +22,10 @@ import java.util.ArrayList;
 import java.util.Arrays;
 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;
@@ -44,12 +46,14 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.support.AbstractBeanDefinition;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
 import org.apache.syncope.core.provisioning.api.data.JEXLItemTransformer;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 
 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<? extends MappingItem> getConnObjectKeyItem(final Provision provision) {
         Mapping mapping = null;
         if (provision != null) {
@@ -89,7 +93,10 @@ public final class MappingUtils {
         // Then other custom transformers
         item.getTransformers().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/MappingManagerImplTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingManagerImplTest.java
index 527ace7297..188673d3d4 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingManagerImplTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingManagerImplTest.java
@@ -27,6 +27,7 @@ import java.util.Set;
 import org.apache.commons.lang3.tuple.Pair;
 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.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
@@ -59,6 +60,9 @@ public class MappingManagerImplTest extends AbstractTest {
     @Autowired
     private EntityFactory entityFactory;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void prepareAttrsForUser() {
         User bellini = userDAO.findByUsername("bellini");
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 6b637d8993..0000000000
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/ImplementationManager.java
+++ /dev/null
@@ -1,251 +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 java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import groovy.lang.GroovyClassLoader;
-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..e00da5d9df
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
@@ -0,0 +1,251 @@
+/*
+ * 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.implementation;
+
+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.ApplicationContextProvider;
+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/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/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 41b1e4a27f..fb76a83303 100644
--- a/common/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,21 +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;
 
-import javax.xml.bind.annotation.XmlEnum;
-
-/**
- * Enum of all possible capabilities that a connector instance can expose.
- */
-@XmlEnum
-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 2613a2290e..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,26 +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 {
-
-    String message() default "{org.apache.syncope.core.persistence.validation.attr}";
-
-    Class<?>[] groups() default {};
+public @interface SyncopeImplementation {
 
-    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 037fac779b..58b1afec89 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,11 +20,15 @@ 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 org.apache.commons.lang3.StringUtils;
 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.Implementation;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
-import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.implementation.ImplementationManager;
 import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
 import org.apache.syncope.core.spring.policy.PolicyPattern;
 import org.slf4j.Logger;
@@ -47,6 +51,8 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
 
     private 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) throws InvalidPasswordRuleConf {
@@ -59,23 +65,33 @@ public class DefaultPasswordGenerator implements PasswordGenerator {
         return generate(policies);
     }
 
-    @Override
-    public String generate(final List<PasswordPolicy> policies) throws InvalidPasswordRuleConf {
-        List<DefaultPasswordRuleConf> defaultRuleConfs = 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) {
-                        defaultRuleConfs.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) throws InvalidPasswordRuleConf {
+        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())));
 
-        DefaultPasswordRuleConf ruleConf = merge(defaultRuleConfs);
+        DefaultPasswordRuleConf ruleConf = merge(ruleConfs);
         check(ruleConf);
         return generate(ruleConf);
     }
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 42b2d84b56..8258cb7274 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
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.spring;
+package org.apache.syncope.core.spring.implementation;
 
 import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -66,8 +66,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);
@@ -81,8 +81,11 @@ public class ImplementationManagerTest {
                             Thread.yield();
                         }
                         try {
-                            ImplementationManager.buildPasswordRule(implementation).
-                                    orElseThrow(() -> new IllegalStateException("No implementation returned"));
+                            ImplementationManager.buildPasswordRule(
+                                    impl,
+                                    () -> null,
+                                    instance -> {
+                                    }).orElseThrow(() -> new IllegalStateException("No implementation returned"));
                         } catch (Exception e) {
                             errorMessages.add(e.getLocalizedMessage());
                             errorCount.incrementAndGet();
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 7e4f042bcd..6c304e83f1 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
@@ -26,7 +26,10 @@ 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.resource.Item;
 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 a54bb05daa..c6ae4410d0 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.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.resource.Item;
 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