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 2018/03/28 14:53:26 UTC

[1/2] syncope git commit: [SYNCOPE-1287] Core implementation

Repository: syncope
Updated Branches:
  refs/heads/master 59a3422e4 -> 51b314fe5


http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/SchemaLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/SchemaLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/SchemaLogic.java
index d9cfafa..892850e 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/SchemaLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/SchemaLogic.java
@@ -48,6 +48,7 @@ import org.apache.syncope.core.provisioning.api.data.SchemaDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class SchemaLogic extends AbstractTransactionalLogic<SchemaTO> {
@@ -145,11 +146,13 @@ public class SchemaLogic extends AbstractTransactionalLogic<SchemaTO> {
     }
 
     @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
     public <T extends SchemaTO> List<T> list(final SchemaType schemaType, final List<String> anyTypeClasses) {
         return doSearch(schemaType, anyTypeClasses, null);
     }
 
     @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
     public <T extends SchemaTO> List<T> search(
             final SchemaType schemaType, final List<String> anyTypeClasses, final String keyword) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/SecurityQuestionLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/SecurityQuestionLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/SecurityQuestionLogic.java
index f77f2fd..46285f2 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/SecurityQuestionLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/SecurityQuestionLogic.java
@@ -50,8 +50,7 @@ public class SecurityQuestionLogic extends AbstractTransactionalLogic<SecurityQu
     @PreAuthorize("isAuthenticated()")
     @Transactional(readOnly = true)
     public List<SecurityQuestionTO> list() {
-        return securityQuestionDAO.findAll().stream().
-                map(securityQuestion -> binder.getSecurityQuestionTO(securityQuestion)).collect(Collectors.toList());
+        return securityQuestionDAO.findAll().stream().map(binder::getSecurityQuestionTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.SECURITY_QUESTION_READ + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
index 0fb587a..8aa7151 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
@@ -23,6 +23,7 @@ import java.lang.management.OperatingSystemMXBean;
 import java.lang.management.RuntimeMXBean;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
+import java.net.URI;
 import java.net.UnknownHostException;
 import java.util.Arrays;
 import java.util.Collections;
@@ -68,6 +69,7 @@ import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
 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.AnyType;
+import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
@@ -210,8 +212,8 @@ public class SyncopeLogic extends AbstractLogic<AbstractBaseBean> {
                 PLATFORM_INFO.setBuildNumber(buildNumber);
 
                 if (bundleManager.getLocations() != null) {
-                    bundleManager.getLocations().
-                            forEach(location -> PLATFORM_INFO.getConnIdLocations().add(location.toASCIIString()));
+                    PLATFORM_INFO.getConnIdLocations().addAll(bundleManager.getLocations().stream().
+                            map(URI::toASCIIString).collect(Collectors.toList()));
                 }
 
                 PLATFORM_INFO.setPropagationTaskExecutor(AopUtils.getTargetClass(propagationTaskExecutor).getName());
@@ -246,19 +248,19 @@ public class SyncopeLogic extends AbstractLogic<AbstractBaseBean> {
             AuthContextUtils.execWithAuthContext(AuthContextUtils.getDomain(), () -> {
                 PLATFORM_INFO.getAnyTypes().clear();
                 PLATFORM_INFO.getAnyTypes().addAll(anyTypeDAO.findAll().stream().
-                        map(type -> type.getKey()).collect(Collectors.toList()));
+                        map(Entity::getKey).collect(Collectors.toList()));
 
                 PLATFORM_INFO.getUserClasses().clear();
                 PLATFORM_INFO.getUserClasses().addAll(anyTypeDAO.findUser().getClasses().stream().
-                        map(cls -> cls.getKey()).collect(Collectors.toList()));
+                        map(Entity::getKey).collect(Collectors.toList()));
 
                 PLATFORM_INFO.getAnyTypeClasses().clear();
                 PLATFORM_INFO.getAnyTypeClasses().addAll(anyTypeClassDAO.findAll().stream().
-                        map(cls -> cls.getKey()).collect(Collectors.toList()));
+                        map(Entity::getKey).collect(Collectors.toList()));
 
                 PLATFORM_INFO.getResources().clear();
                 PLATFORM_INFO.getResources().addAll(resourceDAO.findAll().stream().
-                        map(resource -> resource.getKey()).collect(Collectors.toList()));
+                        map(Entity::getKey).collect(Collectors.toList()));
                 return null;
             });
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index baad7e8..bcbe645 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@ -63,6 +63,7 @@ import org.quartz.SchedulerException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
@@ -156,6 +157,7 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.TASK_LIST + "')")
+    @Transactional(readOnly = true)
     @SuppressWarnings("unchecked")
     public <T extends TaskTO> Pair<Integer, List<T>> list(
             final TaskType type,
@@ -191,6 +193,7 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.TASK_READ + "')")
+    @Transactional(readOnly = true)
     public <T extends TaskTO> T read(final TaskType type, final String key, final boolean details) {
         Task task = taskDAO.find(key);
         if (task == null) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/UserWorkflowLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/UserWorkflowLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/UserWorkflowLogic.java
index 62b43ae..8fc4745 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/UserWorkflowLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/UserWorkflowLogic.java
@@ -58,7 +58,6 @@ public class UserWorkflowLogic extends AbstractTransactionalLogic<WorkflowFormTO
     private UserDAO userDAO;
 
     @PreAuthorize("hasRole('" + StandardEntitlement.WORKFLOW_FORM_CLAIM + "')")
-    @Transactional(rollbackFor = { Throwable.class })
     public WorkflowFormTO claimForm(final String taskId) {
         return uwfAdapter.claimForm(taskId);
     }
@@ -82,20 +81,19 @@ public class UserWorkflowLogic extends AbstractTransactionalLogic<WorkflowFormTO
 
     @PreAuthorize("hasRole('" + StandardEntitlement.WORKFLOW_FORM_READ + "') and hasRole('"
             + StandardEntitlement.USER_READ + "')")
-    @Transactional(rollbackFor = { Throwable.class })
+    @Transactional(readOnly = true)
     public WorkflowFormTO getFormForUser(final String key) {
         User user = userDAO.authFind(key);
         return uwfAdapter.getForm(user.getWorkflowId());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.WORKFLOW_FORM_LIST + "')")
-    @Transactional(rollbackFor = { Throwable.class })
+    @Transactional(readOnly = true)
     public List<WorkflowFormTO> getForms() {
         return uwfAdapter.getForms();
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.WORKFLOW_FORM_SUBMIT + "')")
-    @Transactional(rollbackFor = { Throwable.class })
     public UserTO submitForm(final WorkflowFormTO form) {
         WorkflowResult<? extends AnyPatch> updated = uwfAdapter.submitForm(form);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java
index f9db494..d6a3fe7 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/InvalidEntityException.java
@@ -89,6 +89,8 @@ public class InvalidEntityException extends ValidationException {
 
             entityViolationType.setMessage(message.trim());
 
+            entityViolationType.setPropertyPath(violation.getPropertyPath().toString());
+
             if (!this.violations.containsKey(violation.getLeafBean().getClass())) {
                 this.violations.put(violation.getLeafBean().getClass(), EnumSet.noneOf(EntityViolationType.class));
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java
new file mode 100644
index 0000000..f00cb8a
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RemediationDAO.java
@@ -0,0 +1,39 @@
+/*
+ * 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.api.dao;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+
+public interface RemediationDAO extends DAO<Remediation> {
+
+    Remediation find(String key);
+
+    List<Remediation> findByPullTask(PullTask pullTask);
+
+    List<Remediation> findAll();
+
+    Remediation save(Remediation remediation);
+
+    void delete(Remediation remediation);
+
+    void delete(String key);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Remediation.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Remediation.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Remediation.java
new file mode 100644
index 0000000..1278241
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Remediation.java
@@ -0,0 +1,65 @@
+/*
+ * 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.api.entity;
+
+import java.util.Date;
+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.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+
+public interface Remediation extends Entity {
+
+    AnyTypeKind getAnyTypeKind();
+
+    void setAnyTypeKind(AnyTypeKind anyTypeKind);
+
+    ResourceOperation getOperation();
+
+    void setOperation(ResourceOperation operation);
+
+    void setPayload(AnyTO anyTO);
+
+    void setPayload(AnyPatch anyPatch);
+
+    void setPayload(String key);
+
+    <T extends AnyTO> T getPayloadAsTO(Class<T> reference);
+
+    <P extends AnyPatch> P getPayloadAsPatch(Class<P> reference);
+
+    String getPayloadAsKey();
+
+    String getError();
+
+    void setError(String error);
+
+    Date getInstant();
+
+    void setInstant(Date instant);
+
+    PullTask getPullTask();
+
+    void setPullTask(PullTask pullTask);
+
+    String getRemoteName();
+
+    void setRemoteName(String remoteName);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/PullTask.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/PullTask.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/PullTask.java
index d4234f2..2d8ffdd 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/PullTask.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/task/PullTask.java
@@ -44,4 +44,9 @@ public interface PullTask extends ProvisioningTask {
     Optional<? extends AnyTemplatePullTask> getTemplate(AnyType anyType);
 
     List<? extends AnyTemplatePullTask> getTemplates();
+
+    void setRemediation(boolean remediation);
+
+    boolean isRemediation();
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARemediationDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARemediationDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARemediationDAO.java
new file mode 100644
index 0000000..b78c425
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARemediationDAO.java
@@ -0,0 +1,73 @@
+/*
+ * 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.dao;
+
+import java.util.List;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+import org.apache.syncope.core.persistence.jpa.entity.JPARemediation;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class JPARemediationDAO extends AbstractDAO<Remediation> implements RemediationDAO {
+
+    @Override
+    public Remediation find(final String key) {
+        return entityManager().find(JPARemediation.class, key);
+    }
+
+    @Override
+    public List<Remediation> findByPullTask(final PullTask pullTask) {
+        TypedQuery<Remediation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPARemediation.class.getSimpleName() + " e WHERE e.pullTask=:pullTask",
+                Remediation.class);
+        query.setParameter("pullTask", pullTask);
+        return query.getResultList();
+    }
+
+    @Override
+    public List<Remediation> findAll() {
+        TypedQuery<Remediation> query = entityManager().createQuery(
+                "SELECT e FROM " + JPARemediation.class.getSimpleName() + " e ", Remediation.class);
+        return query.getResultList();
+    }
+
+    @Override
+    public Remediation save(final Remediation remediation) {
+        return entityManager().merge(remediation);
+    }
+
+    @Override
+    public void delete(final Remediation remediation) {
+        entityManager().remove(remediation);
+    }
+
+    @Override
+    public void delete(final String key) {
+        Remediation remediation = find(key);
+        if (remediation == null) {
+            return;
+        }
+
+        delete(remediation);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
index 792a661..d47ba5d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
@@ -25,6 +25,7 @@ import javax.persistence.TypedQuery;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.Entity;
@@ -41,6 +42,7 @@ import org.apache.syncope.core.persistence.jpa.entity.task.JPAPushTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPASchedTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPullTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.AbstractTask;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.ReflectionUtils;
@@ -48,6 +50,9 @@ import org.springframework.util.ReflectionUtils;
 @Repository
 public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
 
+    @Autowired
+    private RemediationDAO remediationDAO;
+
     @Override
     public Class<? extends Task> getEntityReference(final TaskType type) {
         Class<? extends Task> result = null;
@@ -303,6 +308,12 @@ public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
 
     @Override
     public void delete(final Task task) {
+        if (task instanceof PullTask) {
+            remediationDAO.findByPullTask((PullTask) task).forEach(remediation -> {
+                remediation.setPullTask(null);
+            });
+        }
+
         entityManager().remove(task);
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index aa2809a..8de177e 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -130,6 +130,7 @@ import org.apache.syncope.core.persistence.api.entity.DynRealm;
 import org.apache.syncope.core.persistence.api.entity.DynRealmMembership;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
 import org.apache.syncope.core.persistence.api.entity.policy.CorrelationRule;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResourceHistoryConf;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
@@ -285,6 +286,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAAccessToken();
         } else if (reference.equals(Implementation.class)) {
             result = (E) new JPAImplementation();
+        } else if (reference.equals(Remediation.class)) {
+            result = (E) new JPARemediation();
         } else {
             throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARemediation.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARemediation.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARemediation.java
new file mode 100644
index 0000000..6f31cc9
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARemediation.java
@@ -0,0 +1,171 @@
+/*
+ * 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.entity;
+
+import java.util.Date;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.validation.constraints.NotNull;
+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.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+import org.apache.syncope.core.persistence.jpa.entity.task.JPAPullTask;
+import org.apache.syncope.core.persistence.jpa.validation.entity.RemediationCheck;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+@Entity
+@Table(name = JPARemediation.TABLE)
+@RemediationCheck
+public class JPARemediation extends AbstractGeneratedKeyEntity implements Remediation {
+
+    private static final long serialVersionUID = -1612530286294448682L;
+
+    public static final String TABLE = "Remediation";
+
+    @NotNull
+    @Enumerated(EnumType.STRING)
+    private AnyTypeKind anyTypeKind;
+
+    @NotNull
+    @Enumerated(EnumType.STRING)
+    private ResourceOperation operation;
+
+    @NotNull
+    @Lob
+    private String payload;
+
+    @NotNull
+    @Lob
+    private String error;
+
+    @NotNull
+    @Temporal(TemporalType.TIMESTAMP)
+    private Date instant;
+
+    @ManyToOne
+    private JPAPullTask pullTask;
+
+    @NotNull
+    private String remoteName;
+
+    @Override
+    public AnyTypeKind getAnyTypeKind() {
+        return anyTypeKind;
+    }
+
+    @Override
+    public void setAnyTypeKind(final AnyTypeKind anyTypeKind) {
+        this.anyTypeKind = anyTypeKind;
+    }
+
+    @Override
+    public ResourceOperation getOperation() {
+        return operation;
+    }
+
+    @Override
+    public void setOperation(final ResourceOperation operation) {
+        this.operation = operation;
+    }
+
+    @Override
+    public <T extends AnyTO> T getPayloadAsTO(final Class<T> reference) {
+        return POJOHelper.deserialize(this.payload, reference);
+    }
+
+    @Override
+    public <P extends AnyPatch> P getPayloadAsPatch(final Class<P> reference) {
+        return POJOHelper.deserialize(this.payload, reference);
+    }
+
+    @Override
+    public String getPayloadAsKey() {
+        return this.payload;
+    }
+
+    @Override
+    public void setPayload(final AnyTO anyTO) {
+        this.payload = POJOHelper.serialize(anyTO);
+    }
+
+    @Override
+    public void setPayload(final AnyPatch anyPatch) {
+        this.payload = POJOHelper.serialize(anyPatch);
+    }
+
+    @Override
+    public void setPayload(final String key) {
+        this.payload = key;
+    }
+
+    @Override
+    public String getError() {
+        return error;
+    }
+
+    @Override
+    public void setError(final String error) {
+        this.error = error;
+    }
+
+    @Override
+    public Date getInstant() {
+        return instant == null
+                ? null
+                : new Date(instant.getTime());
+    }
+
+    @Override
+    public void setInstant(final Date instant) {
+        this.instant = instant == null
+                ? null
+                : new Date(instant.getTime());
+    }
+
+    @Override
+    public PullTask getPullTask() {
+        return pullTask;
+    }
+
+    @Override
+    public void setPullTask(final PullTask pullTask) {
+        checkType(pullTask, JPAPullTask.class);
+        this.pullTask = (JPAPullTask) pullTask;
+    }
+
+    @Override
+    public String getRemoteName() {
+        return remoteName;
+    }
+
+    @Override
+    public void setRemoteName(final String remoteName) {
+        this.remoteName = remoteName;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAPullTask.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAPullTask.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAPullTask.java
index d42c3ea..5974800 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAPullTask.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/JPAPullTask.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.persistence.jpa.entity.task;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import javax.persistence.Basic;
 import javax.persistence.CascadeType;
 import javax.persistence.DiscriminatorValue;
 import javax.persistence.Entity;
@@ -33,6 +34,8 @@ import javax.persistence.ManyToMany;
 import javax.persistence.ManyToOne;
 import javax.persistence.OneToMany;
 import javax.persistence.OneToOne;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
 import javax.validation.constraints.NotNull;
 import org.apache.syncope.common.lib.types.ImplementationType;
 import org.apache.syncope.common.lib.types.PullMode;
@@ -71,6 +74,11 @@ public class JPAPullTask extends AbstractProvisioningTask implements PullTask {
     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "pullTask")
     private List<JPAAnyTemplatePullTask> templates = new ArrayList<>();
 
+    @Basic
+    @Min(0)
+    @Max(1)
+    private Integer remediation;
+
     @Override
     public PullMode getPullMode() {
         return pullMode;
@@ -134,4 +142,14 @@ public class JPAPullTask extends AbstractProvisioningTask implements PullTask {
         return templates;
     }
 
+    @Override
+    public void setRemediation(final boolean remediation) {
+        this.remediation = getBooleanAsInteger(remediation);
+    }
+
+    @Override
+    public boolean isRemediation() {
+        return isBooleanAsInteger(remediation);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationCheck.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationCheck.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationCheck.java
new file mode 100644
index 0000000..7a0a9c2
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationCheck.java
@@ -0,0 +1,41 @@
+/*
+ * 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.validation.entity;
+
+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 = RemediationValidator.class)
+@Documented
+public @interface RemediationCheck {
+
+    String message() default "{org.apache.syncope.core.persistence.validation.remediation}";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationValidator.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationValidator.java
new file mode 100644
index 0000000..25f9fb2
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/RemediationValidator.java
@@ -0,0 +1,80 @@
+/*
+ * 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.validation.entity;
+
+import javax.validation.ConstraintValidatorContext;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.EntityViolationType;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+
+public class RemediationValidator extends AbstractValidator<RemediationCheck, Remediation> {
+
+    @Override
+    public boolean isValid(final Remediation remediation, final ConstraintValidatorContext context) {
+        boolean isValid = true;
+
+        switch (remediation.getOperation()) {
+            case CREATE:
+                if (remediation.getPayloadAsTO(remediation.getAnyTypeKind().getTOClass()) == null) {
+                    context.disableDefaultConstraintViolation();
+                    context.buildConstraintViolationWithTemplate(
+                            getTemplate(EntityViolationType.InvalidRemediation,
+                                    "Expected " + remediation.getAnyTypeKind().getTOClass().getName())).
+                            addPropertyNode("payload").addConstraintViolation();
+
+                    isValid = false;
+                }
+                break;
+
+            case UPDATE:
+                if (remediation.getPayloadAsPatch(remediation.getAnyTypeKind().getPatchClass()) == null) {
+                    context.disableDefaultConstraintViolation();
+                    context.buildConstraintViolationWithTemplate(
+                            getTemplate(EntityViolationType.InvalidRemediation,
+                                    "Expected " + remediation.getAnyTypeKind().getPatchClass().getName())).
+                            addPropertyNode("payload").addConstraintViolation();
+
+                    isValid = false;
+                }
+                break;
+
+            case DELETE:
+                if (!SyncopeConstants.UUID_PATTERN.matcher(remediation.getPayloadAsKey()).matches()) {
+                    context.disableDefaultConstraintViolation();
+                    context.buildConstraintViolationWithTemplate(
+                            getTemplate(EntityViolationType.InvalidRemediation, "Expected UUID")).
+                            addPropertyNode("payload").addConstraintViolation();
+
+                    isValid = false;
+                }
+                break;
+
+            case NONE:
+            default:
+                context.disableDefaultConstraintViolation();
+                context.buildConstraintViolationWithTemplate(
+                        getTemplate(EntityViolationType.InvalidRemediation, "NONE is not allowed")).
+                        addPropertyNode("operation").addConstraintViolation();
+
+                isValid = false;
+        }
+
+        return isValid;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RemediationTest.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RemediationTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RemediationTest.java
new file mode 100644
index 0000000..02315fa
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RemediationTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.inner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.EntityViolationType;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class RemediationTest extends AbstractTest {
+
+    @Autowired
+    private RemediationDAO remediationDAO;
+
+    @Autowired
+    private TaskDAO taskDAO;
+
+    @Test
+    public void findAll() {
+        List<Remediation> remediations = remediationDAO.findAll();
+        assertTrue(remediations.isEmpty());
+    }
+
+    @Test
+    public void create() {
+        Remediation remediation = entityFactory.newEntity(Remediation.class);
+        remediation.setAnyTypeKind(AnyTypeKind.ANY_OBJECT);
+        remediation.setOperation(ResourceOperation.CREATE);
+        remediation.setError("Error");
+        remediation.setInstant(new Date());
+        remediation.setRemoteName("remote");
+        remediation.setPullTask(taskDAO.find("38abbf9e-a1a3-40a1-a15f-7d0ac02f47f1"));
+
+        // missing payload
+        try {
+            remediationDAO.save(remediation);
+            fail("This should not happen");
+        } catch (InvalidEntityException e) {
+            Set<EntityViolationType> violations = e.getViolations().values().iterator().next();
+            assertEquals(2, violations.size());
+            assertTrue(violations.stream().allMatch(violation -> violation.getPropertyPath().equals("payload")));
+        }
+
+        remediation.setPayload(UUID.randomUUID().toString());
+
+        // wrong payload for operation
+        try {
+            remediationDAO.save(remediation);
+            fail("This should not happen");
+        } catch (InvalidEntityException e) {
+            Set<EntityViolationType> violations = e.getViolations().values().iterator().next();
+            assertEquals(1, violations.size());
+            assertTrue(violations.stream().anyMatch(violation -> violation.getPropertyPath().equals("payload")));
+        }
+
+        remediation.setOperation(ResourceOperation.DELETE);
+
+        remediation = remediationDAO.save(remediation);
+        assertNotNull(remediation.getKey());
+        assertNotNull(remediation.getPullTask());
+
+        taskDAO.delete(remediation.getPullTask());
+
+        remediationDAO.flush();
+
+        remediation = remediationDAO.find(remediation.getKey());
+        assertNull(remediation.getPullTask());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ConnInstanceDataBinder.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ConnInstanceDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ConnInstanceDataBinder.java
index ae7074f..9fa892e 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ConnInstanceDataBinder.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ConnInstanceDataBinder.java
@@ -18,9 +18,11 @@
  */
 package org.apache.syncope.core.provisioning.api.data;
 
+import org.apache.syncope.common.lib.to.ConnInstanceHistoryConfTO;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.types.ConnConfPropSchema;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
+import org.apache.syncope.core.persistence.api.entity.ConnInstanceHistoryConf;
 import org.identityconnectors.framework.api.ConfigurationProperty;
 
 public interface ConnInstanceDataBinder {
@@ -31,6 +33,8 @@ public interface ConnInstanceDataBinder {
 
     ConnInstanceTO getConnInstanceTO(ConnInstance connInstance);
 
+    ConnInstanceHistoryConfTO getConnInstanceHistoryConfTO(ConnInstanceHistoryConf history);
+
     ConnInstance update(ConnInstanceTO connInstanceTO);
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RemediationDataBinder.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RemediationDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RemediationDataBinder.java
new file mode 100644
index 0000000..42d8332
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RemediationDataBinder.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.api.data;
+
+import org.apache.syncope.common.lib.to.RemediationTO;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+
+public interface RemediationDataBinder {
+
+    RemediationTO getRemediationTO(Remediation remediation);
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ResourceDataBinder.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ResourceDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ResourceDataBinder.java
index 9f1ea5d..84b2254 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ResourceDataBinder.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ResourceDataBinder.java
@@ -18,8 +18,10 @@
  */
 package org.apache.syncope.core.provisioning.api.data;
 
+import org.apache.syncope.common.lib.to.ResourceHistoryConfTO;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResourceHistoryConf;
 
 public interface ResourceDataBinder {
 
@@ -29,4 +31,5 @@ public interface ResourceDataBinder {
 
     ExternalResource update(ExternalResource resource, ResourceTO resourceTO);
 
+    ResourceHistoryConfTO getResourceHistoryConfTO(ExternalResourceHistoryConf history);
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java
index d4b9206..333311c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Optional;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.ConnInstanceHistoryConfTO;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.to.ConnPoolConfTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
@@ -277,4 +278,15 @@ public class ConnInstanceDataBinderImpl implements ConnInstanceDataBinder {
 
         return connInstanceTO;
     }
+
+    @Override
+    public ConnInstanceHistoryConfTO getConnInstanceHistoryConfTO(final ConnInstanceHistoryConf history) {
+        ConnInstanceHistoryConfTO historyTO = new ConnInstanceHistoryConfTO();
+        historyTO.setKey(history.getKey());
+        historyTO.setCreator(history.getCreator());
+        historyTO.setCreation(history.getCreation());
+        historyTO.setConnInstanceTO(history.getConf());
+
+        return historyTO;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RemediationDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RemediationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RemediationDataBinderImpl.java
new file mode 100644
index 0000000..d95f866
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RemediationDataBinderImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.data;
+
+import org.apache.syncope.common.lib.to.RemediationTO;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+import org.apache.syncope.core.provisioning.api.data.RemediationDataBinder;
+import org.apache.syncope.core.spring.BeanUtils;
+import org.springframework.stereotype.Component;
+
+@Component
+public class RemediationDataBinderImpl implements RemediationDataBinder {
+
+    private static final String[] IGNORE_PROPERTIES = {
+        "payload", "anyTOPayload", "anyPatchPayload", "keyPayload", "pullTask" };
+
+    @Override
+    public RemediationTO getRemediationTO(final Remediation remediation) {
+        RemediationTO remediationTO = new RemediationTO();
+
+        BeanUtils.copyProperties(remediation, remediationTO);
+
+        switch (remediation.getOperation()) {
+            case CREATE:
+                remediationTO.setAnyTOPayload(
+                        remediation.getPayloadAsTO(remediation.getAnyTypeKind().getTOClass()));
+                break;
+
+            case UPDATE:
+                remediationTO.setAnyPatchPayload(
+                        remediation.getPayloadAsPatch(remediation.getAnyTypeKind().getPatchClass()));
+                break;
+
+            case DELETE:
+                remediationTO.setKeyPayload(remediation.getPayloadAsKey());
+                break;
+
+            default:
+        }
+
+        if (remediation.getPullTask() != null) {
+            remediationTO.setPullTask(remediation.getPullTask().getKey());
+            remediationTO.setResource(remediation.getPullTask().getResource().getKey());
+        }
+
+        return remediationTO;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
----------------------------------------------------------------------
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 1db8e56..681743e 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
@@ -34,6 +34,7 @@ import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.MappingTO;
 import org.apache.syncope.common.lib.to.OrgUnitTO;
 import org.apache.syncope.common.lib.to.ProvisionTO;
+import org.apache.syncope.common.lib.to.ResourceHistoryConfTO;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.MappingPurpose;
@@ -646,4 +647,15 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
 
         return resourceTO;
     }
+
+    @Override
+    public ResourceHistoryConfTO getResourceHistoryConfTO(final ExternalResourceHistoryConf history) {
+        ResourceHistoryConfTO historyTO = new ResourceHistoryConfTO();
+        historyTO.setKey(history.getKey());
+        historyTO.setCreator(history.getCreator());
+        historyTO.setCreation(history.getCreation());
+        historyTO.setResourceTO(history.getConf());
+
+        return historyTO;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
index 1b30e59..6fcc05e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
@@ -219,6 +219,8 @@ public class TaskDataBinderImpl implements TaskDataBinder {
             // remove all templates not contained in the TO
             pullTask.getTemplates().
                     removeIf(anyTemplate -> !pullTaskTO.getTemplates().containsKey(anyTemplate.getAnyType().getKey()));
+
+            pullTask.setRemediation(pullTaskTO.isRemediation());
         }
 
         // 3. fill the remaining fields
@@ -407,6 +409,8 @@ public class TaskDataBinderImpl implements TaskDataBinder {
                 pullTask.getTemplates().forEach(template -> {
                     pullTaskTO.getTemplates().put(template.getAnyType().getKey(), template.get());
                 });
+
+                pullTaskTO.setRemediation(pullTask.isRemediation());
                 break;
 
             case PUSH:

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
index 2832cb5..a748562 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java.pushpull;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.syncope.common.lib.AnyOperations;
@@ -36,10 +37,13 @@ import org.apache.syncope.common.lib.types.PullMode;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 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.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
@@ -78,11 +82,17 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
     protected ConnObjectUtils connObjectUtils;
 
     @Autowired
+    protected RemediationDAO remediationDAO;
+
+    @Autowired
     protected VirSchemaDAO virSchemaDAO;
 
     @Autowired
     protected VirAttrCache virAttrCache;
 
+    @Autowired
+    protected EntityFactory entityFactory;
+
     protected SyncopePullExecutor executor;
 
     protected Result latestResult;
@@ -299,6 +309,19 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             LOG.error("Could not create {} {} ", anyTO.getType(), delta.getUid().getUidValue(), e);
             output = e;
             resultStatus = Result.FAILURE;
+
+            if (profile.getTask().isRemediation()) {
+                Remediation entity = entityFactory.newEntity(Remediation.class);
+                entity.setAnyTypeKind(getAnyUtils().getAnyTypeKind());
+                entity.setOperation(ResourceOperation.CREATE);
+                entity.setPayload(anyTO);
+                entity.setError(result.getMessage());
+                entity.setInstant(new Date());
+                entity.setRemoteName(delta.getObject().getName().getNameValue());
+                entity.setPullTask(profile.getTask());
+
+                remediationDAO.save(entity);
+            }
         }
 
         finalize(operation, resultStatus, null, output, delta);
@@ -343,8 +366,9 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                     resultStatus = Result.FAILURE;
                     output = null;
                 } else {
+                    AnyPatch anyPatch = null;
                     try {
-                        AnyPatch anyPatch = connObjectUtils.getAnyPatch(
+                        anyPatch = connObjectUtils.getAnyPatch(
                                 before.getKey(),
                                 delta.getObject(),
                                 before,
@@ -384,6 +408,19 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                                 provision.getAnyType().getKey(), delta.getUid().getUidValue(), e);
                         output = e;
                         resultStatus = Result.FAILURE;
+
+                        if (profile.getTask().isRemediation()) {
+                            Remediation entity = entityFactory.newEntity(Remediation.class);
+                            entity.setAnyTypeKind(provision.getAnyType().getKind());
+                            entity.setOperation(ResourceOperation.UPDATE);
+                            entity.setPayload(anyPatch);
+                            entity.setError(result.getMessage());
+                            entity.setInstant(new Date());
+                            entity.setRemoteName(delta.getObject().getName().getNameValue());
+                            entity.setPullTask(profile.getTask());
+
+                            remediationDAO.save(entity);
+                        }
                     }
                 }
                 finalize(MatchingRule.toEventName(MatchingRule.UPDATE),
@@ -659,6 +696,19 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                         result.setMessage(ExceptionUtils.getRootCauseMessage(e));
                         LOG.error("Could not delete {} {}", provision.getAnyType().getKey(), key, e);
                         output = e;
+
+                        if (profile.getTask().isRemediation()) {
+                            Remediation entity = entityFactory.newEntity(Remediation.class);
+                            entity.setAnyTypeKind(provision.getAnyType().getKind());
+                            entity.setOperation(ResourceOperation.DELETE);
+                            entity.setPayload(key);
+                            entity.setError(result.getMessage());
+                            entity.setInstant(new Date());
+                            entity.setRemoteName(delta.getObject().getName().getNameValue());
+                            entity.setPullTask(profile.getTask());
+
+                            remediationDAO.save(entity);
+                        }
                     }
 
                     finalize(ResourceOperation.DELETE.name().toLowerCase(), resultStatus, before, output, delta);

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RemediationServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RemediationServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RemediationServiceImpl.java
new file mode 100644
index 0000000..201bb48
--- /dev/null
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RemediationServiceImpl.java
@@ -0,0 +1,116 @@
+/*
+ * 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.rest.cxf.service;
+
+import java.util.Date;
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.RemediationTO;
+import org.apache.syncope.common.rest.api.service.RemediationService;
+import org.apache.syncope.core.logic.RemediationLogic;
+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;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class RemediationServiceImpl extends AbstractServiceImpl implements RemediationService {
+
+    @Autowired
+    private RemediationLogic logic;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private GroupDAO groupDAO;
+
+    @Autowired
+    private AnyObjectDAO anyObjectDAO;
+
+    @Override
+    public List<RemediationTO> list() {
+        return logic.list();
+    }
+
+    @Override
+    public RemediationTO read(final String key) {
+        return logic.read(key);
+    }
+
+    @Override
+    public Response delete(final String key) {
+        logic.delete(key);
+        return Response.noContent().build();
+    }
+
+    @Override
+    public Response remedy(final String key, final AnyTO anyTO) {
+        ProvisioningResult<?> created = logic.remedy(key, anyTO, isNullPriorityAsync());
+        return createResponse(created);
+    }
+
+    private void check(final String key, final String anyKey) {
+        RemediationTO remediation = logic.read(key);
+
+        AnyDAO<?> anyDAO;
+        switch (remediation.getAnyTypeKind()) {
+            case USER:
+            default:
+                anyDAO = userDAO;
+                break;
+
+            case GROUP:
+                anyDAO = groupDAO;
+                break;
+
+            case ANY_OBJECT:
+                anyDAO = anyObjectDAO;
+        }
+
+        Date etagDate = anyDAO.findLastChange(anyKey);
+        if (etagDate == null) {
+            throw new NotFoundException(remediation.getAnyTypeKind().name() + " for " + key);
+        }
+        checkETag(String.valueOf(etagDate.getTime()));
+    }
+
+    @Override
+    public Response remedy(final String key, final AnyPatch anyPatch) {
+        check(key, anyPatch.getKey());
+
+        ProvisioningResult<?> updated = logic.remedy(key, anyPatch, isNullPriorityAsync());
+        return modificationResponse(updated);
+    }
+
+    @Override
+    public Response remedy(final String key, final String anyKey) {
+        check(key, anyKey);
+
+        ProvisioningResult<?> deleted = logic.remedy(key, anyKey, isNullPriorityAsync());
+        return modificationResponse(deleted);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 7143545..3151971 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -86,6 +86,7 @@ import org.apache.syncope.common.rest.api.service.ImplementationService;
 import org.apache.syncope.common.rest.api.service.MailTemplateService;
 import org.apache.syncope.common.rest.api.service.RealmService;
 import org.apache.syncope.common.rest.api.service.RelationshipTypeService;
+import org.apache.syncope.common.rest.api.service.RemediationService;
 import org.apache.syncope.common.rest.api.service.ReportTemplateService;
 import org.apache.syncope.common.rest.api.service.ResourceHistoryService;
 import org.apache.syncope.common.rest.api.service.RoleService;
@@ -241,6 +242,8 @@ public abstract class AbstractITCase {
 
     protected static ImplementationService implementationService;
 
+    protected static RemediationService remediationService;
+
     protected static CamelRouteService camelRouteService;
 
     protected static SAML2SPService saml2SpService;
@@ -311,6 +314,7 @@ public abstract class AbstractITCase {
         schemaService = adminClient.getService(SchemaService.class);
         securityQuestionService = adminClient.getService(SecurityQuestionService.class);
         implementationService = adminClient.getService(ImplementationService.class);
+        remediationService = adminClient.getService(RemediationService.class);
         camelRouteService = adminClient.getService(CamelRouteService.class);
         saml2SpService = adminClient.getService(SAML2SPService.class);
         saml2IdPService = adminClient.getService(SAML2IdPService.class);

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AbstractTaskITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AbstractTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AbstractTaskITCase.java
index 11fbb0b..55c1a85 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AbstractTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AbstractTaskITCase.java
@@ -35,7 +35,6 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.patch.DeassociationPatch;
 import org.apache.syncope.common.lib.to.TaskTO;
 import org.apache.syncope.common.lib.to.ExecTO;
-import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.NotificationTaskTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -102,29 +101,20 @@ public abstract class AbstractTaskITCase extends AbstractITCase {
      * Clean Syncope and LDAP resource status.
      */
     protected void ldapCleanup() {
-        PagedResult<GroupTO> matchingGroups = groupService.search(new AnyQuery.Builder().realm(
-                SyncopeConstants.ROOT_REALM).
+        groupService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
                 fiql(SyncopeClient.getGroupSearchConditionBuilder().is("name").equalTo("testLDAPGroup").query()).
-                build());
-        if (matchingGroups.getSize() > 0) {
-            for (GroupTO group : matchingGroups.getResult()) {
-                groupService.deassociate(new DeassociationPatch.Builder().key(group.getKey()).
-                        action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_LDAP).build());
-                groupService.delete(group.getKey());
-            }
-        }
-        PagedResult<UserTO> matchingUsers = userService.search(
-                new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
-                        fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("pullFromLDAP").
-                                query()).
-                        build());
-        if (matchingUsers.getSize() > 0) {
-            for (UserTO user : matchingUsers.getResult()) {
-                userService.deassociate(new DeassociationPatch.Builder().key(user.getKey()).
-                        action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_LDAP).build());
-                userService.delete(user.getKey());
-            }
-        }
+                build()).getResult().forEach(group -> {
+                    groupService.deassociate(new DeassociationPatch.Builder().key(group.getKey()).
+                            action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_LDAP).build());
+                    groupService.delete(group.getKey());
+                });
+        userService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
+                fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("pullFromLDAP").query()).
+                build()).getResult().forEach(user -> {
+                    userService.deassociate(new DeassociationPatch.Builder().key(user.getKey()).
+                            action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_LDAP).build());
+                    userService.delete(user.getKey());
+                });
     }
 
     protected static ExecTO execTask(

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index 3e6d67a..6522fcb 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -35,6 +35,7 @@ import java.util.Date;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Properties;
 import java.util.Set;
 import java.util.UUID;
@@ -65,6 +66,7 @@ import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.ImplementationTO;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.RemediationTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
@@ -77,6 +79,7 @@ import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.lib.types.PullMode;
+import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
@@ -659,6 +662,87 @@ public class PullTaskITCase extends AbstractTaskITCase {
     }
 
     @Test
+    public void remediation() {
+        // First of all, clear any potential conflict with existing user / group
+        ldapCleanup();
+
+        // 1. create ldap cloned resource, where 'userId' (mandatory on Syncope) is removed from mapping
+        ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
+        ldap.setKey("ldapForRemediation");
+
+        ProvisionTO provision = ldap.getProvision(AnyTypeKind.USER.name()).get();
+        provision.getVirSchemas().clear();
+        provision.getMapping().getItems().removeIf(item -> "userId".equals(item.getIntAttrName()));
+
+        ldap = createResource(ldap);
+
+        // 2. create PullTask with remediation enabled, for the new resource
+        PullTaskTO pullTask = (PullTaskTO) taskService.list(new TaskQuery.Builder(TaskType.PULL).
+                resource(RESOURCE_NAME_LDAP).build()).getResult().get(0);
+        assertNotNull(pullTask);
+        pullTask.setResource(ldap.getKey());
+        pullTask.setRemediation(true);
+        pullTask.getActions().clear();
+
+        Response response = taskService.create(TaskType.PULL, pullTask);
+        if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
+            throw (RuntimeException) clientFactory.getExceptionMapper().fromResponse(response);
+        }
+        pullTask = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
+        assertNotNull(pullTask);
+
+        try {
+            // 3. execute the pull task and verify that:
+            ExecTO execution = execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), 50, false);
+            assertEquals(PropagationTaskExecStatus.SUCCESS, PropagationTaskExecStatus.valueOf(execution.getStatus()));
+
+            // 3a. user was not pulled
+            try {
+                userService.read("pullFromLDAP");
+                fail("This should never happen");
+            } catch (SyncopeClientException e) {
+                assertEquals(ClientExceptionType.NotFound, e.getType());
+            }
+
+            // 3b. remediation was created
+            Optional<RemediationTO> remediation = remediationService.list().stream().
+                    filter(r -> "uid=pullFromLDAP,ou=People,o=isp".equalsIgnoreCase(r.getRemoteName())).
+                    findFirst();
+            assertTrue(remediation.isPresent());
+            assertEquals(AnyTypeKind.USER, remediation.get().getAnyTypeKind());
+            assertEquals(ResourceOperation.CREATE, remediation.get().getOperation());
+            assertNotNull(remediation.get().getAnyTOPayload());
+            assertNull(remediation.get().getAnyPatchPayload());
+            assertNull(remediation.get().getKeyPayload());
+            assertTrue(remediation.get().getError().contains("RequiredValuesMissing [userId]"));
+
+            // 4. remedy by copying the email value to userId
+            UserTO user = (UserTO) remediation.get().getAnyTOPayload();
+            user.getResources().clear();
+
+            String email = user.getPlainAttr("email").get().getValues().get(0);
+            user.getPlainAttrs().add(new AttrTO.Builder().schema("userId").value(email).build());
+
+            remediationService.remedy(remediation.get().getKey(), user);
+
+            // 5. user is now found
+            user = userService.read("pullFromLDAP");
+            assertNotNull(user);
+            assertEquals(email, user.getPlainAttr("userId").get().getValues().get(0));
+
+            // 6. remediation was removed
+            try {
+                remediationService.read(remediation.get().getKey());
+                fail("This should never happen");
+            } catch (SyncopeClientException e) {
+                assertEquals(ClientExceptionType.NotFound, e.getType());
+            }
+        } finally {
+            resourceService.delete(ldap.getKey());
+        }
+    }
+
+    @Test
     public void issueSYNCOPE68() {
         //-----------------------------
         // Create a new user ... it should be updated applying pull policy


[2/2] syncope git commit: [SYNCOPE-1287] Core implementation

Posted by il...@apache.org.
[SYNCOPE-1287] Core implementation


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/51b314fe
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/51b314fe
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/51b314fe

Branch: refs/heads/master
Commit: 51b314fe514695a79b3f0e39e8b1f06dd49dbebc
Parents: 59a3422
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Wed Mar 28 16:53:12 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Wed Mar 28 16:53:12 2018 +0200

----------------------------------------------------------------------
 .../console/tasks/SchedTaskWizardBuilder.java   |   6 +-
 .../tasks/SchedTaskWizardBuilder$Profile.html   |   1 +
 .../SchedTaskWizardBuilder$Profile.properties   |   1 +
 ...SchedTaskWizardBuilder$Profile_it.properties |   1 +
 ...SchedTaskWizardBuilder$Profile_ja.properties |   1 +
 ...edTaskWizardBuilder$Profile_pt_BR.properties |   1 +
 ...SchedTaskWizardBuilder$Profile_ru.properties |   1 +
 .../syncope/common/lib/to/PullTaskTO.java       |  11 +
 .../syncope/common/lib/to/RemediationTO.java    | 165 ++++++++++++++
 .../syncope/common/lib/types/AnyTypeKind.java   |  19 +-
 .../common/lib/types/EntityViolationType.java   |  12 ++
 .../common/lib/types/StandardEntitlement.java   |   8 +
 .../rest/api/service/ImplementationService.java |   6 +-
 .../rest/api/service/RemediationService.java    | 213 +++++++++++++++++++
 .../common/rest/api/service/UserService.java    |  27 +--
 .../syncope/core/logic/AccessTokenLogic.java    |   2 +-
 .../syncope/core/logic/AnyTypeClassLogic.java   |   3 +-
 .../apache/syncope/core/logic/AnyTypeLogic.java |   2 +-
 .../syncope/core/logic/ApplicationLogic.java    |   3 +-
 .../core/logic/ConnectorHistoryLogic.java       |  16 +-
 .../syncope/core/logic/ConnectorLogic.java      |  49 ++---
 .../apache/syncope/core/logic/DomainLogic.java  |   5 +-
 .../syncope/core/logic/DynRealmLogic.java       |   6 +-
 .../syncope/core/logic/ImplementationLogic.java |   6 +-
 .../apache/syncope/core/logic/LoggerLogic.java  |   2 +
 .../syncope/core/logic/MailTemplateLogic.java   |   3 +
 .../syncope/core/logic/NotificationLogic.java   |   6 +-
 .../apache/syncope/core/logic/PolicyLogic.java  |   5 +-
 .../apache/syncope/core/logic/RealmLogic.java   |   2 +
 .../core/logic/RelationshipTypeLogic.java       |   6 +-
 .../syncope/core/logic/RemediationLogic.java    | 210 ++++++++++++++++++
 .../apache/syncope/core/logic/ReportLogic.java  |   5 +-
 .../syncope/core/logic/ReportTemplateLogic.java |   3 +
 .../core/logic/ResourceHistoryLogic.java        |  16 +-
 .../syncope/core/logic/ResourceLogic.java       |   3 +-
 .../apache/syncope/core/logic/RoleLogic.java    |   2 +-
 .../apache/syncope/core/logic/SchemaLogic.java  |   3 +
 .../core/logic/SecurityQuestionLogic.java       |   3 +-
 .../apache/syncope/core/logic/SyncopeLogic.java |  14 +-
 .../apache/syncope/core/logic/TaskLogic.java    |   3 +
 .../syncope/core/logic/UserWorkflowLogic.java   |   6 +-
 .../validation/InvalidEntityException.java      |   2 +
 .../persistence/api/dao/RemediationDAO.java     |  39 ++++
 .../persistence/api/entity/Remediation.java     |  65 ++++++
 .../persistence/api/entity/task/PullTask.java   |   5 +
 .../persistence/jpa/dao/JPARemediationDAO.java  |  73 +++++++
 .../core/persistence/jpa/dao/JPATaskDAO.java    |  11 +
 .../jpa/entity/JPAEntityFactory.java            |   3 +
 .../persistence/jpa/entity/JPARemediation.java  | 171 +++++++++++++++
 .../jpa/entity/task/JPAPullTask.java            |  18 ++
 .../jpa/validation/entity/RemediationCheck.java |  41 ++++
 .../validation/entity/RemediationValidator.java |  80 +++++++
 .../persistence/jpa/inner/RemediationTest.java  | 103 +++++++++
 .../api/data/ConnInstanceDataBinder.java        |   4 +
 .../api/data/RemediationDataBinder.java         |  28 +++
 .../api/data/ResourceDataBinder.java            |   3 +
 .../java/data/ConnInstanceDataBinderImpl.java   |  12 ++
 .../java/data/RemediationDataBinderImpl.java    |  65 ++++++
 .../java/data/ResourceDataBinderImpl.java       |  12 ++
 .../java/data/TaskDataBinderImpl.java           |   4 +
 .../pushpull/AbstractPullResultHandler.java     |  52 ++++-
 .../cxf/service/RemediationServiceImpl.java     | 116 ++++++++++
 .../org/apache/syncope/fit/AbstractITCase.java  |   4 +
 .../syncope/fit/core/AbstractTaskITCase.java    |  36 ++--
 .../apache/syncope/fit/core/PullTaskITCase.java |  84 ++++++++
 65 files changed, 1755 insertions(+), 133 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
index 8efd140..a23f39b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
@@ -240,8 +240,12 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> extends AjaxWizardBui
             destinationRealm.setNullValid(!(taskTO instanceof PullTaskTO));
             pullTaskSpecifics.add(destinationRealm);
 
+            AjaxCheckBoxPanel remediation = new AjaxCheckBoxPanel(
+                    "remediation", "remediation", new PropertyModel<>(taskTO, "remediation"), false);
+            pullTaskSpecifics.add(remediation);
+
             // ------------------------------
-            // Only for pull tasks
+            // Only for push tasks
             // ------------------------------  
             WebMarkupContainer pushTaskSpecifics = new WebMarkupContainer("pushTaskSpecifics");
             add(pushTaskSpecifics.setRenderBodyOnly(true));

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.html b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.html
index d45da13..4eabfb5 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.html
@@ -28,6 +28,7 @@ under the License.
       <div class="form-group"><span wicket:id="destinationRealm">[destinationRealm]</span></div>
       <div class="form-group"><span wicket:id="pullMode">[pullMode]</span></div>
       <div class="form-group"><span wicket:id="reconFilterBuilder">[filter]</span></div>
+      <div class="form-group"><span wicket:id="remediation">[remediation]</span></div>
     </span>      
 
     <span wicket:id="pushTaskSpecifics">

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.properties b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.properties
index 8200501..2acbca2 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile.properties
@@ -31,3 +31,4 @@ edit=Edit
 execute=Execute
 executeDryRun=Dry run
 latestExecStatus=Last status
+remediation=Remediation

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_it.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_it.properties
index 5cddafd..bc2c4bd 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_it.properties
@@ -31,3 +31,4 @@ edit=Edit
 execute=Execute
 executeDryRun=Dry run
 latestExecStatus=Last status
+remediation=Remediation

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ja.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ja.properties
index b1b725c..1204172 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ja.properties
@@ -31,3 +31,4 @@ edit=\u7de8\u96c6
 execute=\u5b9f\u884c
 executeDryRun=\u4e88\u884c\u6f14\u7fd2
 latestExecStatus=\u6700\u7d42\u30b9\u30c6\u30fc\u30bf\u30b9
+remediation=Remediation

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_pt_BR.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_pt_BR.properties
index 8200501..2acbca2 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_pt_BR.properties
@@ -31,3 +31,4 @@ edit=Edit
 execute=Execute
 executeDryRun=Dry run
 latestExecStatus=Last status
+remediation=Remediation

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ru.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ru.properties
index 3b79aad..18bbca6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder$Profile_ru.properties
@@ -32,3 +32,4 @@ edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c
 execute=\u0417\u0430\u043f\u0443\u0441\u043a
 executeDryRun=\u041f\u0440\u043e\u0431\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a
 latestExecStatus=\u0421\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430
+remediation=Remediation

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/lib/src/main/java/org/apache/syncope/common/lib/to/PullTaskTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/PullTaskTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/PullTaskTO.java
index 2638ad6..3981200 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/PullTaskTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/PullTaskTO.java
@@ -50,6 +50,8 @@ public class PullTaskTO extends ProvisioningTaskTO implements TemplatableTO {
     @XmlElement(required = true)
     private String destinationRealm;
 
+    private boolean remediation;
+
     @XmlTransient
     @JsonProperty("@class")
     @Schema(name = "@class", required = true, example = "org.apache.syncope.common.lib.to.PullTaskTO")
@@ -90,4 +92,13 @@ public class PullTaskTO extends ProvisioningTaskTO implements TemplatableTO {
     public Map<String, AnyTO> getTemplates() {
         return templates;
     }
+
+    public boolean isRemediation() {
+        return remediation;
+    }
+
+    public void setRemediation(final boolean remediation) {
+        this.remediation = remediation;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/lib/src/main/java/org/apache/syncope/common/lib/to/RemediationTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RemediationTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RemediationTO.java
new file mode 100644
index 0000000..0b971cd
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RemediationTO.java
@@ -0,0 +1,165 @@
+/*
+ * 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.common.lib.to;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Date;
+import javax.ws.rs.PathParam;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+
+@XmlRootElement(name = "remediation")
+@XmlType
+public class RemediationTO extends AbstractBaseBean implements EntityTO {
+
+    private static final long serialVersionUID = 3983540425142284975L;
+
+    private String key;
+
+    private AnyTypeKind anyTypeKind;
+
+    private ResourceOperation operation;
+
+    private AnyTO anyTOPayload;
+
+    private AnyPatch anyPatchPayload;
+
+    private String keyPayload;
+
+    private String error;
+
+    private Date instant;
+
+    private String pullTask;
+
+    private String resource;
+
+    private String remoteName;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @PathParam("key")
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public AnyTypeKind getAnyTypeKind() {
+        return anyTypeKind;
+    }
+
+    public void setAnyTypeKind(final AnyTypeKind anyTypeKind) {
+        this.anyTypeKind = anyTypeKind;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public ResourceOperation getOperation() {
+        return operation;
+    }
+
+    public void setOperation(final ResourceOperation operation) {
+        this.operation = operation;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public AnyTO getAnyTOPayload() {
+        return anyTOPayload;
+    }
+
+    public void setAnyTOPayload(final AnyTO anyTOPayload) {
+        this.anyTOPayload = anyTOPayload;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public AnyPatch getAnyPatchPayload() {
+        return anyPatchPayload;
+    }
+
+    public void setAnyPatchPayload(final AnyPatch anyPatchPayload) {
+        this.anyPatchPayload = anyPatchPayload;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public String getKeyPayload() {
+        return keyPayload;
+    }
+
+    public void setKeyPayload(final String keyPayload) {
+        this.keyPayload = keyPayload;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public String getError() {
+        return error;
+    }
+
+    public void setError(final String error) {
+        this.error = error;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public Date getInstant() {
+        return instant == null
+                ? null
+                : new Date(instant.getTime());
+    }
+
+    public void setInstant(final Date instant) {
+        this.instant = instant == null
+                ? null
+                : new Date(instant.getTime());
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public String getPullTask() {
+        return pullTask;
+    }
+
+    public void setPullTask(final String pullTask) {
+        this.pullTask = pullTask;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(final String resource) {
+        this.resource = resource;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+
+    public String getRemoteName() {
+        return remoteName;
+    }
+
+    public void setRemoteName(final String remoteName) {
+        this.remoteName = remoteName;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/lib/src/main/java/org/apache/syncope/common/lib/types/AnyTypeKind.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/AnyTypeKind.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/AnyTypeKind.java
index 12fbf59..6cd6c98 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/AnyTypeKind.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/AnyTypeKind.java
@@ -19,6 +19,10 @@
 package org.apache.syncope.common.lib.types;
 
 import javax.xml.bind.annotation.XmlEnum;
+import org.apache.syncope.common.lib.patch.AnyObjectPatch;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.GroupPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.GroupTO;
@@ -27,18 +31,25 @@ import org.apache.syncope.common.lib.to.UserTO;
 @XmlEnum
 public enum AnyTypeKind {
 
-    USER(UserTO.class),
-    GROUP(GroupTO.class),
-    ANY_OBJECT(AnyObjectTO.class);
+    USER(UserTO.class, UserPatch.class),
+    GROUP(GroupTO.class, GroupPatch.class),
+    ANY_OBJECT(AnyObjectTO.class, AnyObjectPatch.class);
 
     private final Class<? extends AnyTO> toClass;
 
-    AnyTypeKind(final Class<? extends AnyTO> toClass) {
+    private final Class<? extends AnyPatch> patchClass;
+
+    AnyTypeKind(final Class<? extends AnyTO> toClass, final Class<? extends AnyPatch> patchClass) {
         this.toClass = toClass;
+        this.patchClass = patchClass;
     }
 
     public Class<? extends AnyTO> getTOClass() {
         return toClass;
     }
 
+    public Class<? extends AnyPatch> getPatchClass() {
+        return patchClass;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java
index 6cc7e7c..462ee5f 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/EntityViolationType.java
@@ -47,10 +47,13 @@ public enum EntityViolationType {
     InvalidPlainAttr("org.apache.syncope.core.persistence.validation.plainattr"),
     InvalidUsername("org.apache.syncope.core.persistence.validation.user.username"),
     InvalidValueList("org.apache.syncope.core.persistence.validation.attr.valueList"),
+    InvalidRemediation("org.apache.syncope.core.persistence.validation.remediation"),
     MoreThanOneNonNull("org.apache.syncope.core.persistence.validation.attrvalue.moreThanOneNonNull");
 
     private String message;
 
+    private String propertyPath;
+
     EntityViolationType(final String message) {
         this.message = message;
     }
@@ -62,4 +65,13 @@ public enum EntityViolationType {
     public String getMessage() {
         return message;
     }
+
+    public String getPropertyPath() {
+        return propertyPath;
+    }
+
+    public void setPropertyPath(final String propertyPath) {
+        this.propertyPath = propertyPath;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
index 00a6638..0968f34 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/StandardEntitlement.java
@@ -302,6 +302,14 @@ public final class StandardEntitlement {
 
     public static final String IMPLEMENTATION_DELETE = "IMPLEMENTATION_DELETE";
 
+    public static final String REMEDIATION_LIST = "REMEDIATION_LIST";
+
+    public static final String REMEDIATION_READ = "REMEDIATION_READ";
+
+    public static final String REMEDIATION_REMEDY = "REMEDIATION_REMEDY";
+
+    public static final String REMEDIATION_DELETE = "REMEDIATION_DELETE";
+
     private static final Set<String> VALUES;
 
     static {

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ImplementationService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ImplementationService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ImplementationService.java
index 620d3fc..cf6352e 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ImplementationService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ImplementationService.java
@@ -47,7 +47,7 @@ import org.apache.syncope.common.lib.types.ImplementationType;
 public interface ImplementationService extends JAXRSService {
 
     /**
-     * Returns a list of all implementations.
+     * Returns a list of all implementations of the given type.
      *
      * @param type implementation type
      * @return list of all implementations.
@@ -58,7 +58,7 @@ public interface ImplementationService extends JAXRSService {
     List<ImplementationTO> list(@NotNull @PathParam("type") ImplementationType type);
 
     /**
-     * Returns implementation with matching key.
+     * Returns implementation with matching type and key.
      *
      * @param type implementation type
      * @param key key of implementation to be read
@@ -94,7 +94,7 @@ public interface ImplementationService extends JAXRSService {
     Response update(@NotNull ImplementationTO implementationTO);
 
     /**
-     * Deletes the implementation matching the given key.
+     * Deletes the implementation matching the given key and type.
      *
      * @param type implementation type
      * @param key key for implementation to be deleted

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RemediationService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RemediationService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RemediationService.java
new file mode 100644
index 0000000..936eca5
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RemediationService.java
@@ -0,0 +1,213 @@
+/*
+ * 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.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PATCH;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.RemediationTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for remediations.
+ */
+@Tag(name = "Remediations")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("remediations")
+public interface RemediationService extends JAXRSService {
+
+    /**
+     * Returns a list of all remediations.
+     *
+     * @return list of all remediations.
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    List<RemediationTO> list();
+
+    /**
+     * Returns remediation with matching key.
+     *
+     * @param key key of remediation to be read
+     * @return remediation with matching key
+     */
+    @GET
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    RemediationTO read(@NotNull @PathParam("key") String key);
+
+    /**
+     * Deletes the remediation matching the given key.
+     *
+     * @param key key for remediation to be deleted
+     * @return an empty response if operation was successful
+     */
+    @DELETE
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    Response delete(@NotNull @PathParam("key") String key);
+
+    /**
+     * Perform remediation by creating the provided user, group or any object.
+     *
+     * @param key key for remediation to act on
+     * @param anyTO user, group or any object to create
+     * @return Response object featuring Location header of created object as well as the object itself
+     * enriched with propagation status information
+     */
+    @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
+            description = "Allows client to specify a preference for the result to be returned from the server",
+            allowEmptyValue = true, schema =
+            @Schema(defaultValue = "return-content", allowableValues = { "return-content", "return-no-content" }))
+    @Parameter(name = RESTHeaders.NULL_PRIORITY_ASYNC, in = ParameterIn.HEADER,
+            description = "If 'true', instructs the propagation process not to wait for completion when communicating"
+            + " with External Resources with no priority set",
+            allowEmptyValue = true, schema =
+            @Schema(type = "boolean", defaultValue = "false"))
+    @ApiResponses(
+            @ApiResponse(responseCode = "201",
+                    description = "Object successfully created enriched with propagation status information, as Entity,"
+                    + "or empty if 'Prefer: return-no-content' was specified",
+                    content =
+                    @Content(schema =
+                            @Schema(implementation = ProvisioningResult.class)), headers = {
+                @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+                        @Schema(type = "string"),
+                        description = "UUID generated for the object created"),
+                @Header(name = HttpHeaders.LOCATION, schema =
+                        @Schema(type = "string"),
+                        description = "URL of the object created"),
+                @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
+                        @Schema(type = "string"),
+                        description = "Allows the server to inform the "
+                        + "client about the fact that a specified preference was applied") }))
+    @POST
+    @Path("{key}")
+    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    Response remedy(@NotNull @PathParam("key") String key, @NotNull AnyTO anyTO);
+
+    /**
+     * Perform remediation by updating the provided user, group or any object.
+     *
+     * @param key key for remediation to act on
+     * @param anyPatch user, group or any object to update
+     * @return Response object featuring the updated object enriched with propagation status information
+     */
+    @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
+            description = "Allows client to specify a preference for the result to be returned from the server",
+            allowEmptyValue = true, schema =
+            @Schema(defaultValue = "return-content", allowableValues = { "return-content", "return-no-content" }))
+    @Parameter(name = HttpHeaders.IF_MATCH, in = ParameterIn.HEADER,
+            description = "When the provided ETag value does not match the latest modification date of the entity, "
+            + "an error is reported and the requested operation is not performed.",
+            allowEmptyValue = true, schema =
+            @Schema(type = "string"))
+    @Parameter(name = RESTHeaders.NULL_PRIORITY_ASYNC, in = ParameterIn.HEADER,
+            description = "If 'true', instructs the propagation process not to wait for completion when communicating"
+            + " with External Resources with no priority set",
+            allowEmptyValue = true, schema =
+            @Schema(type = "boolean", defaultValue = "false"))
+    @ApiResponses({
+        @ApiResponse(responseCode = "200",
+                description = "Object successfully updated enriched with propagation status information, as Entity",
+                content =
+                @Content(schema =
+                        @Schema(implementation = ProvisioningResult.class))),
+        @ApiResponse(responseCode = "204",
+                description = "No content if 'Prefer: return-no-content' was specified", headers =
+                @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
+                        @Schema(type = "string"),
+                        description = "Allows the server to inform the "
+                        + "client about the fact that a specified preference was applied")),
+        @ApiResponse(responseCode = "412",
+                description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
+                + " date of the entity") })
+    @PATCH
+    @Path("{key}")
+    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    Response remedy(@NotNull @PathParam("key") String key, @NotNull AnyPatch anyPatch);
+
+    /**
+     * Perform remediation by deleting the provided user, group or any object.
+     *
+     * @param key key for remediation to act on
+     * @param anyKey user's, group's or any object's key to delete
+     * @return Response object featuring the deleted object enriched with propagation status information
+     */
+    @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
+            description = "Allows client to specify a preference for the result to be returned from the server",
+            allowEmptyValue = true, schema =
+            @Schema(defaultValue = "return-content", allowableValues = { "return-content", "return-no-content" }))
+    @Parameter(name = HttpHeaders.IF_MATCH, in = ParameterIn.HEADER,
+            description = "When the provided ETag value does not match the latest modification date of the entity, "
+            + "an error is reported and the requested operation is not performed.",
+            allowEmptyValue = true, schema =
+            @Schema(type = "string"))
+    @Parameter(name = RESTHeaders.NULL_PRIORITY_ASYNC, in = ParameterIn.HEADER,
+            description = "If 'true', instructs the propagation process not to wait for completion when communicating"
+            + " with External Resources with no priority set",
+            allowEmptyValue = true, schema =
+            @Schema(type = "boolean", defaultValue = "false"))
+    @ApiResponses({
+        @ApiResponse(responseCode = "200",
+                description = "Object successfully deleted enriched with propagation status information, as Entity",
+                content =
+                @Content(schema =
+                        @Schema(implementation = ProvisioningResult.class))),
+        @ApiResponse(responseCode = "204",
+                description = "No content if 'Prefer: return-no-content' was specified", headers =
+                @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
+                        @Schema(type = "string"),
+                        description = "Allows the server to inform the "
+                        + "client about the fact that a specified preference was applied")),
+        @ApiResponse(responseCode = "412",
+                description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
+                + " date of the entity") })
+    @DELETE
+    @Path("{key}/{anyKey}")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    Response remedy(@NotNull @PathParam("key") String key, @NotNull String anyKey);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
index 3bb0148..7a52fa5 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserService.java
@@ -53,8 +53,7 @@ import org.apache.syncope.common.rest.api.beans.AnyQuery;
  */
 @Tag(name = "Users")
 @SecurityRequirements({
-    @SecurityRequirement(name = "BasicAuthentication")
-    ,
+    @SecurityRequirement(name = "BasicAuthentication"),
     @SecurityRequirement(name = "Bearer") })
 @Path("users")
 public interface UserService extends AnyService<UserTO> {
@@ -99,12 +98,10 @@ public interface UserService extends AnyService<UserTO> {
                             @Schema(implementation = ProvisioningResult.class)), headers = {
                 @Header(name = RESTHeaders.RESOURCE_KEY, schema =
                         @Schema(type = "string"),
-                        description = "UUID generated for the user created")
-                ,
+                        description = "UUID generated for the user created"),
                 @Header(name = HttpHeaders.LOCATION, schema =
                         @Schema(type = "string"),
-                        description = "URL of the user created")
-                ,
+                        description = "URL of the user created"),
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
@@ -141,15 +138,13 @@ public interface UserService extends AnyService<UserTO> {
                 description = "User successfully updated enriched with propagation status information, as Entity",
                 content =
                 @Content(schema =
-                        @Schema(implementation = ProvisioningResult.class)))
-        ,
+                        @Schema(implementation = ProvisioningResult.class))),
         @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
