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 2021/08/18 08:45:29 UTC

[syncope] 02/02: [SYNCOPE-1641] New REST endpoint to purge Propagation Tasks

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

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

commit 7878aceeb0adcf12d1c7c0bb65e35957ad10f76c
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed Aug 18 10:28:10 2021 +0200

    [SYNCOPE-1641] New REST endpoint to purge Propagation Tasks
---
 .../common/rest/api/service/TaskService.java       | 20 +++++
 .../org/apache/syncope/core/logic/TaskLogic.java   |  6 ++
 .../syncope/core/persistence/api/dao/TaskDAO.java  |  4 +
 .../core/persistence/jpa/dao/JPATaskDAO.java       | 97 +++++++++++++++++-----
 .../core/rest/cxf/service/TaskServiceImpl.java     | 12 +++
 .../syncope/fit/core/PropagationTaskITCase.java    | 23 +++++
 6 files changed, 139 insertions(+), 23 deletions(-)

diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
index 07a18d8..92ab611 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
@@ -27,6 +27,8 @@ 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.Date;
+import java.util.List;
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.BeanParam;
 import javax.ws.rs.Consumes;
@@ -45,6 +47,7 @@ import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.to.TaskTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.TaskQuery;
@@ -138,4 +141,21 @@ public interface TaskService extends ExecutableService {
     @Path("{type}/{key}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     void delete(@NotNull @PathParam("type") TaskType type, @NotNull @PathParam("key") String key);
+
+    /**
+     * Deletes all the propagation tasks whose latest execution is matching the given conditions.
+     * At least one matching condition must be specified.
+     *
+     * @param since
+     * @param statuses execution status(es) to match
+     * @return deleted propagation tasks
+     */
+    @ApiResponses({
+        @ApiResponse(responseCode = "200", description = "List of deleted propagation tasks, as Entity"),
+        @ApiResponse(responseCode = "412", description = "At least one matching condition must be specified") })
+    @DELETE
+    @Path("PROPAGATION/purge")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    Response purgePropagations(
+            @QueryParam("since") Date since, @QueryParam("statuses") List<ExecStatus> statuses);
 }
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 a06242a..5c51b68 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
@@ -37,6 +37,7 @@ import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.to.SchedTaskTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.lib.types.JobType;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
@@ -452,6 +453,11 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
         doActionJob(JobNamer.getJobKey(task), action);
     }
 
+    @PreAuthorize("hasRole('" + StandardEntitlement.TASK_DELETE + "')")
+    public List<PropagationTaskTO> purgePropagations(final Date since, final List<ExecStatus> statuses) {
+        return taskDAO.purgePropagations(since, statuses);
+    }
+
     @Override
     protected TaskTO resolveReference(final Method method, final Object... args)
             throws UnresolvedReferenceException {
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java
index f619154..7161479 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java
@@ -18,8 +18,11 @@
  */
 package org.apache.syncope.core.persistence.api.dao;
 
+import java.util.Date;
 import java.util.List;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
@@ -73,4 +76,5 @@ public interface TaskDAO extends DAO<Task> {
 
     void deleteAll(ExternalResource resource, TaskType type);
 
+    List<PropagationTaskTO> purgePropagations(Date since, List<ExecStatus> statuses);
 }
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 3e8f80e..6987113 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
@@ -21,14 +21,18 @@ package org.apache.syncope.core.persistence.jpa.dao;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 import javax.persistence.DiscriminatorValue;
 import javax.persistence.ManyToOne;
 import javax.persistence.OneToMany;
 import javax.persistence.Query;
 import javax.persistence.TypedQuery;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ExecStatus;
 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;
@@ -37,6 +41,7 @@ import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Notification;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
@@ -51,6 +56,7 @@ import org.apache.syncope.core.persistence.jpa.entity.task.JPATaskExec;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 import org.springframework.util.ReflectionUtils;
 
 @Repository
@@ -203,7 +209,7 @@ public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
     @Transactional(readOnly = true)
     @Override
     public <T extends Task> List<T> findAll(final TaskType type) {
-        return findAll(type, null, null, null, null, -1, -1, Collections.<OrderByClause>emptyList());
+        return findAll(type, null, null, null, null, -1, -1, Collections.emptyList());
     }
 
     private StringBuilder buildFindAllQuery(
@@ -301,7 +307,6 @@ public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
     }
 
     private String toOrderByStatement(final Class<? extends Task> beanClass, final List<OrderByClause> orderByClauses) {
-
         StringBuilder statement = new StringBuilder();
 
         statement.append(" ORDER BY ");
@@ -345,7 +350,6 @@ public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
     }
 
     @Override
-    @SuppressWarnings("unchecked")
     public <T extends Task> List<T> findAll(
             final TaskType type,
             final ExternalResource resource,
@@ -405,7 +409,23 @@ public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
             query.setMaxResults(itemsPerPage);
         }
 
-        return buildResult(query.getResultList());
+        List<T> result = new ArrayList<>();
+
+        @SuppressWarnings("unchecked")
+        List<Object> raw = query.getResultList();
+        raw.stream().map(key -> key instanceof Object[]
+                ? (String) ((Object[]) key)[0]
+                : ((String) key)).forEach(key -> {
+
+            T task = find(key);
+            if (task == null) {
+                LOG.error("Could not find task with key {}, even if returned by native query", key);
+            } else if (!result.contains(task)) {
+                result.add(task);
+            }
+        });
+
+        return result;
     }
 
     @Override
@@ -452,9 +472,7 @@ 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);
-            });
+            remediationDAO.findByPullTask((PullTask) task).forEach(remediation -> remediation.setPullTask(null));
         }
 
         entityManager().remove(task);
