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().