-                        + "client about the fact that a specified preference was applied"))
-        ,
+                        + "client about the fact that a specified preference was applied")),
         @ApiResponse(responseCode = "412",
                 description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
                 + " date of the entity") })
@@ -185,15 +180,13 @@ public interface UserService extends AnyService<UserTO> {
                 description = "User successfully updated enriched with propagation status information, as Entity",
                 content =
                 @Content(schema =
-                        @Schema(implementation = ProvisioningResult.class)))
-        ,
+                        @Schema(implementation = ProvisioningResult.class))),
         @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
-                        + "client about the fact that a specified preference was applied"))
-        ,
+                        + "client about the fact that a specified preference was applied")),
         @ApiResponse(responseCode = "412",
                 description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
                 + " date of the entity") })
@@ -229,15 +222,13 @@ public interface UserService extends AnyService<UserTO> {
                 description = "User successfully updated enriched with propagation status information, as Entity",
                 content =
                 @Content(schema =
-                        @Schema(implementation = ProvisioningResult.class)))
-        ,
+                        @Schema(implementation = ProvisioningResult.class))),
         @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
                         @Schema(type = "string"),
                         description = "Allows the server to inform the "
-                        + "client about the fact that a specified preference was applied"))
-        ,
+                        + "client about the fact that a specified preference was applied")),
         @ApiResponse(responseCode = "412",
                 description = "The ETag value provided via the 'If-Match' header does not match the latest modification"
                 + " date of the entity") })

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
index aa71fbd..0c00b45 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
@@ -113,7 +113,7 @@ public class AccessTokenLogic extends AbstractTransactionalLogic<AccessTokenTO>
         Integer count = accessTokenDAO.count();
 
         List<AccessTokenTO> result = accessTokenDAO.findAll(page, size, orderByClauses).stream().
