This is an automated email from the ASF dual-hosted git repository. aduprat pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 0980816d7dc2a0c35d8c3c3d41bbc33148379b27 Author: datph <dphamho...@linagora.com> AuthorDate: Mon Apr 8 16:34:50 2019 +0700 JAMES-2711 Add DeleteAPI to DeletedMessageVaultRoutes --- .../routes/DeletedMessagesVaultDeleteTask.java | 84 +++++++++ .../vault/routes/DeletedMessagesVaultRoutes.java | 58 ++++++- .../routes/DeletedMessagesVaultRoutesTest.java | 191 ++++++++++++++++++++- 3 files changed, 329 insertions(+), 4 deletions(-) diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultDeleteTask.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultDeleteTask.java new file mode 100644 index 0000000..9c886c5 --- /dev/null +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultDeleteTask.java @@ -0,0 +1,84 @@ +/**************************************************************** + * 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.vault.routes; + +import java.util.Optional; + +import org.apache.james.core.User; +import org.apache.james.mailbox.model.MessageId; +import org.apache.james.task.Task; +import org.apache.james.task.TaskExecutionDetails; +import org.apache.james.vault.DeletedMessageVault; + +import reactor.core.publisher.Mono; + +public class DeletedMessagesVaultDeleteTask implements Task { + + public class AdditionalInformation implements TaskExecutionDetails.AdditionalInformation { + + private final User user; + private final MessageId deleteMessageId; + + AdditionalInformation(User user, MessageId deleteMessageId) { + this.user = user; + this.deleteMessageId = deleteMessageId; + } + + public String getUser() { + return user.asString(); + } + + public String getDeleteMessageId() { + return deleteMessageId.serialize(); + } + } + + static final String TYPE = "deletedMessages/delete"; + + private final DeletedMessageVault vault; + private final User user; + private final MessageId messageId; + + DeletedMessagesVaultDeleteTask(DeletedMessageVault vault, User user, MessageId messageId) { + this.vault = vault; + this.user = user; + this.messageId = messageId; + } + + @Override + public Result run() { + return Mono.from(vault.delete(user, messageId)) + .doOnError(e -> LOGGER.error("Error while deleting message {} for user {} in DeletedMessageVault: {}", messageId, user, e)) + .thenReturn(Result.COMPLETED) + .blockOptional() + .orElse(Result.PARTIAL); + } + + @Override + public String type() { + return TYPE; + } + + @Override + public Optional<TaskExecutionDetails.AdditionalInformation> details() { + return Optional.of(new AdditionalInformation(user, messageId)); + } + +} diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java index 50f483e..e2faa4c 100644 --- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/main/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutes.java @@ -37,6 +37,7 @@ import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.apache.james.core.MailAddress; import org.apache.james.core.User; +import org.apache.james.mailbox.model.MessageId; import org.apache.james.task.Task; import org.apache.james.task.TaskId; import org.apache.james.task.TaskManager; @@ -157,11 +158,13 @@ public class DeletedMessagesVaultRoutes implements Routes { private final JsonExtractor<QueryElement> jsonExtractor; private final QueryTranslator queryTranslator; private final UsersRepository usersRepository; + private final MessageId.Factory messageIdFactory; @Inject @VisibleForTesting - DeletedMessagesVaultRoutes(DeletedMessageVault deletedMessageVault, RestoreService vaultRestore, ExportService vaultExport, JsonTransformer jsonTransformer, - TaskManager taskManager, QueryTranslator queryTranslator, UsersRepository usersRepository) { + DeletedMessagesVaultRoutes(DeletedMessageVault deletedMessageVault, RestoreService vaultRestore, ExportService vaultExport, + JsonTransformer jsonTransformer, TaskManager taskManager, QueryTranslator queryTranslator, + UsersRepository usersRepository, MessageId.Factory messageIdFactory) { this.deletedMessageVault = deletedMessageVault; this.vaultRestore = vaultRestore; this.vaultExport = vaultExport; @@ -170,6 +173,7 @@ public class DeletedMessagesVaultRoutes implements Routes { this.queryTranslator = queryTranslator; this.usersRepository = usersRepository; this.jsonExtractor = new JsonExtractor<>(QueryElement.class); + this.messageIdFactory = messageIdFactory; } @Override @@ -181,6 +185,7 @@ public class DeletedMessagesVaultRoutes implements Routes { public void define(Service service) { service.post(RESTORE_PATH, this::userActions, jsonTransformer); service.delete(ROOT_PATH, this::deleteWithScope, jsonTransformer); + service.delete(DELETE_PATH, this::deleteMessage, jsonTransformer); } @POST @@ -248,6 +253,41 @@ public class DeletedMessagesVaultRoutes implements Routes { return TaskIdDto.respond(response, taskId); } + @DELETE + @Path(DELETE_PATH) + @ApiOperation(value = "Delete message with messageId") + @ApiImplicitParams({ + @ApiImplicitParam( + required = true, + name = "user", + paramType = "path parameter", + dataType = "String", + defaultValue = "none", + example = "us...@james.org", + value = "Compulsory. Needs to be a valid username represent for an user had requested to restore deleted emails"), + @ApiImplicitParam( + required = true, + name = "messageId", + paramType = "path parameter", + dataType = "String", + defaultValue = "none", + value = "Compulsory, Need to be a valid messageId") + }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.CREATED_201, message = "Task is created", response = TaskIdDto.class), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - user param is invalid"), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - messageId param is invalid"), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.") + }) + private TaskIdDto deleteMessage(Request request, Response response) { + User user = extractUser(request); + validateUserExist(user); + MessageId messageId = parseMessageId(request); + + TaskId taskId = taskManager.submit(new DeletedMessagesVaultDeleteTask(deletedMessageVault, user, messageId)); + return TaskIdDto.respond(response, taskId); + } + private Task generateVaultScopeTask(Request request) { VaultScope scope = extractParam(request, SCOPE_QUERY_PARAM, this::getVaultScope); if (!scope.equals(VaultScope.EXPIRED)) { @@ -385,4 +425,18 @@ public class DeletedMessagesVaultRoutes implements Routes { .collect(Guavate.toImmutableList())))) .haltError(); } + + private MessageId parseMessageId(Request request) { + String messageIdAsString = request.params(MESSAGE_ID_PARAM); + try { + return messageIdFactory.fromString(messageIdAsString); + } catch (Exception e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .message("Can not deserialize the supplied messageId: " + messageIdAsString) + .cause(e) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .haltError(); + } + } } diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java index 74c3167..9375a6d 100644 --- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java @@ -31,12 +31,14 @@ import static org.apache.james.vault.DeletedMessageFixture.FINAL_STAGE; import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_1; import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_2; import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_3; +import static org.apache.james.vault.DeletedMessageFixture.MESSAGE_ID; import static org.apache.james.vault.DeletedMessageFixture.SUBJECT; import static org.apache.james.vault.DeletedMessageFixture.USER; import static org.apache.james.vault.DeletedMessageFixture.USER_2; import static org.apache.james.vault.DeletedMessageVaultSearchContract.MESSAGE_ID_GENERATOR; import static org.apache.james.webadmin.Constants.SEPARATOR; import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; +import static org.apache.james.webadmin.vault.routes.DeletedMessagesVaultRoutes.MESSAGE_PATH_PARAM; import static org.apache.james.webadmin.vault.routes.DeletedMessagesVaultRoutes.USERS; import static org.apache.james.webadmin.vault.routes.DeletedMessagesVaultRoutes.USER_PATH; import static org.apache.james.webadmin.vault.routes.RestoreService.RESTORE_MAILBOX_NAME; @@ -92,6 +94,7 @@ import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources; import org.apache.james.mailbox.model.FetchGroupImpl; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.MessageRange; import org.apache.james.mailbox.model.MessageResult; import org.apache.james.mailbox.model.MultimailboxesSearchQuery; @@ -120,7 +123,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import io.restassured.RestAssured; @@ -147,6 +149,8 @@ class DeletedMessagesVaultRoutesTest { "}"; private static final Domain DOMAIN = Domain.of("apache.org"); private static final String BOB_PATH = USERS + SEPARATOR + USER.asString(); + private static final String DELETED_MESSAGE_PARAM_PATH = MESSAGE_PATH_PARAM + SEPARATOR + MESSAGE_ID.serialize(); + private static final String BOB_DELETE_PATH = BOB_PATH + SEPARATOR + DELETED_MESSAGE_PARAM_PATH; private WebAdminServer webAdminServer; private MemoryDeletedMessagesVault vault; @@ -178,10 +182,11 @@ class DeletedMessagesVaultRoutesTest { exportService = new ExportService(blobExporting, blobStore, zipper, vault); QueryTranslator queryTranslator = new QueryTranslator(new InMemoryId.Factory()); usersRepository = createUsersRepository(); + MessageId.Factory messageIdFactory = new InMemoryMessageId.Factory(); webAdminServer = WebAdminUtils.createWebAdminServer( new DefaultMetricFactory(), new TasksRoutes(taskManager, jsonTransformer), - new DeletedMessagesVaultRoutes(vault, vaultRestore, exportService, jsonTransformer, taskManager, queryTranslator, usersRepository)); + new DeletedMessagesVaultRoutes(vault, vaultRestore, exportService, jsonTransformer, taskManager, queryTranslator, usersRepository, messageIdFactory)); webAdminServer.configure(NO_CONFIGURATION); webAdminServer.await(); @@ -2089,6 +2094,188 @@ class DeletedMessagesVaultRoutesTest { } } + @Nested + class DeleteTest { + + @Test + void deleteShouldReturnATaskCreated() { + when() + .delete(BOB_DELETE_PATH) + .then() + .statusCode(HttpStatus.CREATED_201) + .body("taskId", notNullValue()); + } + + @Test + void deleteShouldProduceASuccessfulTaskEvenNoDeletedMessageExisted() { + String taskId = + with() + .delete(BOB_DELETE_PATH) + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(taskId)) + .body("type", is(DeletedMessagesVaultDeleteTask.TYPE)) + .body("additionalInformation.user", is(USER.asString())) + .body("additionalInformation.deleteMessageId", is(MESSAGE_ID.serialize())) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + void deleteShouldProduceASuccessfulTask() { + vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block(); + vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); + + String taskId = + with() + .delete(BOB_DELETE_PATH) + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(taskId)) + .body("type", is(DeletedMessagesVaultDeleteTask.TYPE)) + .body("additionalInformation.user", is(USER.asString())) + .body("additionalInformation.deleteMessageId", is(MESSAGE_ID.serialize())) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + void deleteShouldNotAppendMessagesToUserMailbox() throws Exception { + vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block(); + vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); + + String taskId = + with() + .delete(BOB_DELETE_PATH) + .jsonPath() + .get("taskId"); + + with() + .basePath(TasksRoutes.BASE) + .get(taskId + "/await"); + + assertThat(hasAnyMail(USER)) + .isFalse(); + } + + @Test + void deleteShouldDeleteMessagesFromTheVault() throws Exception { + vault.append(USER, DELETED_MESSAGE, new ByteArrayInputStream(CONTENT)).block(); + + String taskId = + with() + .delete(BOB_DELETE_PATH) + .jsonPath() + .get("taskId"); + + with() + .basePath(TasksRoutes.BASE) + .get(taskId + "/await"); + + assertThat(Flux.from(vault.search(USER, Query.ALL)).toStream()) + .isEmpty(); + } + + @Test + void deleteShouldNotDeleteNotMatchMessagesFromTheVault() throws Exception { + vault.append(USER, DELETED_MESSAGE_2, new ByteArrayInputStream(CONTENT)).block(); + + String taskId = + with() + .delete(BOB_DELETE_PATH) + .jsonPath() + .get("taskId"); + + with() + .basePath(TasksRoutes.BASE) + .get(taskId + "/await"); + + assertThat(Flux.from(vault.search(USER, Query.ALL)).toStream()) + .contains(DELETED_MESSAGE_2); + } + + @Nested + class FailingDeleteTest { + + @Test + void deleteShouldProduceAFailedTask() { + doReturn(Mono.error(new RuntimeException("mock exception"))) + .when(vault) + .delete(any(), any()); + + String taskId = + with() + .delete(BOB_DELETE_PATH) + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("failed")) + .body("taskId", is(taskId)) + .body("type", is(DeletedMessagesVaultDeleteTask.TYPE)) + .body("additionalInformation.user", is(USER.asString())) + .body("additionalInformation.deleteMessageId", is(MESSAGE_ID.serialize())) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(nullValue())); + } + + @Test + void deleteShouldReturnInvalidWhenUserIsInvalid() { + when() + .delete(USERS + SEPARATOR + "not@va...@user.com" + SEPARATOR + DELETED_MESSAGE_PARAM_PATH) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is(notNullValue())) + .body("details", is(notNullValue())); + } + + @Test + void deleteShouldReturnNotFoundWhenUserIsNotFoundInSystem() { + when() + .delete(USERS + SEPARATOR + USER_2.asString() + SEPARATOR + DELETED_MESSAGE_PARAM_PATH) + .then() + .statusCode(HttpStatus.NOT_FOUND_404) + .body("statusCode", is(404)) + .body("type", is(ErrorResponder.ErrorType.NOT_FOUND.getType())) + .body("message", is(notNullValue())); + } + + @Test + void deleteShouldReturnInvalidWhenMessageIdIsInvalid() { + when() + .delete(BOB_PATH + SEPARATOR + MESSAGE_PATH_PARAM + SEPARATOR + "invalid") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is(notNullValue())); + } + } + } + private boolean hasAnyMail(User user) throws MailboxException { MailboxSession session = mailboxManager.createSystemSession(user.asString()); int limitToOneMessage = 1; --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org