You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2019/05/30 07:22:21 UTC

[james-project] 10/19: MAILBOX-351 WebAdmin route to allow me to reIndex previous tasks failures

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

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 3b040cad230da8e3b4dbb260bfcdbed2a16992d2
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu May 23 18:10:15 2019 +0700

    MAILBOX-351 WebAdmin route to  allow me to reIndex previous tasks failures
---
 .../org/apache/james/webadmin/dto/TaskIdDto.java   |   2 +-
 .../apache/james/webadmin/routes/TasksRoutes.java  |   3 +-
 .../apache/james/webadmin/dto/TaskIdDtoTest.java   |   4 +-
 .../james/webadmin/routes/TasksRoutesTest.java     |   4 +-
 .../james/webadmin/routes/ReindexingRoutes.java    |  76 ++++++-
 .../service/PreviousReIndexingService.java         |  60 ++++++
 .../webadmin/routes/ReindexingRoutesTest.java      | 222 +++++++++++++++++++++
 .../main/java/org/apache/james/task/TaskId.java    |  17 +-
 .../java/org/apache/james/task/TaskManager.java    |  22 +-
 9 files changed, 389 insertions(+), 21 deletions(-)

diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/TaskIdDto.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/TaskIdDto.java
index 35f88e9..fbb74f0 100644
--- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/TaskIdDto.java
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/dto/TaskIdDto.java
@@ -33,7 +33,7 @@ public class TaskIdDto {
 
     public static TaskIdDto respond(Response response, TaskId taskId) {
         response.status(HttpStatus.CREATED_201);
-        response.header(LOCATION.asString(), TasksRoutes.BASE + "/" + taskId.getValue());
+        response.header(LOCATION.asString(), TasksRoutes.BASE + "/" + taskId.asString());
         return TaskIdDto.from(taskId);
     }
 
diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java
index 7dd1204..84f6eff 100644
--- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/TasksRoutes.java
@@ -21,7 +21,6 @@ package org.apache.james.webadmin.routes;
 
 import java.util.List;
 import java.util.Optional;
-import java.util.UUID;
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
@@ -172,7 +171,7 @@ public class TasksRoutes implements Routes {
     private TaskId getTaskId(Request req) {
         try {
             String id = req.params("id");
-            return new TaskId(UUID.fromString(id));
+            return TaskId.fromString(id);
         } catch (Exception e) {
             throw ErrorResponder.builder()
                 .statusCode(HttpStatus.BAD_REQUEST_400)
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/dto/TaskIdDtoTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/dto/TaskIdDtoTest.java
index 5f85a54..3a593eb 100644
--- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/dto/TaskIdDtoTest.java
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/dto/TaskIdDtoTest.java
@@ -24,8 +24,6 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
-import java.util.UUID;
-
 import org.apache.james.task.TaskId;
 import org.eclipse.jetty.http.HttpStatus;
 import org.junit.Test;
@@ -38,7 +36,7 @@ public class TaskIdDtoTest {
     @Test
     public void respondShouldReturnCreatedWithTaskIdHeader() {
         Response response = mock(Response.class);
-        TaskId taskId = new TaskId(UUID.fromString(UID_VALUE));
+        TaskId taskId = TaskId.fromString(UID_VALUE);
 
         TaskIdDto.respond(response, taskId);
 
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java
index 39947c0..1642068 100644
--- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/TasksRoutesTest.java
@@ -99,7 +99,7 @@ public class TasksRoutesTest {
             .statusCode(HttpStatus.OK_200)
             .body("", hasSize(1))
             .body("[0].status", is(TaskManager.Status.IN_PROGRESS.getValue()))
-            .body("[0].taskId", is(taskId.getValue().toString()))
+            .body("[0].taskId", is(taskId.asString()))
             .body("[0].class", is(not(empty())));
     }
 
@@ -130,7 +130,7 @@ public class TasksRoutesTest {
             .statusCode(HttpStatus.OK_200)
             .body("", hasSize(1))
             .body("[0].status", is(TaskManager.Status.IN_PROGRESS.getValue()))
-            .body("[0].taskId", is(taskId.getValue().toString()))
+            .body("[0].taskId", is(taskId.asString()))
             .body("[0].type", is(Task.UNKNOWN));
     }
 
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java
index 8c5a773..083579e 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/ReindexingRoutes.java
@@ -28,13 +28,16 @@ import org.apache.james.core.User;
 import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
+import org.apache.james.mailbox.indexer.IndexingDetailInformation;
 import org.apache.james.mailbox.indexer.ReIndexer;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.task.Task;
 import org.apache.james.task.TaskId;
 import org.apache.james.task.TaskManager;
+import org.apache.james.task.TaskNotFoundException;
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.dto.TaskIdDto;
+import org.apache.james.webadmin.service.PreviousReIndexingService;
 import org.apache.james.webadmin.utils.ErrorResponder;
 import org.apache.james.webadmin.utils.JsonTransformer;
 import org.eclipse.jetty.http.HttpStatus;
@@ -62,19 +65,22 @@ public class ReindexingRoutes implements Routes {
 
     private static final String BASE_PATH = "/mailboxes";
     private static final String USER_QUERY_PARAM = "user";
+    private static final String RE_INDEX_FAILED_MESSAGES_QUERY_PARAM = "reIndexFailedMessagesOf";
     private static final String MAILBOX_PARAM = ":mailbox";
     private static final String UID_PARAM = ":uid";
     private static final String MAILBOX_PATH = BASE_PATH + "/" + MAILBOX_PARAM;
     private static final String MESSAGE_PATH = MAILBOX_PATH + "/mails/" + UID_PARAM;
 
     private final TaskManager taskManager;
+    private final PreviousReIndexingService previousReIndexingService;
     private final MailboxId.Factory mailboxIdFactory;
     private final ReIndexer reIndexer;
     private final JsonTransformer jsonTransformer;
 
     @Inject
-    ReindexingRoutes(TaskManager taskManager, MailboxId.Factory mailboxIdFactory, ReIndexer reIndexer, JsonTransformer jsonTransformer) {
+    ReindexingRoutes(TaskManager taskManager, PreviousReIndexingService previousReIndexingService, MailboxId.Factory mailboxIdFactory, ReIndexer reIndexer, JsonTransformer jsonTransformer) {
         this.taskManager = taskManager;
+        this.previousReIndexingService = previousReIndexingService;
         this.mailboxIdFactory = mailboxIdFactory;
         this.reIndexer = reIndexer;
         this.jsonTransformer = jsonTransformer;
@@ -110,7 +116,15 @@ public class ReindexingRoutes implements Routes {
             dataType = "String",
             defaultValue = "none",
             example = "?user=toto%40domain.tld",
-            value = "optional. If present, only mailboxes of that user will be reIndexed.")
+            value = "optional. If present, only mailboxes of that user will be reIndexed."),
+        @ApiImplicitParam(
+            name = "reIndexFailedMessagesOf",
+            paramType = "query parameter",
+            dataType = "String",
+            defaultValue = "none",
+            example = "?reIndexFailedMessagesOf=3294a976-ce63-491e-bd52-1b6f465ed7a2",
+            value = "optional. References a previously run reIndexing task. if present, the messages that this previous " +
+                "task failed to index will be reIndexed.")
     })
     @ApiResponses(value = {
         @ApiResponse(code = HttpStatus.CREATED_201, message = "Task is created", response = TaskIdDto.class),
@@ -118,10 +132,62 @@ public class ReindexingRoutes implements Routes {
         @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - details in the returned error message")
     })
     private TaskIdDto reIndexAll(Request request, Response response) {
-        if (Strings.isNullOrEmpty(request.queryParams(USER_QUERY_PARAM))) {
-            return wrap(request, response, reIndexer::reIndex);
+        boolean userReIndexing = !Strings.isNullOrEmpty(request.queryParams(USER_QUERY_PARAM));
+        boolean indexingCorrection = !Strings.isNullOrEmpty(request.queryParams(RE_INDEX_FAILED_MESSAGES_QUERY_PARAM));
+        if (userReIndexing && indexingCorrection) {
+            return rejectInvalidQueryParameterCombination();
+        }
+        if (userReIndexing) {
+            return wrap(request, response, () -> reIndexer.reIndex(extractUser(request)));
+        }
+        if (indexingCorrection) {
+                IndexingDetailInformation indexingDetailInformation = retrieveIndexingExecutionDetails(request);
+                return wrap(request, response, () -> reIndexer.reIndex(indexingDetailInformation.failures()));
+        }
+        return wrap(request, response, reIndexer::reIndex);
+    }
+
+    private IndexingDetailInformation retrieveIndexingExecutionDetails(Request request) {
+        TaskId taskId = getTaskId(request);
+        try {
+            return previousReIndexingService.retrieveIndexingExecutionDetails(taskId);
+        } catch (PreviousReIndexingService.NotAnIndexingRetryiableTask | PreviousReIndexingService.TaskNotYetFinishedException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+                .message("Invalid task id")
+                .cause(e)
+                .haltError();
+        } catch (TaskNotFoundException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+                .message("TaskId " + taskId.asString() + " does not exist")
+                .cause(e)
+                .haltError();
+        }
+    }
+
+    private TaskId getTaskId(Request request) {
+        try {
+            String id = request.queryParams(RE_INDEX_FAILED_MESSAGES_QUERY_PARAM);
+            return TaskId.fromString(id);
+        } catch (Exception e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .cause(e)
+                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+                .message("Invalid task id")
+                .haltError();
         }
-        return wrap(request, response, () -> reIndexer.reIndex(extractUser(request)));
+    }
+
+    private TaskIdDto rejectInvalidQueryParameterCombination() {
+        throw ErrorResponder.builder()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+            .message("Can not specify '" + USER_QUERY_PARAM + "' and '" + RE_INDEX_FAILED_MESSAGES_QUERY_PARAM + "' query parameters at the same time")
+            .haltError();
     }
 
     @POST
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/service/PreviousReIndexingService.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/service/PreviousReIndexingService.java
new file mode 100644
index 0000000..2df5879
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/service/PreviousReIndexingService.java
@@ -0,0 +1,60 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.service;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.indexer.IndexingDetailInformation;
+import org.apache.james.task.TaskExecutionDetails;
+import org.apache.james.task.TaskId;
+import org.apache.james.task.TaskManager;
+import org.apache.james.task.TaskNotFoundException;
+
+public class PreviousReIndexingService {
+    public static class TaskNotYetFinishedException extends RuntimeException {
+        TaskNotYetFinishedException(TaskManager.Status currentStatus) {
+            super("Task is not yet finished. Current status is: " + currentStatus);
+        }
+    }
+
+    public static class NotAnIndexingRetryiableTask extends RuntimeException {
+        NotAnIndexingRetryiableTask(String type) {
+            super("'" + type + "' is not a valid type of task for retrying a failed indexing");
+        }
+    }
+
+    private final TaskManager taskManager;
+
+    @Inject
+    public PreviousReIndexingService(TaskManager taskManager) {
+        this.taskManager = taskManager;
+    }
+
+    public IndexingDetailInformation retrieveIndexingExecutionDetails(TaskId taskId) throws NotAnIndexingRetryiableTask, TaskNotFoundException, TaskNotYetFinishedException {
+        TaskExecutionDetails executionDetails = taskManager.getExecutionDetails(taskId);
+        if (!executionDetails.getStatus().isFinished()) {
+            throw new TaskNotYetFinishedException(executionDetails.getStatus());
+        }
+        return executionDetails.getAdditionalInformation()
+            .filter(additionalInformation -> additionalInformation instanceof IndexingDetailInformation)
+            .map(additionalInformation -> (IndexingDetailInformation) additionalInformation)
+            .orElseThrow(() -> new NotAnIndexingRetryiableTask(executionDetails.getType()));
+    }
+}
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java
index e0af217..091ddb9 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/ReindexingRoutesTest.java
@@ -27,7 +27,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
@@ -48,6 +51,7 @@ import org.apache.james.metrics.logger.DefaultMetricFactory;
 import org.apache.james.task.MemoryTaskManager;
 import org.apache.james.webadmin.WebAdminServer;
 import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.service.PreviousReIndexingService;
 import org.apache.james.webadmin.utils.ErrorResponder;
 import org.apache.james.webadmin.utils.JsonTransformer;
 import org.apache.mailbox.tools.indexer.FullReindexingTask;
@@ -94,6 +98,7 @@ class ReindexingRoutesTest {
             new TasksRoutes(taskManager, jsonTransformer),
             new ReindexingRoutes(
                 taskManager,
+                new PreviousReIndexingService(taskManager),
                 mailboxIdFactory,
                 reIndexer,
                 jsonTransformer),
@@ -828,4 +833,221 @@ class ReindexingRoutesTest {
             }
         }
     }
+
+    @Nested
+    class FixingReIndexing {
+        @Nested
+        class Validation {
+            @Test
+            void fixingReIndexingShouldThrowOnMissingTaskQueryParameter() {
+                String taskId = with()
+                    .post("/mailboxes?task=reIndex")
+                    .jsonPath()
+                    .get("taskId");
+
+                with()
+                    .basePath(TasksRoutes.BASE)
+                    .get(taskId + "/await");
+
+                given()
+                    .queryParam("reIndexFailedMessagesOf", taskId)
+                .when()
+                    .post("/mailboxes")
+                .then()
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(400))
+                    .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                    .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`"));
+            }
+
+            @Test
+            void fixingReIndexingShouldThrowOnUserParameter() {
+                String taskId = with()
+                    .post("/mailboxes?task=reIndex")
+                    .jsonPath()
+                    .get("taskId");
+
+                with()
+                    .basePath(TasksRoutes.BASE)
+                    .get(taskId + "/await");
+
+                given()
+                    .queryParam("reIndexFailedMessagesOf", taskId)
+                    .queryParam("task", "reIndex")
+                    .queryParam("user", "bob@domain.tld")
+                .when()
+                    .post("/mailboxes")
+                .then()
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(400))
+                    .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                    .body("message", is("Can not specify 'user' and 'reIndexFailedMessagesOf' query parameters at the same time"));
+            }
+
+            @Test
+            void fixingReIndexingShouldFailWithBadTask() {
+                String taskId = with()
+                    .post("/mailboxes?task=reIndex")
+                    .jsonPath()
+                    .get("taskId");
+
+                with()
+                    .basePath(TasksRoutes.BASE)
+                    .get(taskId + "/await");
+
+                given()
+                    .queryParam("reIndexFailedMessagesOf", taskId)
+                .when()
+                    .post("/mailboxes?task=bad")
+                .then()
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(400))
+                    .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                    .body("message", is("task query parameter is mandatory. The only supported value is `reIndex`"));
+            }
+
+            @Test
+            void fixingReIndexingShouldRejectNotExistingTask() {
+                String taskId = "bbdb69c9-082a-44b0-a85a-6e33e74287a5";
+
+                given()
+                    .queryParam("reIndexFailedMessagesOf", taskId)
+                .when()
+                    .post("/mailboxes?task=bad")
+                .then()
+                    .statusCode(HttpStatus.BAD_REQUEST_400)
+                    .body("statusCode", is(400))
+                    .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                    .body("message", is("TaskId bbdb69c9-082a-44b0-a85a-6e33e74287a5 does not exist"));
+            }
+        }
+
+        @Nested
+        class TaskDetails {
+            @Test
+            void fixingReIndexingShouldNotFailWhenNoMail() {
+                String taskId = with()
+                    .post("/mailboxes?task=reIndex")
+                    .jsonPath()
+                    .get("taskId");
+
+                with()
+                    .basePath(TasksRoutes.BASE)
+                    .get(taskId + "/await");
+
+                String fixingTaskId = with()
+                    .queryParam("reIndexFailedMessagesOf", taskId)
+                    .queryParam("task", "reIndex")
+                    .post("/mailboxes")
+                    .jsonPath()
+                    .get("taskId");
+
+                given()
+                    .basePath(TasksRoutes.BASE)
+                .when()
+                    .get(fixingTaskId + "/await")
+                .then()
+                    .body("status", is("completed"))
+                    .body("taskId", is(notNullValue()))
+                    .body("type", is("ReIndexPreviousFailures"))
+                    .body("additionalInformation.successfullyReprocessMailCount", is(0))
+                    .body("additionalInformation.failedReprocessedMailCount", is(0))
+                    .body("startedDate", is(notNullValue()))
+                    .body("submitDate", is(notNullValue()))
+                    .body("completedDate", is(notNullValue()));
+            }
+
+            @Test
+            void fixingReIndexingShouldReturnTaskDetailsWhenMail() throws Exception {
+                MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME);
+                mailboxManager.createMailbox(INBOX, systemSession).get();
+                mailboxManager.getMailbox(INBOX, systemSession)
+                    .appendMessage(
+                        MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"),
+                        systemSession);
+
+                doThrow(new RuntimeException()).when(searchIndex).add(any(MailboxSession.class), any(Mailbox.class), any(MailboxMessage.class));
+
+                String taskId = with()
+                    .post("/mailboxes?task=reIndex")
+                    .jsonPath()
+                    .get("taskId");
+
+                with()
+                    .basePath(TasksRoutes.BASE)
+                    .get(taskId + "/await");
+
+                doNothing().when(searchIndex).add(any(MailboxSession.class), any(Mailbox.class), any(MailboxMessage.class));
+
+                String fixingTaskId = with()
+                    .queryParam("reIndexFailedMessagesOf", taskId)
+                    .queryParam("task", "reIndex")
+                    .post("/mailboxes")
+                    .jsonPath()
+                    .get("taskId");
+
+                given()
+                    .basePath(TasksRoutes.BASE)
+                .when()
+                    .get(fixingTaskId + "/await")
+                .then()
+                    .body("status", is("completed"))
+                    .body("taskId", is(notNullValue()))
+                    .body("type", is("ReIndexPreviousFailures"))
+                    .body("additionalInformation.successfullyReprocessMailCount", is(1))
+                    .body("additionalInformation.failedReprocessedMailCount", is(0))
+                    .body("startedDate", is(notNullValue()))
+                    .body("submitDate", is(notNullValue()))
+                    .body("completedDate", is(notNullValue()));
+            }
+        }
+
+        @Nested
+        class SideEffects {
+            @Test
+            void fixingReprocessingShouldPerformReprocessingWhenMail() throws Exception {
+                MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME);
+                MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get();
+                ComposedMessageId createdMessage = mailboxManager.getMailbox(INBOX, systemSession)
+                    .appendMessage(
+                        MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"),
+                        systemSession);
+
+                doThrow(new RuntimeException()).when(searchIndex).add(any(MailboxSession.class), any(Mailbox.class), any(MailboxMessage.class));
+
+                String taskId = with()
+                    .post("/mailboxes?task=reIndex")
+                    .jsonPath()
+                    .get("taskId");
+
+                with()
+                    .basePath(TasksRoutes.BASE)
+                    .get(taskId + "/await");
+
+                reset(searchIndex);
+
+                String fixingTaskId = with()
+                    .queryParam("reIndexFailedMessagesOf", taskId)
+                    .queryParam("task", "reIndex")
+                    .post("/mailboxes")
+                    .jsonPath()
+                    .get("taskId");
+
+                with()
+                    .basePath(TasksRoutes.BASE)
+                    .get(fixingTaskId + "/await")
+                    .then()
+                    .body("status", is("completed"));
+
+                ArgumentCaptor<MailboxMessage> messageCaptor = ArgumentCaptor.forClass(MailboxMessage.class);
+                ArgumentCaptor<Mailbox> mailboxCaptor = ArgumentCaptor.forClass(Mailbox.class);
+                verify(searchIndex).add(any(MailboxSession.class), mailboxCaptor.capture(), messageCaptor.capture());
+                verifyNoMoreInteractions(searchIndex);
+
+                assertThat(mailboxCaptor.getValue()).matches(mailbox -> mailbox.getMailboxId().equals(mailboxId));
+                assertThat(messageCaptor.getValue()).matches(message -> message.getMailboxId().equals(mailboxId)
+                    && message.getUid().equals(createdMessage.getUid()));
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/server/task/src/main/java/org/apache/james/task/TaskId.java b/server/task/src/main/java/org/apache/james/task/TaskId.java
index 4b66feb..3f30712 100644
--- a/server/task/src/main/java/org/apache/james/task/TaskId.java
+++ b/server/task/src/main/java/org/apache/james/task/TaskId.java
@@ -23,11 +23,22 @@ import java.util.Objects;
 import java.util.UUID;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
 
 public class TaskId {
 
     public static TaskId generateTaskId() {
-        return new TaskId(UUID.randomUUID());
+        return fromUUID(UUID.randomUUID());
+    }
+
+    public static TaskId fromUUID(UUID uuid) {
+        Preconditions.checkNotNull(uuid, "'uuid' should not be null");
+        return new TaskId(uuid);
+    }
+
+    public static TaskId fromString(String uuid) {
+        Preconditions.checkNotNull(uuid, "'uuid' should not be null");
+        return fromUUID(UUID.fromString(uuid));
     }
 
     private final UUID value;
@@ -40,6 +51,10 @@ public class TaskId {
         return value;
     }
 
+    public String asString() {
+        return value.toString();
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof TaskId) {
diff --git a/server/task/src/main/java/org/apache/james/task/TaskManager.java b/server/task/src/main/java/org/apache/james/task/TaskManager.java
index de3e16b..b04f2c2 100644
--- a/server/task/src/main/java/org/apache/james/task/TaskManager.java
+++ b/server/task/src/main/java/org/apache/james/task/TaskManager.java
@@ -23,13 +23,15 @@ import java.util.Arrays;
 import java.util.List;
 
 public interface TaskManager {
+    boolean FINISHED = true;
+
     enum Status {
-        WAITING("waiting"),
-        IN_PROGRESS("inProgress"),
-        COMPLETED("completed"),
-        CANCEL_REQUESTED("canceledRequested"),
-        CANCELLED("canceled"),
-        FAILED("failed");
+        WAITING("waiting", !FINISHED),
+        IN_PROGRESS("inProgress", !FINISHED),
+        CANCEL_REQUESTED("canceledRequested", !FINISHED),
+        COMPLETED("completed", FINISHED),
+        CANCELLED("canceled", FINISHED),
+        FAILED("failed", FINISHED);
 
         public static Status fromString(String value) {
             return Arrays.stream(values())
@@ -40,14 +42,20 @@ public interface TaskManager {
         }
 
         private final String value;
+        private final boolean finished;
 
-        Status(String value) {
+        Status(String value, boolean finished) {
             this.value = value;
+            this.finished = finished;
         }
 
         public String getValue() {
             return value;
         }
+
+        public boolean isFinished() {
+            return finished;
+        }
     }
 
     TaskId submit(Task task);


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org