-                map(accessToken -> binder.getAccessTokenTO(accessToken)).collect(Collectors.toList());
+                map(binder::getAccessTokenTO).collect(Collectors.toList());
 
         return Pair.of(count, result);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeClassLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeClassLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeClassLogic.java
index 03f6909..adab632 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeClassLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeClassLogic.java
@@ -62,8 +62,7 @@ public class AnyTypeClassLogic extends AbstractTransactionalLogic<AnyTypeClassTO
     @PreAuthorize("hasRole('" + StandardEntitlement.ANYTYPECLASS_LIST + "')")
     @Transactional(readOnly = true)
     public List<AnyTypeClassTO> list() {
-        return anyTypeClassDAO.findAll().stream().
-                map(anyTypeClass -> binder.getAnyTypeClassTO(anyTypeClass)).collect(Collectors.toList());
+        return anyTypeClassDAO.findAll().stream().map(binder::getAnyTypeClassTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.ANYTYPECLASS_CREATE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeLogic.java
index 95944c9..d606af2 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AnyTypeLogic.java
@@ -66,7 +66,7 @@ public class AnyTypeLogic extends AbstractTransactionalLogic<AnyTypeTO> {
     @PreAuthorize("hasRole('" + StandardEntitlement.ANYTYPE_LIST + "')")
     @Transactional(readOnly = true)
     public List<AnyTypeTO> list() {
-        return anyTypeDAO.findAll().stream().map(anyType -> binder.getAnyTypeTO(anyType)).collect(Collectors.toList());
+        return anyTypeDAO.findAll().stream().map(binder::getAnyTypeTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.ANYTYPE_CREATE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
index e25f532..50495ca 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ApplicationLogic.java
@@ -73,8 +73,7 @@ public class ApplicationLogic extends AbstractTransactionalLogic<ApplicationTO>
     @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_LIST + "')")
     @Transactional(readOnly = true)
     public List<ApplicationTO> list() {
-        return applicationDAO.findAll().stream().
-                map(application -> binder.getApplicationTO(application)).collect(Collectors.toList());
+        return applicationDAO.findAll().stream().map(binder::getApplicationTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.APPLICATION_CREATE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorHistoryLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorHistoryLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorHistoryLogic.java
index e7ff07c..e3a22a8 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorHistoryLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorHistoryLogic.java
@@ -32,6 +32,7 @@ import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class ConnectorHistoryLogic extends AbstractTransactionalLogic<ConnInstanceHistoryConfTO> {
@@ -45,17 +46,8 @@ public class ConnectorHistoryLogic extends AbstractTransactionalLogic<ConnInstan
     @Autowired
     private ConnInstanceDataBinder binder;
 
-    private ConnInstanceHistoryConfTO getConnInstanceHistoryConfTO(final ConnInstanceHistoryConf history) {
-        ConnInstanceHistoryConfTO historyTO = new ConnInstanceHistoryConfTO();
-        historyTO.setKey(history.getKey());
-        historyTO.setCreator(history.getCreator());
-        historyTO.setCreation(history.getCreation());
-        historyTO.setConnInstanceTO(history.getConf());
-
-        return historyTO;
-    }
-
     @PreAuthorize("hasRole('" + StandardEntitlement.CONNECTOR_HISTORY_LIST + "')")
+    @Transactional(readOnly = true)
     public List<ConnInstanceHistoryConfTO> list(final String key) {
         ConnInstance connInstance = connInstanceDAO.find(key);
         if (connInstance == null) {
@@ -63,7 +55,7 @@ public class ConnectorHistoryLogic extends AbstractTransactionalLogic<ConnInstan
         }
 
         return connInstanceHistoryConfDAO.findByEntity(connInstance).stream().
-                map(historyConf -> getConnInstanceHistoryConfTO(historyConf)).collect(Collectors.toList());
+                map(binder::getConnInstanceHistoryConfTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.CONNECTOR_HISTORY_RESTORE + "')")
@@ -93,7 +85,7 @@ public class ConnectorHistoryLogic extends AbstractTransactionalLogic<ConnInstan
         if (!"list".equals(method.getName())) {
             try {
                 String key = (String) args[0];
-                return getConnInstanceHistoryConfTO(connInstanceHistoryConfDAO.find(key));
+                return binder.getConnInstanceHistoryConfTO(connInstanceHistoryConfDAO.find(key));
             } catch (Throwable ignore) {
                 LOG.debug("Unresolved reference", ignore);
                 throw new UnresolvedReferenceException(ignore);

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
index 75d704e..c5bbdf8 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java
@@ -19,11 +19,9 @@
 package org.apache.syncope.core.logic;
 
 import java.lang.reflect.Method;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
@@ -47,8 +45,8 @@ import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.identityconnectors.common.l10n.CurrentLocale;
 import org.identityconnectors.framework.api.ConfigurationProperties;
-import org.identityconnectors.framework.api.ConnectorInfoManager;
 import org.identityconnectors.framework.api.ConnectorKey;
+import org.identityconnectors.framework.common.objects.AttributeInfo;
 import org.identityconnectors.framework.common.objects.AttributeUtil;
 import org.identityconnectors.framework.common.objects.ObjectClassInfo;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -149,9 +147,8 @@ public class ConnectorLogic extends AbstractTransactionalLogic<ConnInstanceTO> {
                     try {
                         result = binder.getConnInstanceTO(connInstance);
                     } catch (NotFoundException e) {
-                        LOG.
-                                error("Connector '{}#{}' not found", connInstance.getBundleName(), connInstance.
-                                        getVersion());
+                        LOG.error("Connector '{}#{}' not found",
+                                connInstance.getBundleName(), connInstance.getVersion());
                     }
 
                     return result;
@@ -181,29 +178,31 @@ public class ConnectorLogic extends AbstractTransactionalLogic<ConnInstanceTO> {
         }
 
         List<ConnBundleTO> connectorBundleTOs = new ArrayList<>();
-        for (Map.Entry<URI, ConnectorInfoManager> entry : connIdBundleManager.getConnInfoManagers().entrySet()) {
-            entry.getValue().getConnectorInfos().stream().map(bundle -> {
+        connIdBundleManager.getConnInfoManagers().forEach((uri, cim) -> {
+            connectorBundleTOs.addAll(cim.getConnectorInfos().stream().map(bundle -> {
                 ConnBundleTO connBundleTO = new ConnBundleTO();
                 connBundleTO.setDisplayName(bundle.getConnectorDisplayName());
-                connBundleTO.setLocation(entry.getKey().toString());
+
+                connBundleTO.setLocation(uri.toString());
+
                 ConnectorKey key = bundle.getConnectorKey();
                 connBundleTO.setBundleName(key.getBundleName());
                 connBundleTO.setConnectorName(key.getConnectorName());
                 connBundleTO.setVersion(key.getBundleVersion());
+
                 ConfigurationProperties properties = connIdBundleManager.getConfigurationProperties(bundle);
-                for (String propName : properties.getPropertyNames()) {
-                    connBundleTO.getProperties().add(binder.build(properties.getProperty(propName)));
-                }
+                connBundleTO.getProperties().addAll(properties.getPropertyNames().stream().
+                        map(propName -> binder.build(properties.getProperty(propName))).collect(Collectors.toList()));
+
                 return connBundleTO;
-            }).forEachOrdered(connBundleTO -> {
-                connectorBundleTOs.add(connBundleTO);
-            });
-        }
+            }).collect(Collectors.toList()));
+        });
 
         return connectorBundleTOs;
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.CONNECTOR_READ + "')")
+
     public List<ConnIdObjectClassTO> buildObjectClassInfo(
             final ConnInstanceTO connInstanceTO, final boolean includeSpecial) {
 
@@ -217,23 +216,19 @@ public class ConnectorLogic extends AbstractTransactionalLogic<ConnInstanceTO> {
                 connFactory.buildConnInstanceOverride(actual, connInstanceTO.getConf(), null)).
                 getObjectClassInfo();
 
-        List<ConnIdObjectClassTO> result = new ArrayList<>(objectClassInfo.size());
-        objectClassInfo.stream().map(info -> {
+        return objectClassInfo.stream().map(info -> {
             ConnIdObjectClassTO connIdObjectClassTO = new ConnIdObjectClassTO();
             connIdObjectClassTO.setType(info.getType());
             connIdObjectClassTO.setAuxiliary(info.isAuxiliary());
             connIdObjectClassTO.setContainer(info.isContainer());
-            info.getAttributeInfo().stream().
+
+            connIdObjectClassTO.getAttributes().addAll(info.getAttributeInfo().stream().
                     filter(attrInfo -> includeSpecial || !AttributeUtil.isSpecialName(attrInfo.getName())).
-                    forEachOrdered(attrInfo -> {
-                        connIdObjectClassTO.getAttributes().add(attrInfo.getName());
-                    });
-            return connIdObjectClassTO;
-        }).forEachOrdered((connIdObjectClassTO) -> {
-            result.add(connIdObjectClassTO);
-        });
+                    map(AttributeInfo::getName).
+                    collect(Collectors.toList()));
 
-        return result;
+            return connIdObjectClassTO;
+        }).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.CONNECTOR_READ + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/DomainLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
index d9d29ba..9e3967b 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
@@ -33,6 +33,7 @@ import org.apache.syncope.core.provisioning.api.data.DomainDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class DomainLogic extends AbstractTransactionalLogic<DomainTO> {
@@ -48,6 +49,7 @@ public class DomainLogic extends AbstractTransactionalLogic<DomainTO> {
 
     @PreAuthorize("hasRole('" + StandardEntitlement.DOMAIN_READ + "') and authentication.details.domain == "
             + "T(org.apache.syncope.common.lib.SyncopeConstants).MASTER_DOMAIN")
+    @Transactional(readOnly = true)
     public DomainTO read(final String key) {
         Domain domain = domainDAO.find(key);
         if (domain == null) {
@@ -60,8 +62,9 @@ public class DomainLogic extends AbstractTransactionalLogic<DomainTO> {
     }
 
     @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
     public List<DomainTO> list() {
-        return domainDAO.findAll().stream().map(domain -> binder.getDomainTO(domain)).collect(Collectors.toList());
+        return domainDAO.findAll().stream().map(binder::getDomainTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.DOMAIN_CREATE + "') and authentication.details.domain == "

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/DynRealmLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/DynRealmLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/DynRealmLogic.java
index de9e78a..487cc59 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/DynRealmLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/DynRealmLogic.java
@@ -31,6 +31,7 @@ import org.apache.syncope.core.provisioning.api.data.DynRealmDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class DynRealmLogic extends AbstractTransactionalLogic<DynRealmTO> {
@@ -42,6 +43,7 @@ public class DynRealmLogic extends AbstractTransactionalLogic<DynRealmTO> {
     private DynRealmDAO dynRealmDAO;
 
     @PreAuthorize("hasRole('" + StandardEntitlement.DYNREALM_READ + "')")
+    @Transactional(readOnly = true)
     public DynRealmTO read(final String key) {
         DynRealm dynRealm = dynRealmDAO.find(key);
         if (dynRealm == null) {
@@ -53,9 +55,9 @@ public class DynRealmLogic extends AbstractTransactionalLogic<DynRealmTO> {
         return binder.getDynRealmTO(dynRealm);
     }
 
+    @Transactional(readOnly = true)
     public List<DynRealmTO> list() {
-        return dynRealmDAO.findAll().stream().
-                map(dynRealm -> binder.getDynRealmTO(dynRealm)).collect(Collectors.toList());
+        return dynRealmDAO.findAll().stream().map(binder::getDynRealmTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.DYNREALM_CREATE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java
index 83bc933..3079483 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java
@@ -43,6 +43,7 @@ import org.apache.syncope.core.provisioning.api.data.ImplementationDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class ImplementationLogic extends AbstractTransactionalLogic<ImplementationTO> {
@@ -75,12 +76,13 @@ public class ImplementationLogic extends AbstractTransactionalLogic<Implementati
     private NotificationDAO notificationDAO;
 
     @PreAuthorize("hasRole('" + StandardEntitlement.IMPLEMENTATION_LIST + "')")
+    @Transactional(readOnly = true)
     public List<ImplementationTO> list(final ImplementationType type) {
-        return implementationDAO.find(type).stream().
-                map(implementation -> binder.getImplementationTO(implementation)).collect(Collectors.toList());
+        return implementationDAO.find(type).stream().map(binder::getImplementationTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.IMPLEMENTATION_READ + "')")
+    @Transactional(readOnly = true)
     public ImplementationTO read(final ImplementationType type, final String key) {
         Implementation implementation = implementationDAO.find(key);
         if (implementation == null) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
index 0867c62..c576774 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
@@ -95,6 +95,7 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
 
     @PreAuthorize("hasRole('" + StandardEntitlement.LOG_LIST + "') and authentication.details.domain == "
             + "T(org.apache.syncope.common.lib.SyncopeConstants).MASTER_DOMAIN")
+    @Transactional(readOnly = true)
     public List<LogAppender> memoryAppenders() {
         return loggerLoader.getMemoryAppenders().keySet().stream().map(appender -> {
             LogAppender logAppender = new LogAppender();
@@ -105,6 +106,7 @@ public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
 
     @PreAuthorize("hasRole('" + StandardEntitlement.LOG_READ + "') and authentication.details.domain == "
             + "T(org.apache.syncope.common.lib.SyncopeConstants).MASTER_DOMAIN")
+    @Transactional(readOnly = true)
     public List<LogStatementTO> getLastLogStatements(final String memoryAppender) {
         MemoryAppender appender = loggerLoader.getMemoryAppenders().get(memoryAppender);
         if (appender == null) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/MailTemplateLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/MailTemplateLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/MailTemplateLogic.java
index 3f2b7c8..d33d68a 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/MailTemplateLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/MailTemplateLogic.java
@@ -39,6 +39,7 @@ import org.apache.syncope.core.persistence.api.entity.Notification;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class MailTemplateLogic extends AbstractTransactionalLogic<MailTemplateTO> {
@@ -59,6 +60,7 @@ public class MailTemplateLogic extends AbstractTransactionalLogic<MailTemplateTO
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.MAIL_TEMPLATE_READ + "')")
+    @Transactional(readOnly = true)
     public MailTemplateTO read(final String key) {
         MailTemplate mailTemplate = mailTemplateDAO.find(key);
         if (mailTemplate == null) {
@@ -71,6 +73,7 @@ public class MailTemplateLogic extends AbstractTransactionalLogic<MailTemplateTO
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.MAIL_TEMPLATE_LIST + "')")
+    @Transactional(readOnly = true)
     public List<MailTemplateTO> list() {
         return mailTemplateDAO.findAll().stream().
                 map(template -> getMailTemplateTO(template.getKey())).collect(Collectors.toList());

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/NotificationLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/NotificationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/NotificationLogic.java
index b594613..d9a662f 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/NotificationLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/NotificationLogic.java
@@ -38,6 +38,7 @@ import org.quartz.JobKey;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class NotificationLogic extends AbstractJobLogic<NotificationTO> {
@@ -49,6 +50,7 @@ public class NotificationLogic extends AbstractJobLogic<NotificationTO> {
     private NotificationDataBinder binder;
 
     @PreAuthorize("hasRole('" + StandardEntitlement.NOTIFICATION_READ + "')")
+    @Transactional(readOnly = true)
     public NotificationTO read(final String key) {
         Notification notification = notificationDAO.find(key);
         if (notification == null) {
@@ -61,9 +63,9 @@ public class NotificationLogic extends AbstractJobLogic<NotificationTO> {
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.NOTIFICATION_LIST + "')")
+    @Transactional(readOnly = true)
     public List<NotificationTO> list() {
-        return notificationDAO.findAll().stream().
-                map(notification -> binder.getNotificationTO(notification)).collect(Collectors.toList());
+        return notificationDAO.findAll().stream().map(binder::getNotificationTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.NOTIFICATION_CREATE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java
index 2506a90..29f3052 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java
@@ -36,6 +36,7 @@ import org.apache.syncope.core.provisioning.api.data.PolicyDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class PolicyLogic extends AbstractTransactionalLogic<PolicyTO> {
@@ -76,14 +77,16 @@ public class PolicyLogic extends AbstractTransactionalLogic<PolicyTO> {
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.POLICY_LIST + "')")
+    @Transactional(readOnly = true)
     public <T extends PolicyTO> List<T> list(final PolicyType type) {
         PolicyUtils policyUtils = policyUtilsFactory.getInstance(type);
 
         return policyDAO.find(policyUtils.policyClass()).stream().
-                <T>map(policy -> binder.getPolicyTO(policy)).collect(Collectors.toList());
+                <T>map(binder::getPolicyTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.POLICY_READ + "')")
+    @Transactional(readOnly = true)
     public <T extends PolicyTO> T read(final PolicyType type, final String key) {
         Policy policy = policyDAO.find(key);
         if (policy == null) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
index 4c2a8cc..86a1847 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
@@ -50,6 +50,7 @@ import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
@@ -70,6 +71,7 @@ public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
     private PropagationTaskExecutor taskExecutor;
 
     @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
     public List<RealmTO> list(final String fullPath) {
         Realm realm = realmDAO.findByFullPath(fullPath);
         if (realm == null) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/RelationshipTypeLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/RelationshipTypeLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/RelationshipTypeLogic.java
index 30ac9a2..b0c1ff5 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/RelationshipTypeLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/RelationshipTypeLogic.java
@@ -32,6 +32,7 @@ import org.apache.syncope.core.provisioning.api.data.RelationshipTypeDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class RelationshipTypeLogic extends AbstractTransactionalLogic<RelationshipTypeTO> {
@@ -43,6 +44,7 @@ public class RelationshipTypeLogic extends AbstractTransactionalLogic<Relationsh
     private RelationshipTypeDAO relationshipTypeDAO;
 
     @PreAuthorize("hasRole('" + StandardEntitlement.RELATIONSHIPTYPE_READ + "')")
+    @Transactional(readOnly = true)
     public RelationshipTypeTO read(final String key) {
         RelationshipType relationshipType = relationshipTypeDAO.find(key);
         if (relationshipType == null) {
@@ -55,9 +57,9 @@ public class RelationshipTypeLogic extends AbstractTransactionalLogic<Relationsh
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.RELATIONSHIPTYPE_LIST + "')")
+    @Transactional(readOnly = true)
     public List<RelationshipTypeTO> list() {
-        return relationshipTypeDAO.findAll().stream().
-                map(relationshipType -> binder.getRelationshipTypeTO(relationshipType)).collect(Collectors.toList());
+        return relationshipTypeDAO.findAll().stream().map(binder::getRelationshipTypeTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.RELATIONSHIPTYPE_CREATE + "')")

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/RemediationLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/RemediationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/RemediationLogic.java
new file mode 100644
index 0000000..1fb93e0
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/RemediationLogic.java
@@ -0,0 +1,210 @@
+/*
+ * 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.logic;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.patch.AnyObjectPatch;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.GroupPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.AnyObjectTO;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.RemediationTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.RemediationDAO;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
+import org.apache.syncope.core.provisioning.api.data.RemediationDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class RemediationLogic extends AbstractTransactionalLogic<RemediationTO> {
+
+    @Autowired
+    private UserLogic userLogic;
+
+    @Autowired
+    private GroupLogic groupLogic;
+
+    @Autowired
+    private AnyObjectLogic anyObjectLogic;
+
+    @Autowired
+    private RemediationDataBinder binder;
+
+    @Autowired
+    private RemediationDAO remediationDAO;
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.REMEDIATION_LIST + "')")
+    @Transactional(readOnly = true)
+    public List<RemediationTO> list() {
+        return remediationDAO.findAll().stream().map(binder::getRemediationTO).collect(Collectors.toList());
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.REMEDIATION_READ + "')")
+    @Transactional(readOnly = true)
+    public RemediationTO read(final String key) {
+        Remediation remediation = remediationDAO.find(key);
+        if (remediation == null) {
+            LOG.error("Could not find remediation '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        return binder.getRemediationTO(remediation);
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.REMEDIATION_DELETE + "')")
+    public void delete(final String key) {
+        Remediation remediation = remediationDAO.find(key);
+        if (remediation == null) {
+            LOG.error("Could not find remediation '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        remediationDAO.delete(remediation);
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.REMEDIATION_REMEDY + "')")
+    public ProvisioningResult<?> remedy(final String key, final AnyTO anyTO, final boolean nullPriorityAsync) {
+        Remediation remediation = remediationDAO.find(key);
+        if (remediation == null) {
+            LOG.error("Could not find remediation '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        ProvisioningResult<?> result;
+        switch (remediation.getAnyTypeKind()) {
+            case USER:
+            default:
+                result = userLogic.create((UserTO) anyTO, true, nullPriorityAsync);
+                break;
+
+            case GROUP:
+                result = groupLogic.create((GroupTO) anyTO, nullPriorityAsync);
+                break;
+
+            case ANY_OBJECT:
+                result = anyObjectLogic.create((AnyObjectTO) anyTO, nullPriorityAsync);
+        }
+
+        remediationDAO.delete(remediation);
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.REMEDIATION_REMEDY + "')")
+    public ProvisioningResult<?> remedy(final String key, final AnyPatch anyPatch, final boolean nullPriorityAsync) {
+        Remediation remediation = remediationDAO.find(key);
+        if (remediation == null) {
+            LOG.error("Could not find remediation '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        ProvisioningResult<?> result;
+        switch (remediation.getAnyTypeKind()) {
+            case USER:
+            default:
+                result = userLogic.update((UserPatch) anyPatch, nullPriorityAsync);
+                break;
+
+            case GROUP:
+                result = groupLogic.update((GroupPatch) anyPatch, nullPriorityAsync);
+                break;
+
+            case ANY_OBJECT:
+                result = anyObjectLogic.update((AnyObjectPatch) anyPatch, nullPriorityAsync);
+        }
+
+        remediationDAO.delete(remediation);
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.REMEDIATION_REMEDY + "')")
+    public ProvisioningResult<?> remedy(final String key, final String anyKey, final boolean nullPriorityAsync) {
+        Remediation remediation = remediationDAO.find(key);
+        if (remediation == null) {
+            LOG.error("Could not find remediation '" + key + "'");
+
+            throw new NotFoundException(key);
+        }
+
+        ProvisioningResult<?> result;
+        switch (remediation.getAnyTypeKind()) {
+            case USER:
+            default:
+                result = userLogic.delete(anyKey, nullPriorityAsync);
+                break;
+
+            case GROUP:
+                result = groupLogic.delete(anyKey, nullPriorityAsync);
+                break;
+
+            case ANY_OBJECT:
+                result = anyObjectLogic.delete(anyKey, nullPriorityAsync);
+        }
+
+        remediationDAO.delete(remediation);
+
+        return result;
+    }
+
+    @Override
+    protected RemediationTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String key = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; key == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    key = (String) args[i];
+                } else if (args[i] instanceof RemediationTO) {
+                    key = ((RemediationTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (StringUtils.isNotBlank(key)) {
+            try {
+                return binder.getRemediationTO(remediationDAO.find(key));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
index 6a5a442..780e8ac 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
@@ -68,6 +68,7 @@ import org.quartz.SchedulerException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
@@ -136,11 +137,13 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_LIST + "')")
+    @Transactional(readOnly = true)
     public List<ReportTO> list() {
-        return reportDAO.findAll().stream().map(report -> binder.getReportTO(report)).collect(Collectors.toList());
+        return reportDAO.findAll().stream().map(binder::getReportTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_READ + "')")
+    @Transactional(readOnly = true)
     public ReportTO read(final String key) {
         Report report = reportDAO.find(key);
         if (report == null) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ReportTemplateLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportTemplateLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportTemplateLogic.java
index d59831c..5dc61c1 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReportTemplateLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReportTemplateLogic.java
@@ -39,6 +39,7 @@ import org.apache.syncope.core.persistence.api.entity.Report;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class ReportTemplateLogic extends AbstractTransactionalLogic<ReportTemplateTO> {
@@ -59,6 +60,7 @@ public class ReportTemplateLogic extends AbstractTransactionalLogic<ReportTempla
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_TEMPLATE_READ + "')")
+    @Transactional(readOnly = true)
     public ReportTemplateTO read(final String key) {
         ReportTemplate reportTemplate = reportTemplateDAO.find(key);
         if (reportTemplate == null) {
@@ -71,6 +73,7 @@ public class ReportTemplateLogic extends AbstractTransactionalLogic<ReportTempla
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_TEMPLATE_LIST + "')")
+    @Transactional(readOnly = true)
     public List<ReportTemplateTO> list() {
         return reportTemplateDAO.findAll().stream().
                 map(template -> getReportTemplateTO(template.getKey())).collect(Collectors.toList());

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceHistoryLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceHistoryLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceHistoryLogic.java
index d027e97..42126fb 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceHistoryLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceHistoryLogic.java
@@ -32,6 +32,7 @@ import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 public class ResourceHistoryLogic extends AbstractTransactionalLogic<ResourceHistoryConfTO> {
@@ -45,17 +46,8 @@ public class ResourceHistoryLogic extends AbstractTransactionalLogic<ResourceHis
     @Autowired
     private ResourceDataBinder binder;
 
-    private ResourceHistoryConfTO getResourceHistoryConfTO(final ExternalResourceHistoryConf history) {
-        ResourceHistoryConfTO historyTO = new ResourceHistoryConfTO();
-        historyTO.setKey(history.getKey());
-        historyTO.setCreator(history.getCreator());
-        historyTO.setCreation(history.getCreation());
-        historyTO.setResourceTO(history.getConf());
-
-        return historyTO;
-    }
-
     @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_HISTORY_LIST + "')")
+    @Transactional(readOnly = true)
     public List<ResourceHistoryConfTO> list(final String key) {
         ExternalResource resource = resourceDAO.find(key);
         if (resource == null) {
@@ -63,7 +55,7 @@ public class ResourceHistoryLogic extends AbstractTransactionalLogic<ResourceHis
         }
 
         return resourceHistoryConfDAO.findByEntity(resource).stream().
-                map(historyConf -> getResourceHistoryConfTO(historyConf)).collect(Collectors.toList());
+                map(binder::getResourceHistoryConfTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_HISTORY_RESTORE + "')")
@@ -93,7 +85,7 @@ public class ResourceHistoryLogic extends AbstractTransactionalLogic<ResourceHis
         if (!"list".equals(method.getName())) {
             try {
                 String key = (String) args[0];
-                return getResourceHistoryConfTO(resourceHistoryConfDAO.find(key));
+                return binder.getResourceHistoryConfTO(resourceHistoryConfDAO.find(key));
             } catch (Throwable ignore) {
                 LOG.debug("Unresolved reference", ignore);
                 throw new UnresolvedReferenceException(ignore);

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
index 1901393..e90b245 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
@@ -274,8 +274,7 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
     @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_LIST + "')")
     @Transactional(readOnly = true)
     public List<ResourceTO> list() {
-        return resourceDAO.findAll().stream().
-                map(resource -> binder.getResourceTO(resource)).collect(Collectors.toList());
+        return resourceDAO.findAll().stream().map(binder::getResourceTO).collect(Collectors.toList());
     }
 
     private Triple<ExternalResource, AnyType, Provision> connObjectInit(

http://git-wip-us.apache.org/repos/asf/syncope/blob/51b314fe/core/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java
index e8b9f8a..a04e518 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java
@@ -59,7 +59,7 @@ public class RoleLogic extends AbstractTransactionalLogic<RoleTO> {
     @PreAuthorize("hasRole('" + StandardEntitlement.ROLE_LIST + "')")
     @Transactional(readOnly = true)
     public List<RoleTO> list() {
-        return roleDAO.findAll().stream().map(role -> binder.getRoleTO(role)).collect(Collectors.toList());
+        return roleDAO.findAll().stream().map(binder::getRoleTO).collect(Collectors.toList());
     }
 
     @PreAuthorize("hasRole('" + StandardEntitlement.ROLE_CREATE + "')")