@@ -462,27 +480,60 @@ public class JPATaskDAO extends AbstractDAO<Task> implements TaskDAO {
 
     @Override
     public void deleteAll(final ExternalResource resource, final TaskType type) {
-        findAll(type, resource, null, null, null, -1, -1, Collections.<OrderByClause>emptyList()).
-                stream().map(Entity::getKey).forEach(task -> delete(task));
+        findAll(type, resource, null, null, null, -1, -1, Collections.emptyList()).
+                stream().map(Entity::getKey).forEach(this::delete);
     }
 
-    private <T extends Task> List<T> buildResult(final List<Object> raw) {
-        List<T> result = new ArrayList<>();
+    @Override
+    public List<PropagationTaskTO> purgePropagations(final Date since, final List<ExecStatus> statuses) {
+        StringBuilder queryString = new StringBuilder("SELECT t.task_id "
+                + "FROM TaskExec t INNER JOIN Task z ON t.task_id=z.id AND z.dtype='PropagationTask' "
+                + "WHERE t.enddate=(SELECT MAX(e.enddate) FROM TaskExec e WHERE e.task_id=t.task_id) ");
+
+        List<Object> queryParameters = new ArrayList<>();
+        if (since != null) {
+            queryParameters.add(since);
+            queryString.append("AND t.enddate <= ?").append(queryParameters.size()).append(' ');
+        }
+        if (!CollectionUtils.isEmpty(statuses)) {
+            queryString.append("AND (").
+                    append(statuses.stream().map(status -> {
+                        queryParameters.add(status.name());
+                        return "t.status = ?" + queryParameters.size();
+                    }).collect(Collectors.joining(" OR "))).
+                    append(")");
+        }
 
-        for (Object anyKey : raw) {
-            String actualKey = anyKey instanceof Object[]
-                    ? (String) ((Object[]) anyKey)[0]
-                    : ((String) anyKey);
+        Query query = entityManager().createNativeQuery(queryString.toString());
+        for (int i = 1; i <= queryParameters.size(); i++) {
+            query.setParameter(i, queryParameters.get(i - 1));
+        }
 
-            @SuppressWarnings("unchecked")
-            T task = find(actualKey);
-            if (task == null) {
-                LOG.error("Could not find task with id {}, even if returned by native query", actualKey);
-            } else if (!result.contains(task)) {
-                result.add(task);
+        @SuppressWarnings("unchecked")
+        List<Object> raw = query.getResultList();
+
+        List<PropagationTaskTO> purged = new ArrayList<>();
+        raw.stream().map(Object::toString).distinct().forEach(key -> {
+            PropagationTask task = find(key);
+            if (task != null) {
+                PropagationTaskTO taskTO = new PropagationTaskTO();
+
+                taskTO.setOperation(task.getOperation());
+                taskTO.setConnObjectKey(task.getConnObjectKey());
+                taskTO.setOldConnObjectKey(task.getOldConnObjectKey());
+                taskTO.setAttributes(task.getSerializedAttributes());
+                taskTO.setResource(task.getResource().getKey());
+                taskTO.setObjectClassName(task.getObjectClassName());
+                taskTO.setAnyTypeKind(task.getAnyTypeKind());
+                taskTO.setAnyType(task.getAnyType());
+                taskTO.setEntityKey(task.getEntityKey());
+
+                purged.add(taskTO);
+
+                delete(task);
             }
-        }
+        });
 
-        return result;
+        return purged;
     }
 }
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
index cd48508..6efb30f 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.rest.cxf.service;
 
 import java.net.URI;
+import java.util.Date;
 import java.util.List;
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.core.Response;
@@ -26,6 +27,7 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.to.TaskTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.TaskQuery;
@@ -34,6 +36,7 @@ import org.apache.syncope.core.logic.AbstractExecutableLogic;
 import org.apache.syncope.core.logic.TaskLogic;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 
 @Service
 public class TaskServiceImpl extends AbstractExecutableService implements TaskService {
@@ -91,4 +94,13 @@ public class TaskServiceImpl extends AbstractExecutableService implements TaskSe
     public void update(final TaskType type, final SchedTaskTO taskTO) {
         logic.updateSchedTask(type, taskTO);
     }
+
+    @Override
+    public Response purgePropagations(final Date since, final List<ExecStatus> statuses) {
+        if (since == null && CollectionUtils.isEmpty(statuses)) {
+            return Response.status(Response.Status.PRECONDITION_FAILED).build();
+        }
+
+        return Response.ok(logic.purgePropagations(since, statuses)).build();
+    }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
index 260be17..13a469c 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.IOException;
 import java.text.ParseException;
@@ -39,6 +40,8 @@ import org.apache.syncope.client.lib.batch.BatchRequest;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import javax.ws.rs.core.GenericType;
+import javax.xml.ws.WebServiceException;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientException;
@@ -268,6 +271,26 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
     }
 
     @Test
+    public void purgePropagations() {
+        try {
+            taskService.purgePropagations(null, null);
+            fail();
+        } catch (WebServiceException e) {
+            assertNotNull(e);
+        }
+
+        Calendar oneWeekAgo = Calendar.getInstance();
+        oneWeekAgo.add(Calendar.WEEK_OF_YEAR, -1);
+        Response response = taskService.purgePropagations(
+                oneWeekAgo.getTime(), Collections.singletonList(ExecStatus.SUCCESS));
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+
+        List<PropagationTaskTO> deleted = response.readEntity(new GenericType<List<PropagationTaskTO>>() {
+        });
+        assertNotNull(deleted);
+    }
+
+    @Test
     public void issueSYNCOPE741() {
         for (int i = 0; i < 3; i++) {
             taskService.execute(new ExecuteQuery.Builder().